Skip to content

Commit b1220f6

Browse files
committed
Bug 1703469: Guard on script instead of function for non-first getter/setters r=jandem
Caching the callee inside the CacheIRWriter seemed simpler than trying to rewrite all the callers of `emitGuardGetterSetterSlot` / `emitCallGetterResultGuards` / etc to conditionally return an operand for the callee (only when scripted). LoadGetterSetterFunction unfortunately has to guard that the result is a function, because it is possible to create a GetterSetter containing other types. We do enforce that the accessor function must be callable, but that allows bound functions and callable proxies. We could consider adding a shape flag for objects that have a non-JSFunction getter/setter, or alternatively low-bit tagging the setter if the GetterSetter contains a non-JSFunction, to avoid the class guard. Removing the class guard is about a 15-25% improvement on the microbenchmark in bug 1962778. We would get all that improvement with a shape flag, or most of that improvement with a low-bit tag. We could also consider a fuse. Differential Revision: https://phabricator.services.mozilla.com/D251756
1 parent 237d510 commit b1220f6

File tree

11 files changed

+223
-17
lines changed

11 files changed

+223
-17
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
with ({}) {}
2+
3+
function makeObjWithFunctionGetter(n) {
4+
var o = {};
5+
Object.defineProperty(o, "x", {
6+
get() { return n; }
7+
});
8+
9+
return o;
10+
}
11+
12+
function makeObjWithProxyGetter() {
13+
var inner = () => "proxy";
14+
var proxy = new Proxy(inner, {});
15+
16+
var o = {};
17+
Object.defineProperty(o, "x", {
18+
get: proxy
19+
});
20+
return o;
21+
}
22+
23+
function makeObjWithBoundGetter() {
24+
var inner = () => "bound";;
25+
var bound = inner.bind({});
26+
27+
var o = {};
28+
Object.defineProperty(o, "x", {
29+
get: bound
30+
});
31+
return o;
32+
}
33+
34+
function foo(o) { return o.x; }
35+
36+
for (var i = 0; i < 100; i++) {
37+
foo(makeObjWithFunctionGetter(i));
38+
}
39+
40+
assertEq(foo(makeObjWithProxyGetter()), "proxy");
41+
assertEq(foo(makeObjWithBoundGetter()), "bound");

js/src/jit/CacheIR.cpp

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,16 @@ static void EmitMissingPropResult(CacheIRWriter& writer, NativeObject* obj,
937937
writer.loadUndefinedResult();
938938
}
939939

