Bug 1658268: Optimise Boolean in CacheIR and Warp. r=jandem

The CacheIR implementation uses the new `LoadValueTruthyResult` op instead of
the existing `LoadThingTruthyResult` ops to mirror how Warp implements boolean
coercion: Warp doesn't use ICs but instead uses MNot resp. MTest with Value
typed inputs.
Mirroring this approach avoids to have (from the user POV) strange performance
differences where for example using `if (Boolean(v))` can be faster than just
`if (v)`, when `v` is Value-typed, but monomorphic.

Differential Revision: https://phabricator.services.mozilla.com/D86524
This commit is contained in:
André Bargull 2020-08-12 18:18:54 +00:00
parent a6e858b6ff
commit c258dcd9fb
6 changed files with 254 additions and 6 deletions

View File

@ -0,0 +1,67 @@
function wrapper(values) {
function test(values) {
var expected = values.map(v => !!v);
for (var i = 0; i < 100; ++i) {
var ix = i % values.length;
var val = values[ix];
var actual = Boolean(val);
assertEq(actual, expected[ix]);
}
}
for (var i = 0; i < 2; ++i) {
test(values);
}
}
function makeTest() {
// Create a copy to avoid type pollution.
return Function(`return ${wrapper}`)();
}
// Use a new compartment to create a wrapper.
var g = newGlobal({newCompartment: true});
var testValues = {
boolean: [true, false],
int32: [-2147483648, -1, 0, 1, 2147483647],
double: [-Infinity, -1.5, -1, -0.5, -0, +0, +0.5, +1, +1.5, Infinity, NaN],
string: ["", "true", "false", "0", "1", "hello"],
symbol: [Symbol(), Symbol("desc"), Symbol.iterator],
bigint: [
-(2n ** 1000n),
-18446744073709551616n, // -(2n**64n)
-9223372036854775808n, // -(2n**63n)
-4294967296n, // -(2**32)
-2147483648n, // -(2**31)
-1n, 0n, 1n,
2147483648n, // 2**31
4294967296n, // 2**32
9223372036854775808n, // 2n**63n
18446744073709551616n, // 2n**64n
2n ** 1000n,
],
object: [{}, [], function(){}, new Proxy({}, {}), createIsHTMLDDA(), g.eval("createIsHTMLDDA()")],
null: [null],
undefined: [undefined],
};
for (var values of Object.values(testValues)) {
makeTest()(values);
}
// boolean and int32
makeTest()([].concat(testValues.boolean, testValues.int32));
// int32 and double
makeTest()([].concat(testValues.int32, testValues.double));
// null and undefined
makeTest()([].concat(testValues.null, testValues.undefined));
// null, undefined, and object
makeTest()([].concat(testValues.null, testValues.undefined, testValues.object));
// all values
makeTest()(Object.values(testValues).flat());

View File

