Bug 1932305 part 12 - Optimize calls to Map.prototype.set and Set.prototype.add. r=iain

This is just a VM call for now but this could be optimized more later.

These functions return the `Map`/`Set` object so that the calls can be chained, but
we don't have a good way to do this in the CacheIR compiler after the VM call.
This adds separate VM functions for the ICs that return the object.

Differential Revision: https://phabricator.services.mozilla.com/D229608
This commit is contained in:
Jan de Mooij 2024-11-22 13:40:20 +00:00
parent 65be165dc2
commit b1494ee1be
15 changed files with 257 additions and 2 deletions

View File

@ -432,7 +432,7 @@ const JSPropertySpec MapObject::properties[] = {
const JSFunctionSpec MapObject::methods[] = {
JS_INLINABLE_FN("get", get, 1, 0, MapGet),
JS_INLINABLE_FN("has", has, 1, 0, MapHas),
JS_FN("set", set, 2, 0),
JS_INLINABLE_FN("set", set, 2, 0, MapSet),
JS_FN("delete", delete_, 1, 0),
JS_FN("keys", keys, 0, 0),
JS_FN("values", values, 0, 0),
@ -1191,7 +1191,7 @@ const JSPropertySpec SetObject::properties[] = {
const JSFunctionSpec SetObject::methods[] = {
JS_INLINABLE_FN("has", has, 1, 0, SetHas),
JS_FN("add", add, 1, 0),
JS_INLINABLE_FN("add", add, 1, 0, SetAdd),
JS_FN("delete", delete_, 1, 0),
JS_FN("entries", entries, 0, 0),
JS_FN("clear", clear, 0, 0),

View File

@ -0,0 +1,11 @@
function test() {
var m = new Map();
var s = new Set();
for (var i = 0; i < 100; i++) {
m.set("a", 0).set(i, i).set("a", 1);
s.add("a").add(i).add("a");
}
assertEq(m.size, 101);
assertEq(s.size, 101);
}
test();

View File

@ -10242,6 +10242,37 @@ AttachDecision InlinableNativeIRGenerator::tryAttachSetHas() {
return AttachDecision::Attach;
}
AttachDecision InlinableNativeIRGenerator::tryAttachSetAdd() {
// Ensure |this| is a SetObject.
if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
return AttachDecision::NoAction;
}
// Need one argument.
if (argc_ != 1) {
return AttachDecision::NoAction;
}
// Initialize the input operand.
initializeInputOperand();
// Guard callee is the 'add' native function.
emitNativeCalleeGuard();
// Guard |this| is a SetObject.
ValOperandId thisValId =
writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
ObjOperandId objId = writer.guardToObject(thisValId);
emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set);
ValOperandId keyId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
writer.setAddResult(objId, keyId);
writer.returnFromIC();
trackAttached("SetAdd");
return AttachDecision::Attach;
}
AttachDecision InlinableNativeIRGenerator::tryAttachSetSize() {
// Ensure |this| is a SetObject.
if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
@ -10446,6 +10477,44 @@ AttachDecision InlinableNativeIRGenerator::tryAttachMapGet() {
return AttachDecision::Attach;
}
AttachDecision InlinableNativeIRGenerator::tryAttachMapSet() {
#ifdef JS_CODEGEN_X86
// 32-bit x86 does not have enough registers for the AutoCallVM output, the
// MapObject*, and two Values.
return AttachDecision::NoAction;
#endif
// Ensure |this| is a MapObject.
if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
return AttachDecision::NoAction;
}
// Need two arguments.
if (argc_ != 2) {
return AttachDecision::NoAction;
}
// Initialize the input operand.
initializeInputOperand();
// Guard callee is the 'set' native function.
emitNativeCalleeGuard();
// Guard |this| is a MapObject.
ValOperandId thisValId =
writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
ObjOperandId objId = writer.guardToObject(thisValId);
emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map);
ValOperandId keyId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
writer.mapSetResult(objId, keyId, valId);
writer.returnFromIC();
trackAttached("MapSet");
return AttachDecision::Attach;
}
AttachDecision InlinableNativeIRGenerator::tryAttachDateGetTime(
InlinableNative native) {
// Ensure |this| is a DateObject.
@ -12332,6 +12401,8 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() {
return AttachDecision::NoAction; // Not callable.
case InlinableNative::SetHas:
return tryAttachSetHas();
case InlinableNative::SetAdd:
return tryAttachSetAdd();
case InlinableNative::SetSize:
return tryAttachSetSize();
@ -12342,6 +12413,8 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() {
return tryAttachMapHas();
case InlinableNative::MapGet:
return tryAttachMapGet();
case InlinableNative::MapSet:
return tryAttachMapSet();
// Date natives and intrinsics.
case InlinableNative::DateGetTime:

View File

@ -10584,6 +10584,24 @@ bool CacheIRCompiler::emitSetHasObjectResult(ObjOperandId setId,
return true;
}
bool CacheIRCompiler::emitSetAddResult(ObjOperandId setId, ValOperandId keyId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
AutoCallVM callvm(masm, this, allocator);
Register set = allocator.useRegister(masm, setId);
ValueOperand key = allocator.useValueRegister(masm, keyId);
callvm.prepare();
masm.Push(key);
masm.Push(set);
using Fn =
bool (*)(JSContext*, Handle<SetObject*>, HandleValue, MutableHandleValue);
callvm.call<Fn, jit::SetObjectAddFromIC>();
return true;
}
bool CacheIRCompiler::emitSetSizeResult(ObjOperandId setId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
@ -10735,6 +10753,27 @@ bool CacheIRCompiler::emitMapGetResult(ObjOperandId mapId, ValOperandId valId) {
return true;
}
bool CacheIRCompiler::emitMapSetResult(ObjOperandId mapId, ValOperandId keyId,
ValOperandId valId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
AutoCallVM callvm(masm, this, allocator);
Register map = allocator.useRegister(masm, mapId);
ValueOperand key = allocator.useValueRegister(masm, keyId);
ValueOperand val = allocator.useValueRegister(masm, valId);
callvm.prepare();
masm.Push(val);
masm.Push(key);
masm.Push(map);
using Fn = bool (*)(JSContext*, Handle<MapObject*>, HandleValue, HandleValue,
MutableHandleValue);
callvm.call<Fn, jit::MapObjectSetFromIC>();
return true;
}
bool CacheIRCompiler::emitMapGetNonGCThingResult(ObjOperandId mapId,
ValOperandId valId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

View File

@ -771,9 +771,11 @@ class MOZ_RAII InlinableNativeIRGenerator {
AttachDecision tryAttachBigIntAsIntN();
AttachDecision tryAttachBigIntAsUintN();
AttachDecision tryAttachSetHas();
AttachDecision tryAttachSetAdd();
AttachDecision tryAttachSetSize();
AttachDecision tryAttachMapHas();
AttachDecision tryAttachMapGet();
AttachDecision tryAttachMapSet();
AttachDecision tryAttachDateGetTime(InlinableNative native);
AttachDecision tryAttachDateGet(DateComponent component);
#ifdef FUZZING_JS_FUZZILLI

View File

@ -3420,6 +3420,14 @@
set: ObjId
obj: ObjId
- name: SetAddResult
shared: true
transpile: true
cost_estimate: 5
args:
set: ObjId
key: ValId
- name: SetSizeResult
shared: true
transpile: true
@ -3523,6 +3531,15 @@
map: ObjId
obj: ObjId
- name: MapSetResult
shared: true
transpile: true
cost_estimate: 5
args:
map: ObjId
key: ValId
val: ValId
- name: MapSizeResult
shared: true
transpile: true

View File

@ -21667,6 +21667,13 @@ void CodeGenerator::visitSetObjectHasValueVMCall(
callVM<Fn, jit::SetObjectHas>(ins);
}
void CodeGenerator::visitSetObjectAdd(LSetObjectAdd* ins) {
pushArg(ToValue(ins, LSetObjectAdd::KeyIndex));
pushArg(ToRegister(ins->setObject()));
using Fn = bool (*)(JSContext*, Handle<SetObject*>, HandleValue);
callVM<Fn, jit::SetObjectAdd>(ins);
}
void CodeGenerator::visitSetObjectSize(LSetObjectSize* ins) {
Register setObj = ToRegister(ins->setObject());
Register output = ToRegister(ins->output());
@ -21772,6 +21779,14 @@ void CodeGenerator::visitMapObjectGetValueVMCall(
callVM<Fn, jit::MapObjectGet>(ins);
}
void CodeGenerator::visitMapObjectSet(LMapObjectSet* ins) {
pushArg(ToValue(ins, LMapObjectSet::ValueIndex));
pushArg(ToValue(ins, LMapObjectSet::KeyIndex));
pushArg(ToRegister(ins->mapObject()));
using Fn = bool (*)(JSContext*, Handle<MapObject*>, HandleValue, HandleValue);
callVM<Fn, jit::MapObjectSet>(ins);
}
void CodeGenerator::visitMapObjectSize(LMapObjectSize* ins) {
Register mapObj = ToRegister(ins->mapObject());
Register output = ToRegister(ins->output());

View File

@ -313,12 +313,14 @@ bool js::jit::CanInlineNativeCrossRealm(InlinableNative native) {
case InlinableNative::MapConstructor:
case InlinableNative::MapGet:
case InlinableNative::MapHas:
case InlinableNative::MapSet:
case InlinableNative::Number:
case InlinableNative::NumberParseInt:
case InlinableNative::NumberToString:
case InlinableNative::ReflectGetPrototypeOf:
case InlinableNative::SetConstructor:
case InlinableNative::SetHas:
case InlinableNative::SetAdd:
case InlinableNative::SetSize:
case InlinableNative::String:
case InlinableNative::StringToString:

View File

@ -99,6 +99,7 @@
_(MapConstructor) \
_(MapGet) \
_(MapHas) \
_(MapSet) \
\
_(MathAbs) \
_(MathFloor) \
@ -155,6 +156,7 @@
\
_(SetConstructor) \
_(SetHas) \
_(SetAdd) \
_(SetSize) \
\
_(String) \

View File

@ -7666,6 +7666,13 @@ void LIRGenerator::visitSetObjectHasValueVMCall(MSetObjectHasValueVMCall* ins) {
assignSafepoint(lir, ins);
}
void LIRGenerator::visitSetObjectAdd(MSetObjectAdd* ins) {
auto* lir = new (alloc()) LSetObjectAdd(useRegisterAtStart(ins->setObject()),
useBoxAtStart(ins->key()));
add(lir, ins);
assignSafepoint(lir, ins);
}
void LIRGenerator::visitSetObjectSize(MSetObjectSize* ins) {
auto* lir = new (alloc()) LSetObjectSize(useRegisterAtStart(ins->set()));
define(lir, ins);
@ -7727,6 +7734,14 @@ void LIRGenerator::visitMapObjectGetValueVMCall(MMapObjectGetValueVMCall* ins) {
assignSafepoint(lir, ins);
}
void LIRGenerator::visitMapObjectSet(MMapObjectSet* ins) {
auto* lir = new (alloc())
LMapObjectSet(useRegisterAtStart(ins->mapObject()),
useBoxAtStart(ins->key()), useBoxAtStart(ins->value()));
add(lir, ins);
assignSafepoint(lir, ins);
}
void LIRGenerator::visitMapObjectSize(MMapObjectSize* ins) {
auto* lir = new (alloc()) LMapObjectSize(useRegisterAtStart(ins->map()));
define(lir, ins);

View File

@ -3252,6 +3252,13 @@
alias_set: custom
possibly_calls: true
- name: SetObjectAdd
operands:
setObject: Object
key: Value
possibly_calls: true
generate_lir: true
- name: SetObjectSize
operands:
set: Object
@ -3342,6 +3349,14 @@
alias_set: custom
possibly_calls: true
- name: MapObjectSet
operands:
mapObject: Object
key: Value
value: Value
possibly_calls: true
generate_lir: true
- name: MapObjectSize
operands:
map: Object

View File

@ -250,6 +250,8 @@ namespace jit {
_(MapObjectCreate, js::MapObject::create) \
_(MapObjectGet, js::jit::MapObjectGet) \
_(MapObjectHas, js::jit::MapObjectHas) \
_(MapObjectSet, js::jit::MapObjectSet) \
_(MapObjectSetFromIC, js::jit::MapObjectSetFromIC) \
_(MutatePrototype, js::jit::MutatePrototype) \
_(NamedLambdaObjectCreateWithoutEnclosing, \
js::NamedLambdaObject::createWithoutEnclosing) \
@ -304,6 +306,8 @@ namespace jit {
_(SetElementSuper, js::SetElementSuper) \
_(SetFunctionName, js::SetFunctionName) \
_(SetIntrinsicOperation, js::SetIntrinsicOperation) \
_(SetObjectAdd, js::jit::SetObjectAdd) \
_(SetObjectAddFromIC, js::jit::SetObjectAddFromIC) \
_(SetObjectCreate, js::SetObject::create) \
_(SetObjectHas, js::jit::SetObjectHas) \
_(SetPropertyMegamorphicNoCache, js::jit::SetPropertyMegamorphic<false>) \

View File

@ -3115,6 +3115,19 @@ bool SetObjectHas(JSContext* cx, Handle<SetObject*> obj, HandleValue key,
return obj->has(cx, key, rval);
}
bool SetObjectAdd(JSContext* cx, Handle<SetObject*> obj, HandleValue key) {
return obj->add(cx, key);
}
bool SetObjectAddFromIC(JSContext* cx, Handle<SetObject*> obj, HandleValue key,
MutableHandleValue rval) {
if (!SetObjectAdd(cx, obj, key)) {
return false;
}
rval.setObject(*obj);
return true;
}
bool MapObjectHas(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
bool* rval) {
return obj->has(cx, key, rval);
@ -3125,6 +3138,20 @@ bool MapObjectGet(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
return obj->get(cx, key, rval);
}
bool MapObjectSet(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
HandleValue val) {
return obj->set(cx, key, val);
}
bool MapObjectSetFromIC(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
HandleValue val, MutableHandleValue rval) {
if (!MapObjectSet(cx, obj, key, val)) {
return false;
}
rval.setObject(*obj);
return true;
}
#ifdef DEBUG
template <class T>
static mozilla::HashNumber HashValue(JSContext* cx, T* obj,

View File

@ -705,10 +705,17 @@ JSAtom* AtomizeStringNoGC(JSContext* cx, JSString* str);
bool SetObjectHas(JSContext* cx, Handle<SetObject*> obj, HandleValue key,
bool* rval);
bool SetObjectAdd(JSContext* cx, Handle<SetObject*> obj, HandleValue key);
bool SetObjectAddFromIC(JSContext* cx, Handle<SetObject*> obj, HandleValue key,
MutableHandleValue rval);
bool MapObjectHas(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
bool* rval);
bool MapObjectGet(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
MutableHandleValue rval);
bool MapObjectSet(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
HandleValue val);
bool MapObjectSetFromIC(JSContext* cx, Handle<MapObject*> obj, HandleValue key,
HandleValue val, MutableHandleValue rval);
void AssertSetObjectHash(JSContext* cx, SetObject* obj, const Value* value,
mozilla::HashNumber actualHash);

View File

@ -5282,6 +5282,18 @@ bool WarpCacheIRTranspiler::emitSetHasResult(ObjOperandId setId,
return true;
}
bool WarpCacheIRTranspiler::emitSetAddResult(ObjOperandId setId,
ValOperandId keyId) {
MDefinition* set = getOperand(setId);
MDefinition* key = getOperand(keyId);
auto* ins = MSetObjectAdd::New(alloc(), set, key);
addEffectful(ins);
pushResult(set);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitSetSizeResult(ObjOperandId setId) {
MDefinition* set = getOperand(setId);
@ -5500,6 +5512,20 @@ bool WarpCacheIRTranspiler::emitMapGetResult(ObjOperandId mapId,
return true;
}
bool WarpCacheIRTranspiler::emitMapSetResult(ObjOperandId mapId,
ValOperandId keyId,
ValOperandId valId) {
MDefinition* map = getOperand(mapId);
MDefinition* key = getOperand(keyId);
MDefinition* val = getOperand(valId);
auto* ins = MMapObjectSet::New(alloc(), map, key, val);
addEffectful(ins);
pushResult(map);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitMapSizeResult(ObjOperandId mapId) {
MDefinition* map = getOperand(mapId);