940+
static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder,
941+
ObjOperandId holderId, uint32_t slot) {
942+
if (holder->isFixedSlot(slot)) {
943+
return writer.loadFixedSlot(holderId,
944+
NativeObject::getFixedSlotOffset(slot));
945+
}
946+
size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot);
947+
return writer.loadDynamicSlot(holderId, dynamicSlotIndex);
948+
}
949+
940950
void IRGenerator::emitCallGetterResultNoGuards(NativeGetPropKind kind,
941951
NativeObject* obj,
942952
NativeObject* holder,
@@ -971,6 +981,7 @@ void IRGenerator::emitCallGetterResultNoGuards(NativeGetPropKind kind,
971981
void IRGenerator::emitGuardGetterSetterSlot(NativeObject* holder,
972982
PropertyInfo prop,
973983
ObjOperandId holderId,
984+
AccessorKind kind,
974985
bool holderIsConstant) {
975986
// If the holder is guaranteed to be the same object, and it never had a
976987
// slot holding a GetterSetter mutated or deleted, its Shape will change when
@@ -980,6 +991,25 @@ void IRGenerator::emitGuardGetterSetterSlot(NativeObject* holder,
980991
}
981992

982993
size_t slot = prop.slot();
994+
995+
// For the same reasons as emitCalleeGuard, we guard on the BaseScript
996+
// instead of the GetterSetter if the callee is scripted and this isn't
997+
// the first IC stub.
998+
if (!isFirstStub_) {
999+
bool isGetter = kind == AccessorKind::Getter;
1000+
JSObject* accessor = isGetter ? holder->getGetter(prop)
1001+
: holder->getSetter(prop);
1002+
JSFunction* fun = &accessor->as<JSFunction>();
1003+
if (fun->hasBaseScript()) {
1004+
ValOperandId getterSetterId = EmitLoadSlot(writer, holder, holderId, slot);
1005+
ObjOperandId functionId = writer.loadGetterSetterFunction(getterSetterId,
1006+
isGetter);
1007+
writer.saveScriptedGetterSetterCallee(functionId);
1008+
writer.guardFunctionScript(functionId, fun->baseScript());
1009+
return;
1010+
}
1011+
}
1012+
9831013
Value slotVal = holder->getSlot(slot);
9841014
MOZ_ASSERT(slotVal.isPrivateGCThing());
9851015

@@ -1014,9 +1044,10 @@ void GetPropIRGenerator::emitCallGetterResultGuards(NativeObject* obj,
10141044
TestMatchingHolder(writer, holder, holderId);
10151045

10161046
emitGuardGetterSetterSlot(holder, prop, holderId,
1047+
AccessorKind::Getter,
10171048
/* holderIsConstant = */ true);
10181049
} else {
1019-
emitGuardGetterSetterSlot(holder, prop, objId);
1050+
emitGuardGetterSetterSlot(holder, prop, objId, AccessorKind::Getter);
10201051
}
10211052
} else {
10221053
GetterSetter* gs = holder->getGetterSetter(prop);
@@ -1114,16 +1145,6 @@ void GetPropIRGenerator::emitCallDOMGetterResult(NativeObject* obj,
11141145
emitCallDOMGetterResultNoGuards(holder, prop, objId);
11151146
}
11161147

1117-
static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder,
1118-
ObjOperandId holderId, uint32_t slot) {
1119-
if (holder->isFixedSlot(slot)) {
1120-
return writer.loadFixedSlot(holderId,
1121-
NativeObject::getFixedSlotOffset(slot));
1122-
}
1123-
size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot);
1124-
return writer.loadDynamicSlot(holderId, dynamicSlotIndex);
1125-
}
1126-
11271148
void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
11281149
jsid id) {
11291150
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
@@ -1822,7 +1843,8 @@ AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
18221843
// and not the expando object.
18231844
MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
18241845
kind == NativeGetPropKind::ScriptedGetter);
1825-
emitGuardGetterSetterSlot(nativeExpandoObj, *prop, expandoObjId);
1846+
emitGuardGetterSetterSlot(nativeExpandoObj, *prop, expandoObjId,
1847+
AccessorKind::Getter);
18261848
emitCallGetterResultNoGuards(kind, nativeExpandoObj, nativeExpandoObj,
18271849
*prop, receiverId);
18281850
}
@@ -1972,6 +1994,7 @@ AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
19721994
kind == NativeGetPropKind::ScriptedGetter);
19731995
MOZ_ASSERT(!isSuper());
19741996
emitGuardGetterSetterSlot(holder, *prop, holderId,
1997+
AccessorKind::Getter,
19751998
/* holderIsConstant = */ true);
19761999
emitCallGetterResultNoGuards(kind, nativeProtoObj, holder, *prop,
19772000
receiverId);
@@ -3625,11 +3648,13 @@ AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
36253648
ObjOperandId holderId = writer.loadObject(holder);
36263649
writer.guardShape(holderId, holder->shape());
36273650
emitGuardGetterSetterSlot(holder, *prop, holderId,
3651+
AccessorKind::Getter,
36283652
/* holderIsConstant = */ true);
36293653
} else {
36303654
// Note: pass true for |holderIsConstant| because the holder must be the
36313655
// current global object.
36323656
emitGuardGetterSetterSlot(holder, *prop, globalId,
3657+
AccessorKind::Getter,
36333658
/* holderIsConstant = */ true);
36343659
}
36353660

@@ -4876,9 +4901,10 @@ AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
48764901
TestMatchingHolder(writer, holder, holderId);
48774902