@ -7392,6 +7392,35 @@ AttachDecision CallIRGenerator::tryAttachAtomicsIsLockFree(
return AttachDecision::Attach;
}
AttachDecision CallIRGenerator::tryAttachBoolean(HandleFunction callee) {
// Need zero or one argument.
if (argc_ > 1) {
return AttachDecision::NoAction;
}
// Initialize the input operand.
Int32OperandId argcId(writer.setInputOperandId(0));
// Guard callee is the 'Boolean' native function.
emitNativeCalleeGuard(callee);
if (argc_ == 0) {
writer.loadBooleanResult(false);
} else {
ValOperandId valId =
writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
writer.loadValueTruthyResult(valId);
}
// This stub doesn't need to be monitored, because it always returns a bool.
writer.returnFromIC();
cacheIRStubKind_ = BaselineCacheIRStubKind::Regular;
trackAttached("Boolean");
return AttachDecision::Attach;
}
AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) {
MOZ_ASSERT(callee->isNativeWithoutJitEntry());
if (callee->native() != fun_call) {
@ -8494,6 +8523,10 @@ AttachDecision CallIRGenerator::tryAttachInlinableNative(
case InlinableNative::AtomicsIsLockFree:
return tryAttachAtomicsIsLockFree(callee);
// Boolean natives.
case InlinableNative::Boolean:
return tryAttachBoolean(callee);
default:
return AttachDecision::NoAction;
}

View File

@ -1722,6 +1722,7 @@ class MOZ_RAII CallIRGenerator : public IRGenerator {
AttachDecision tryAttachAtomicsLoad(HandleFunction callee);
AttachDecision tryAttachAtomicsStore(HandleFunction callee);
AttachDecision tryAttachAtomicsIsLockFree(HandleFunction callee);
AttachDecision tryAttachBoolean(HandleFunction callee);
AttachDecision tryAttachFunCall(HandleFunction calleeFunc);
AttachDecision tryAttachFunApply(HandleFunction calleeFunc);

View File

@ -5756,12 +5756,23 @@ bool CacheIRCompiler::emitLoadObjectTruthyResult(ObjOperandId objId) {
masm.jump(&done);
masm.bind(&slowPath);
masm.setupUnalignedABICall(scratch);
masm.passABIArg(obj);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::EmulatesUndefined));
masm.convertBoolToInt32(ReturnReg, ReturnReg);
masm.xor32(Imm32(1), ReturnReg);
masm.tagValue(JSVAL_TYPE_BOOLEAN, ReturnReg, output.valueReg());
{
LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(),
liveVolatileFloatRegs());
volatileRegs.takeUnchecked(scratch);
volatileRegs.takeUnchecked(output);
masm.PushRegsInMask(volatileRegs);
masm.setupUnalignedABICall(scratch);
masm.passABIArg(obj);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::EmulatesUndefined));
masm.convertBoolToInt32(ReturnReg, scratch);
masm.xor32(Imm32(1), scratch);
masm.PopRegsInMask(volatileRegs);
masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
}
masm.bind(&done);
return true;
@ -5786,6 +5797,122 @@ bool CacheIRCompiler::emitLoadBigIntTruthyResult(BigIntOperandId bigIntId) {
return true;
}
bool CacheIRCompiler::emitLoadValueTruthyResult(ValOperandId inputId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
AutoOutputRegister output(*this);
ValueOperand value = allocator.useValueRegister(masm, inputId);
AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
AutoScratchRegister scratch2(allocator, masm);
AutoScratchFloatRegister floatReg(this);
Label ifFalse, ifTrue, done;
{
ScratchTagScope tag(masm, value);
masm.splitTagForTest(value, tag);
masm.branchTestUndefined(Assembler::Equal, tag, &ifFalse);
masm.branchTestNull(Assembler::Equal, tag, &ifFalse);
Label notBoolean;
masm.branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
{
ScratchTagScopeRelease _(&tag);
masm.branchTestBooleanTruthy(false, value, &ifFalse);
masm.jump(&ifTrue);
}
masm.bind(&notBoolean);
Label notInt32;
masm.branchTestInt32(Assembler::NotEqual, tag, &notInt32);
{
ScratchTagScopeRelease _(&tag);
masm.branchTestInt32Truthy(false, value, &ifFalse);
masm.jump(&ifTrue);
}
masm.bind(&notInt32);
Label notObject;
masm.branchTestObject(Assembler::NotEqual, tag, &notObject);
{
ScratchTagScopeRelease _(&tag);
Register obj = masm.extractObject(value, scratch1);
Label slowPath;
masm.branchIfObjectEmulatesUndefined(obj, scratch2, &slowPath, &ifFalse);
masm.jump(&ifTrue);
masm.bind(&slowPath);
{
LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(),
liveVolatileFloatRegs());
volatileRegs.takeUnchecked(scratch1);
volatileRegs.takeUnchecked(scratch2);
volatileRegs.takeUnchecked(output);
masm.PushRegsInMask(volatileRegs);
masm.setupUnalignedABICall(scratch2);
masm.passABIArg(obj);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::EmulatesUndefined));
masm.storeCallBoolResult(scratch2);
masm.PopRegsInMask(volatileRegs);
masm.branchTest32(Assembler::NonZero, scratch2, scratch2, &ifFalse);
masm.jump(&ifTrue);
}
}
masm.bind(&notObject);
Label notString;
masm.branchTestString(Assembler::NotEqual, tag, &notString);
{
ScratchTagScopeRelease _(&tag);
masm.branchTestStringTruthy(false, value, &ifFalse);
masm.jump(&ifTrue);
}
masm.bind(&notString);
Label notBigInt;
masm.branchTestBigInt(Assembler::NotEqual, tag, &notBigInt);
{
ScratchTagScopeRelease _(&tag);
masm.branchTestBigIntTruthy(false, value, &ifFalse);
masm.jump(&ifTrue);
}
masm.bind(&notBigInt);
masm.branchTestSymbol(Assembler::Equal, tag, &ifTrue);
#ifdef DEBUG
Label isDouble;
masm.branchTestDouble(Assembler::Equal, tag, &isDouble);
masm.assumeUnreachable("Unexpected value type");
masm.bind(&isDouble);
#endif
{
ScratchTagScopeRelease _(&tag);
masm.unboxDouble(value, floatReg);
masm.branchTestDoubleTruthy(false, floatReg, &ifFalse);
}
// Fall through to true case.
}
masm.bind(&ifTrue);
masm.moveValue(BooleanValue(true), output.valueReg());
masm.jump(&done);
masm.bind(&ifFalse);
masm.moveValue(BooleanValue(false), output.valueReg());
masm.bind(&done);
return true;
}
bool CacheIRCompiler::emitLoadNewObjectFromTemplateResult(
uint32_t templateObjectOffset, uint32_t, uint32_t) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

View File

@ -2285,6 +2285,13 @@
args:
bigInt: BigIntId
- name: LoadValueTruthyResult
shared: true
transpile: true
cost_estimate: 4
args:
input: ValId
- name: LoadValueResult
shared: false
transpile: false

View File

@ -2507,6 +2507,19 @@ bool WarpCacheIRTranspiler::emitAtomicsIsLockFreeResult(
return true;
}
bool WarpCacheIRTranspiler::emitLoadValueTruthyResult(ValOperandId inputId) {
MDefinition* input = getOperand(inputId);
// Convert to bool with the '!!' idiom.
auto* resultInverted = MNot::New(alloc(), input);
add(resultInverted);
auto* result = MNot::New(alloc(), resultInverted);
add(result);
pushResult(result);
return true;
}
bool WarpCacheIRTranspiler::emitLoadArgumentSlot(ValOperandId resultId,
uint32_t slotIndex) {
// Reverse of GetIndexOfArgument specialized to !hasArgumentArray.