commit 7076134fd1bf9a8603ceafbdca199d2a5df52a15
parent 59d50bd368fb5ba1d562b3c8e7bf50e67be6fe7c
Author: Hannes Verschore <hv1989@gmail.com>
Date: Thu, 18 Dec 2025 16:07:14 +0000
Bug 1999828: Part 6 - Implement StoreFixedSlotFromOffset and StoreDynamicSlotFromOffset. r=iain
Adds the folding and addToFolded code for Store{Fixed|Dynamic}Slot.
This patch contains the cache stub code and ion code.
Differential Revision: https://phabricator.services.mozilla.com/D274207
Diffstat:
11 files changed, 382 insertions(+), 4 deletions(-)
diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp
@@ -2574,6 +2574,46 @@ bool CacheIRCompiler::emitLoadFixedSlotFromOffsetResult(
return true;
}
+bool CacheIRCompiler::emitStoreFixedSlotFromOffset(ObjOperandId objId,
+ Int32OperandId offsetId,
+ ValOperandId rhsId) {
+ JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
+
+ AutoScratchRegister scratch(allocator, masm);
+ Register obj = allocator.useRegister(masm, objId);
+ Register offset = allocator.useRegister(masm, offsetId);
+ ValueOperand val = allocator.useValueRegister(masm, rhsId);
+
+ BaseIndex slot(obj, offset, TimesOne);
+ EmitPreBarrier(masm, slot, MIRType::Value);
+
+ masm.storeValue(val, slot);
+
+ emitPostBarrierSlot(obj, val, scratch);
+
+ return true;
+}
+
+bool CacheIRCompiler::emitStoreDynamicSlotFromOffset(ObjOperandId objId,
+ Int32OperandId offsetId,
+ ValOperandId rhsId) {
+ JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
+
+ Register obj = allocator.useRegister(masm, objId);
+ Register offset = allocator.useRegister(masm, offsetId);
+ ValueOperand val = allocator.useValueRegister(masm, rhsId);
+
+ AutoScratchRegister slots(allocator, masm);
+
+ masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), slots);
+ BaseIndex slot(slots, offset, TimesOne);
+ EmitPreBarrier(masm, slot, MIRType::Value);
+ masm.storeValue(val, slot);
+
+ emitPostBarrierSlot(obj, val, /*scratch=*/slots);
+ return true;
+}
+
bool CacheIRCompiler::emitLoadDynamicSlot(ValOperandId resultId,
ObjOperandId objId,
uint32_t slotOffset) {
diff --git a/js/src/jit/CacheIROps.yaml b/js/src/jit/CacheIROps.yaml
@@ -814,6 +814,24 @@
obj: ObjId
offset: Int32Id
+- name: StoreFixedSlotFromOffset
+ shared: true
+ transpile: true
+ cost_estimate: 6
+ args:
+ obj: ObjId
+ offset: Int32Id
+ rhs: ValId
+
+- name: StoreDynamicSlotFromOffset
+ shared: true
+ transpile: true
+ cost_estimate: 6
+ args:
+ obj: ObjId
+ offset: Int32Id
+ rhs: ValId
+
- name: LoadDynamicSlot
shared: true
transpile: true
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
@@ -4347,6 +4347,47 @@ void CodeGenerator::visitStoreDynamicSlotV(LStoreDynamicSlotV* lir) {
masm.storeValue(value, Address(base, offset));
}
+void CodeGenerator::visitStoreDynamicSlotFromOffsetV(
+ LStoreDynamicSlotFromOffsetV* lir) {
+ Register slots = ToRegister(lir->slots());
+ Register offset = ToRegister(lir->offset());
+ ValueOperand value = ToValue(lir->value());
+ Register temp = ToRegister(lir->temp0());
+
+ BaseIndex baseIndex(slots, offset, TimesOne);
+ masm.computeEffectiveAddress(baseIndex, temp);
+
+ Address address(temp, 0);
+
+ emitPreBarrier(address);
+
+ // obj->slots[offset]
+ masm.storeValue(value, address);
+}
+
+void CodeGenerator::visitStoreDynamicSlotFromOffsetT(
+ LStoreDynamicSlotFromOffsetT* lir) {
+ Register slots = ToRegister(lir->slots());
+ Register offset = ToRegister(lir->offset());
+ const LAllocation* value = lir->value();
+ MIRType valueType = lir->mir()->value()->type();
+ Register temp = ToRegister(lir->temp0());
+
+ BaseIndex baseIndex(slots, offset, TimesOne);
+ masm.computeEffectiveAddress(baseIndex, temp);
+
+ Address address(temp, 0);
+
+ emitPreBarrier(address);
+
+ // obj->slots[offset]
+ ConstantOrRegister nvalue =
+ value->isConstant()
+ ? ConstantOrRegister(value->toConstant()->toJSValue())
+ : TypedOrValueRegister(valueType, ToAnyRegister(value));
+ masm.storeConstantOrRegister(nvalue, address);
+}
+
void CodeGenerator::visitElements(LElements* lir) {
Address elements(ToRegister(lir->object()), NativeObject::offsetOfElements());
masm.loadPtr(elements, ToRegister(lir->output()));
@@ -18054,6 +18095,49 @@ void CodeGenerator::visitLoadFixedSlotFromOffset(
masm.loadValue(BaseIndex(obj, offset, TimesOne), out);
}
+void CodeGenerator::visitStoreFixedSlotFromOffsetV(
+ LStoreFixedSlotFromOffsetV* lir) {
+ Register obj = ToRegister(lir->object());
+ Register offset = ToRegister(lir->offset());
+ ValueOperand value = ToValue(lir->value());
+ Register temp = ToRegister(lir->temp0());
+
+ BaseIndex baseIndex(obj, offset, TimesOne);
+ masm.computeEffectiveAddress(baseIndex, temp);
+
+ Address slot(temp, 0);
+ if (lir->mir()->needsBarrier()) {
+ emitPreBarrier(slot);
+ }
+
+ // obj[offset]
+ masm.storeValue(value, slot);
+}
+
+void CodeGenerator::visitStoreFixedSlotFromOffsetT(
+ LStoreFixedSlotFromOffsetT* lir) {
+ Register obj = ToRegister(lir->object());
+ Register offset = ToRegister(lir->offset());
+ const LAllocation* value = lir->value();
+ MIRType valueType = lir->mir()->value()->type();
+ Register temp = ToRegister(lir->temp0());
+
+ BaseIndex baseIndex(obj, offset, TimesOne);
+ masm.computeEffectiveAddress(baseIndex, temp);
+
+ Address slot(temp, 0);
+ if (lir->mir()->needsBarrier()) {
+ emitPreBarrier(slot);
+ }
+
+ // obj[offset]
+ ConstantOrRegister nvalue =
+ value->isConstant()
+ ? ConstantOrRegister(value->toConstant()->toJSValue())
+ : TypedOrValueRegister(valueType, ToAnyRegister(value));
+ masm.storeConstantOrRegister(nvalue, slot);
+}
+
template <typename T>
static void EmitLoadAndUnbox(MacroAssembler& masm, const T& src, MIRType type,
bool fallible, AnyRegister dest, Register64 temp,
diff --git a/js/src/jit/LIROps.yaml b/js/src/jit/LIROps.yaml
@@ -2453,6 +2453,22 @@
value: WordSized
mir_op: StoreFixedSlot
+- name: StoreFixedSlotFromOffsetV
+ operands:
+ object: WordSized
+ offset: WordSized
+ value: BoxedValue
+ num_temps: 1
+ mir_op: StoreFixedSlotFromOffset
+
+- name: StoreFixedSlotFromOffsetT
+ operands:
+ object: WordSized
+ offset: WordSized
+ value: WordSized
+ num_temps: 1
+ mir_op: StoreFixedSlotFromOffset
+
- name: GetPropSuperCache
result_type: BoxedValue
operands:
@@ -2505,6 +2521,22 @@
value: WordSized
mir_op: StoreDynamicSlot
+- name: StoreDynamicSlotFromOffsetV
+ operands:
+ slots: WordSized
+ offset: WordSized
+ value: BoxedValue
+ num_temps: 1
+ mir_op: StoreDynamicSlotFromOffset
+
+- name: StoreDynamicSlotFromOffsetT
+ operands:
+ slots: WordSized
+ offset: WordSized
+ value: WordSized
+ num_temps: 1
+ mir_op: StoreDynamicSlotFromOffset
+
# Take the floor of a double precision number and converts it to an int32.
# Implements Math.floor().
- name: Floor
diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp
@@ -5623,6 +5623,40 @@ void LIRGenerator::visitStoreFixedSlot(MStoreFixedSlot* ins) {
}
}
+void LIRGenerator::visitStoreFixedSlotFromOffset(
+ MStoreFixedSlotFromOffset* ins) {
+ MOZ_ASSERT(ins->object()->type() == MIRType::Object);
+
+ if (ins->value()->type() == MIRType::Value) {
+ LStoreFixedSlotFromOffsetV* lir = new (alloc()) LStoreFixedSlotFromOffsetV(
+ useRegister(ins->object()), useRegister(ins->offset()),
+ useBox(ins->value()), temp());
+ add(lir, ins);
+ } else {
+ LStoreFixedSlotFromOffsetT* lir = new (alloc()) LStoreFixedSlotFromOffsetT(
+ useRegister(ins->object()), useRegister(ins->offset()),
+ useRegisterOrConstant(ins->value()), temp());
+ add(lir, ins);
+ }
+}
+
+void LIRGenerator::visitStoreDynamicSlotFromOffset(
+ MStoreDynamicSlotFromOffset* ins) {
+ MOZ_ASSERT(ins->slots()->type() == MIRType::Slots);
+
+ if (ins->value()->type() == MIRType::Value) {
+ auto* lir = new (alloc()) LStoreDynamicSlotFromOffsetV(
+ useRegister(ins->slots()), useRegister(ins->offset()),
+ useBox(ins->value()), temp());
+ add(lir, ins);
+ } else {
+ auto* lir = new (alloc()) LStoreDynamicSlotFromOffsetT(
+ useRegister(ins->slots()), useRegister(ins->offset()),
+ useRegisterOrConstant(ins->value()), temp());
+ add(lir, ins);
+ }
+}
+
void LIRGenerator::visitGetNameCache(MGetNameCache* ins) {
MOZ_ASSERT(ins->envObj()->type() == MIRType::Object);
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
@@ -7978,6 +7978,41 @@ class MStoreFixedSlot
ALLOW_CLONE(MStoreFixedSlot)
};
+class MStoreFixedSlotFromOffset
+ : public MTernaryInstruction,
+ public MixPolicy<ObjectPolicy<0>, UnboxedInt32Policy<1>,
+ NoFloatPolicy<2>>::Data {
+ bool needsBarrier_;
+
+ MStoreFixedSlotFromOffset(MDefinition* obj, MDefinition* offset,
+ MDefinition* rval, bool barrier)
+ : MTernaryInstruction(classOpcode, obj, offset, rval),
+ needsBarrier_(barrier) {
+ MOZ_ASSERT(obj->type() == MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreFixedSlotFromOffset)
+ NAMED_OPERANDS((0, object), (1, offset), (2, value))
+
+ static MStoreFixedSlotFromOffset* NewBarriered(TempAllocator& alloc,
+ MDefinition* obj,
+ MDefinition* offset,
+ MDefinition* rval) {
+ return new (alloc) MStoreFixedSlotFromOffset(obj, offset, rval, true);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::FixedSlot);
+ }
+ bool needsBarrier() const { return needsBarrier_; }
+ void setNeedsBarrier(bool needsBarrier = true) {
+ needsBarrier_ = needsBarrier;
+ }
+
+ ALLOW_CLONE(MStoreFixedSlotFromOffset)
+};
+
class MGetPropertyCache : public MBinaryInstruction,
public MixPolicy<BoxExceptPolicy<0, MIRType::Object>,
CacheIdPolicy<1>>::Data {
@@ -8410,6 +8445,33 @@ class MStoreDynamicSlot : public MBinaryInstruction,
ALLOW_CLONE(MStoreDynamicSlot)
};
+class MStoreDynamicSlotFromOffset
+ : public MTernaryInstruction,
+ public MixPolicy<UnboxedInt32Policy<1>, NoFloatPolicy<2>>::Data {
+ MStoreDynamicSlotFromOffset(MDefinition* slots, MDefinition* offset,
+ MDefinition* rval, bool barrier)
+ : MTernaryInstruction(classOpcode, slots, offset, rval) {
+ MOZ_ASSERT(slots->type() == MIRType::Slots);
+ }
+
+ public:
+ INSTRUCTION_HEADER(StoreDynamicSlotFromOffset)
+ NAMED_OPERANDS((0, slots), (1, offset), (2, value))
+
+ static MStoreDynamicSlotFromOffset* New(TempAllocator& alloc,
+ MDefinition* slots,
+ MDefinition* offset,
+ MDefinition* rval) {
+ return new (alloc) MStoreDynamicSlotFromOffset(slots, offset, rval, true);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::DynamicSlot);
+ }
+
+ ALLOW_CLONE(MStoreDynamicSlotFromOffset)
+};
+
class MSetPropertyCache : public MTernaryInstruction,
public MixPolicy<SingleObjectPolicy, CacheIdPolicy<1>,
NoFloatPolicy<2>>::Data {
diff --git a/js/src/jit/MIROps.yaml b/js/src/jit/MIROps.yaml
@@ -2204,6 +2204,12 @@
- name: StoreFixedSlot
gen_boilerplate: false
+- name: StoreFixedSlotFromOffset
+ gen_boilerplate: false
+
+- name: StoreDynamicSlotFromOffset
+ gen_boilerplate: false
+
- name: GetPropertyCache
gen_boilerplate: false
diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp
@@ -294,10 +294,11 @@ static bool IsObjectEscaped(MDefinition* ins, MInstruction* newObject,
// toDefinition should normally never fail, since they don't get
// captured by resume points.
MDefinition* def = (*i)->consumer()->toDefinition();
- MOZ_ASSERT(def->op() == MDefinition::Opcode::StoreDynamicSlot ||
- def->op() == MDefinition::Opcode::LoadDynamicSlot ||
- def->op() ==
- MDefinition::Opcode::LoadDynamicSlotFromOffset);
+ MOZ_ASSERT(
+ def->op() == MDefinition::Opcode::StoreDynamicSlot ||
+ def->op() == MDefinition::Opcode::LoadDynamicSlot ||
+ def->op() == MDefinition::Opcode::StoreDynamicSlotFromOffset ||
+ def->op() == MDefinition::Opcode::LoadDynamicSlotFromOffset);
}
#endif
break;
diff --git a/js/src/jit/StubFolding.cpp b/js/src/jit/StubFolding.cpp
@@ -329,6 +329,34 @@ static bool TryFoldingGuardShapes(JSContext* cx, ICFallbackStub* fallback,
offsetSuccess = true;
break;
}
+ case CacheOp::StoreFixedSlot: {
+ auto [objId, offsetOffset, rhsId] = reader.argsForStoreFixedSlot();
+ if (!hasSlotOffsets || offsetOffset != *foldableOffsetOffset) {
+ // Unrelated StoreFixedSlot
+ uint32_t offset = stubInfo->getStubRawWord(firstStub, offsetOffset);
+ writer.storeFixedSlot(objId, offset, rhsId);
+ break;
+ }
+
+ MOZ_ASSERT(offsetId.isSome());
+ writer.storeFixedSlotFromOffset(objId, offsetId.value(), rhsId);
+ offsetSuccess = true;
+ break;
+ }
+ case CacheOp::StoreDynamicSlot: {
+ auto [objId, offsetOffset, rhsId] = reader.argsForStoreDynamicSlot();
+ if (!hasSlotOffsets || offsetOffset != *foldableOffsetOffset) {
+ // Unrelated StoreDynamicSlot
+ uint32_t offset = stubInfo->getStubRawWord(firstStub, offsetOffset);
+ writer.storeDynamicSlot(objId, offset, rhsId);
+ break;
+ }
+
+ MOZ_ASSERT(offsetId.isSome());
+ writer.storeDynamicSlotFromOffset(objId, offsetId.value(), rhsId);
+ offsetSuccess = true;
+ break;
+ }
case CacheOp::LoadDynamicSlotResult: {
auto [objId, offsetOffset] = reader.argsForLoadDynamicSlotResult();
if (!hasSlotOffsets || offsetOffset != *foldableOffsetOffset) {
@@ -595,6 +623,43 @@ bool js::jit::AddToFoldedStub(JSContext* cx, const CacheIRWriter& writer,
stubReader.skip();
break;
}
+ case CacheOp::StoreFixedSlotFromOffset:
+ case CacheOp::StoreDynamicSlotFromOffset: {
+ // Check that the new stub has a corresponding Store{Fixed|Dynamic}Slot
+ if (stubOp == CacheOp::StoreFixedSlotFromOffset &&
+ newOp != CacheOp::StoreFixedSlot) {
+ return false;
+ }
+ if (stubOp == CacheOp::StoreDynamicSlotFromOffset &&
+ newOp != CacheOp::StoreDynamicSlot) {
+ return false;
+ }
+
+ // Verify operand ID.
+ if (newReader.objOperandId() != stubReader.objOperandId()) {
+ return false;
+ }
+
+ MOZ_ASSERT(offsetFieldOffset.isNothing());
+ offsetFieldOffset.emplace(newReader.stubOffset());
+
+ // Get the offset from the new stub
+ StubField offsetField =
+ writer.readStubField(*offsetFieldOffset, StubField::Type::RawInt32);
+ newOffset = PrivateUint32Value(offsetField.asWord());
+
+ // Consume the offsetId argument.
+ stubReader.skip();
+
+ // Verify rhs ID.
+ if (newReader.valOperandId() != stubReader.valOperandId()) {
+ return false;
+ }
+
+ MOZ_ASSERT(stubReader.peekOp() == CacheOp::ReturnFromIC);
+ MOZ_ASSERT(newReader.peekOp() == CacheOp::ReturnFromIC);
+ break;
+ }
default: {
// Check that the op is the same.
if (newOp != stubOp) {
diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp
@@ -1121,6 +1121,8 @@ bool ClampPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const {
_(MixPolicy<UnboxedInt32Policy<0>, UnboxedInt32Policy<1>, \
NoFloatPolicyAfter<2>>) \
_(MixPolicy<IntPtrPolicy<0>, IntPtrPolicy<1>, IntPtrPolicy<2>>) \
+ _(MixPolicy<ObjectPolicy<0>, UnboxedInt32Policy<1>, NoFloatPolicy<2>>) \
+ _(MixPolicy<UnboxedInt32Policy<1>, NoFloatPolicy<2>>) \
_(NoFloatPolicy<0>) \
_(NoFloatPolicy<1>) \
_(NoFloatPolicy<2>) \
diff --git a/js/src/jit/WarpCacheIRTranspiler.cpp b/js/src/jit/WarpCacheIRTranspiler.cpp
@@ -1961,6 +1961,40 @@ bool WarpCacheIRTranspiler::emitLoadFixedSlotFromOffsetResult(
return true;
}
+bool WarpCacheIRTranspiler::emitStoreFixedSlotFromOffset(
+ ObjOperandId objId, Int32OperandId offsetId, ValOperandId rhsId) {
+ MDefinition* obj = getOperand(objId);
+ MDefinition* offset = getOperand(offsetId);
+ MDefinition* rhs = getOperand(rhsId);
+
+ auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs);
+ add(barrier);
+
+ auto* store =
+ MStoreFixedSlotFromOffset::NewBarriered(alloc(), obj, offset, rhs);
+ addEffectful(store);
+
+ return resumeAfter(store);
+}
+
+bool WarpCacheIRTranspiler::emitStoreDynamicSlotFromOffset(
+ ObjOperandId objId, Int32OperandId offsetId, ValOperandId rhsId) {
+ MDefinition* obj = getOperand(objId);
+ MDefinition* offset = getOperand(offsetId);
+ MDefinition* rhs = getOperand(rhsId);
+
+ auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs);
+ add(barrier);
+
+ auto* slots = MSlots::New(alloc(), obj);
+ add(slots);
+
+ auto* store = MStoreDynamicSlotFromOffset::New(alloc(), slots, offset, rhs);
+ addEffectful(store);
+
+ return resumeAfter(store);
+}
+
bool WarpCacheIRTranspiler::emitLoadFixedSlotResult(ObjOperandId objId,
uint32_t offsetOffset) {
int32_t offset = int32StubField(offsetOffset);