48784903
emitGuardGetterSetterSlot(holder, *prop, holderId,
4904+
AccessorKind::Setter,
48794905
/* holderIsConstant = */ true);
48804906
} else {
4881-
emitGuardGetterSetterSlot(holder, *prop, objId);
4907+
emitGuardGetterSetterSlot(holder, *prop, objId, AccessorKind::Setter);
48824908
}
48834909
} else {
48844910
GetterSetter* gs = holder->getGetterSetter(*prop);
@@ -5340,7 +5366,7 @@ AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
53405366
ObjOperandId holderId = writer.loadObject(holder);
53415367
TestMatchingHolder(writer, holder, holderId);
53425368

5343-
emitGuardGetterSetterSlot(holder, *prop, holderId,
5369+
emitGuardGetterSetterSlot(holder, *prop, holderId, AccessorKind::Setter,
53445370
/* holderIsConstant = */ true);
53455371

53465372
// EmitCallSetterNoGuards expects |obj| to be the object the property is
@@ -5398,7 +5424,8 @@ AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
53985424
obj, objId, expandoVal, nativeExpandoObj);
53995425

54005426
MOZ_ASSERT(holder == nativeExpandoObj);
5401-
emitGuardGetterSetterSlot(nativeExpandoObj, *prop, expandoObjId);
5427+
emitGuardGetterSetterSlot(nativeExpandoObj, *prop, expandoObjId,
5428+
AccessorKind::Setter);
54025429
emitCallSetterNoGuards(nativeExpandoObj, nativeExpandoObj, *prop, objId,
54035430
rhsId);
54045431
trackAttached("SetProp.DOMProxyExpandoSetter");

js/src/jit/CacheIRCompiler.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9312,6 +9312,32 @@ bool CacheIRCompiler::emitMegamorphicStoreSlot(ObjOperandId objId,
93129312
return true;
93139313
}
93149314

9315+
bool CacheIRCompiler::emitLoadGetterSetterFunction(ValOperandId getterSetterId,
9316+
bool isGetter,
9317+
ObjOperandId resultId) {
9318+
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
9319+
9320+
ValueOperand getterSetter = allocator.useValueRegister(masm, getterSetterId);
9321+
Register output = allocator.defineRegister(masm, resultId);
9322+
AutoScratchRegister scratch(allocator, masm);
9323+
9324+
FailurePath* failure;
9325+
if (!addFailurePath(&failure)) {
9326+
return false;
9327+
}
9328+
9329+
masm.unboxNonDouble(getterSetter, output, JSVAL_TYPE_PRIVATE_GCTHING);
9330+
9331+
size_t offset = isGetter ? GetterSetter::offsetOfGetter() : GetterSetter::offsetOfSetter();
9332+
masm.loadPtr(Address(output, offset), output);
9333+
9334+
masm.branchTestPtr(Assembler::Zero, output, output, failure->label());
9335+
masm.branchTestObjIsFunction(Assembler::NotEqual, output, scratch, output,
9336+
failure->label());
9337+
return true;
9338+
}
9339+
9340+
93159341
bool CacheIRCompiler::emitGuardHasGetterSetter(ObjOperandId objId,
93169342
uint32_t idOffset,
93179343
uint32_t getterSetterOffset) {

js/src/jit/CacheIRGenerator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,10 @@ class MOZ_RAII IRGenerator {
107107
void emitOptimisticClassGuard(ObjOperandId objId, JSObject* obj,
108108
GuardClassKind kind);
109109

110+
enum class AccessorKind { Getter, Setter };
110111
void emitGuardGetterSetterSlot(NativeObject* holder, PropertyInfo prop,
111112
ObjOperandId holderId,
113+
AccessorKind kind,
112114
bool holderIsConstant = false);
113115
void emitCallGetterResultNoGuards(NativeGetPropKind kind, NativeObject* obj,
114116
NativeObject* holder, PropertyInfo prop,

js/src/jit/CacheIROps.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,17 @@
616616
boolean: BooleanId
617617
result: NumberId
618618

619+
# Load the getter or setter out of a GetterSetter.
620+
# Fails if it is null.
621+
- name: LoadGetterSetterFunction
622+
shared: true
623+
transpile: true
624+
cost_estimate: 1
625+
args:
626+
getterSetter: ValId
627+
isGetter: BoolImm
628+
result: ObjId
629+
619630
- name: GuardHasGetterSetter
620631
shared: true
621632
transpile: true

js/src/jit/CacheIRWriter.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
102102
// instruction.
103103
TrialInliningState trialInliningState_ = TrialInliningState::Failure;
104104

105+
ObjOperandId savedScriptedGetterSetterCallee_;
106+
105107
// Basic caching to avoid quadatic lookup behaviour in readStubField.
106108
mutable uint32_t lastOffset_;
107109
mutable uint32_t lastIndex_;
@@ -618,8 +620,20 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
618620
callClassHook_(calleeId, argc, flags, argcFixed, target);
619621
}
620622

623+
void saveScriptedGetterSetterCallee(ObjOperandId callee) {
624+
MOZ_ASSERT(!hasSavedScriptedGetterSetterCallee());
625+
savedScriptedGetterSetterCallee_ = callee;
626+
}
627+
621628
private:
629+
bool hasSavedScriptedGetterSetterCallee() {
630+
return savedScriptedGetterSetterCallee_.valid();
631+
}
632+
622633
ObjOperandId getterSetterCalleeOperand(JSFunction* target) {
634+
if (hasSavedScriptedGetterSetterCallee()) {
635+
return savedScriptedGetterSetterCallee_;
636+
}
623637
return loadObject(target);
624638
}
625639
public:
@@ -637,6 +651,7 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
637651
JSFunction* getter, ICScript* icScript,
638652
bool sameRealm) {
639653
MOZ_ASSERT(getter->hasJitEntry());
654+
MOZ_ASSERT(!hasSavedScriptedGetterSetterCallee());
640655
uint32_t nargsAndFlags = getter->flagsAndArgCountRaw();
641656
callInlinedGetterResult_(receiver, callee, icScript, sameRealm,
642657
nargsAndFlags);
@@ -646,6 +661,7 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
646661
void callNativeGetterResult(ValOperandId receiver, JSFunction* getter,
647662
bool sameRealm) {
648663
MOZ_ASSERT(getter->isNativeWithoutJitEntry());
664+
MOZ_ASSERT(!hasSavedScriptedGetterSetterCallee());
649665
uint32_t nargsAndFlags = getter->flagsAndArgCountRaw();
650666
callNativeGetterResult_(receiver, getter, sameRealm, nargsAndFlags);
651667
}
@@ -663,6 +679,7 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
663679
JSFunction* setter, ValOperandId rhs,
664680
ICScript* icScript, bool sameRealm) {
665681
MOZ_ASSERT(setter->hasJitEntry());
682+
MOZ_ASSERT(!hasSavedScriptedGetterSetterCallee());
666683
uint32_t nargsAndFlags = setter->flagsAndArgCountRaw();
667684
callInlinedSetter_(receiver, callee, rhs, icScript, sameRealm,
668685
nargsAndFlags);
@@ -672,6 +689,7 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
672689
void callNativeSetter(ObjOperandId receiver, JSFunction* setter,
673690
ValOperandId rhs, bool sameRealm) {
674691
MOZ_ASSERT(setter->isNativeWithoutJitEntry());
692+
MOZ_ASSERT(!hasSavedScriptedGetterSetterCallee());
675693
uint32_t nargsAndFlags = setter->flagsAndArgCountRaw();
676694
callNativeSetter_(receiver, setter, rhs, sameRealm, nargsAndFlags);
677695
}

js/src/jit/CodeGenerator.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21109,6 +21109,24 @@ void CodeGenerator::visitLoadWrapperTarget(LLoadWrapperTarget* lir) {
2110921109
}
2111021110
}
2111121111

21112+
void CodeGenerator::visitLoadGetterSetterFunction(LLoadGetterSetterFunction* lir) {
21113+
ValueOperand getterSetter = ToValue(lir->getterSetter());
21114+
Register output = ToRegister(lir->output());
21115+
Register temp = ToRegister(lir->temp0());
21116+
21117+
masm.unboxNonDouble(getterSetter, output, JSVAL_TYPE_PRIVATE_GCTHING);
21118+
21119+
size_t offset = lir->mir()->isGetter() ? GetterSetter::offsetOfGetter()
21120+
: GetterSetter::offsetOfSetter();
21121+
masm.loadPtr(Address(output, offset), output);
21122+
21123+
Label bail;
21124+
masm.branchTestPtr(Assembler::Zero, output, output, &bail);
21125+
masm.branchTestObjIsFunction(Assembler::NotEqual, output, temp, output,
21126+
&bail);
21127+
bailoutFrom(&bail, lir->snapshot());
21128+
}
21129+
2111221130
void CodeGenerator::visitGuardHasGetterSetter(LGuardHasGetterSetter* lir) {
2111321131
Register object = ToRegister(lir->object());
2111421132
Register temp0 = ToRegister(lir->temp0());

js/src/jit/Lowering.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7466,6 +7466,15 @@ void LIRGenerator::visitLoadWrapperTarget(MLoadWrapperTarget* ins) {
74667466
define(lir, ins);
74677467
}
74687468

7469+
void LIRGenerator::visitLoadGetterSetterFunction(MLoadGetterSetterFunction* ins) {
7470+
MDefinition* getterSetter = ins->getterSetter();
7471+
7472+
auto* lir = new (alloc())
7473+
LLoadGetterSetterFunction(useBoxAtStart(getterSetter), temp());
7474+
assignSnapshot(lir, ins->bailoutKind());
7475+
define(lir, ins);
7476+
}
7477+
74697478
void LIRGenerator::visitGuardHasGetterSetter(MGuardHasGetterSetter* ins) {
74707479
MDefinition* object = ins->object();
74717480
MOZ_ASSERT(object->type() == MIRType::Object);

js/src/jit/MIROps.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3285,6 +3285,16 @@
32853285
alias_set: custom
32863286
generate_lir: true
32873287

3288+
- name: LoadGetterSetterFunction
3289+
operands:
3290+
getterSetter: Value
3291+
arguments:
3292+
isGetter: bool
3293+
result_type: Object
3294+
generate_lir: true
3295+
lir_temps: 1
3296+
alias_set: none
3297+
32883298
# Guard the accessor shape is present on the object or its prototype chain.
32893299
- name: GuardHasGetterSetter
32903300
operands:

0 commit comments

Comments
 (0)