commit 59d50bd368fb5ba1d562b3c8e7bf50e67be6fe7c
parent 653729f665e59fc8ebee035ecb7c8ec46a298de8
Author: Hannes Verschore <hv1989@gmail.com>
Date: Thu, 18 Dec 2025 16:07:14 +0000
Bug 1999828: Part 5 - Implement LoadFixedSlotFromOffset and LoadDynamicSlotFromOffset. r=iain
Adds the folding and addToFolded code for Load{Fixed|Dynamic}Slot.
This patch contains the cache stub code and ion code.
Differential Revision: https://phabricator.services.mozilla.com/D274206
Diffstat:
10 files changed, 211 insertions(+), 4 deletions(-)
diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp
@@ -2562,6 +2562,18 @@ bool CacheIRCompiler::emitLoadFixedSlot(ValOperandId resultId,
return true;
}
+bool CacheIRCompiler::emitLoadFixedSlotFromOffsetResult(
+ ObjOperandId objId, Int32OperandId offsetId) {
+ JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
+ AutoOutputRegister output(*this);
+ Register obj = allocator.useRegister(masm, objId);
+ Register offset = allocator.useRegister(masm, offsetId);
+
+ // Load the value at the offset reg.
+ masm.loadValue(BaseIndex(obj, offset, TimesOne), output.valueReg());
+ return true;
+}
+
bool CacheIRCompiler::emitLoadDynamicSlot(ValOperandId resultId,
ObjOperandId objId,
uint32_t slotOffset) {
@@ -2580,6 +2592,20 @@ bool CacheIRCompiler::emitLoadDynamicSlot(ValOperandId resultId,
return true;
}
+bool CacheIRCompiler::emitLoadDynamicSlotFromOffsetResult(
+ ObjOperandId objId, Int32OperandId offsetId) {
+ JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
+ AutoOutputRegister output(*this);
+ Register obj = allocator.useRegister(masm, objId);
+ Register offset = allocator.useRegister(masm, offsetId);
+ AutoScratchRegister scratch(allocator, masm);
+
+ // obj->slots
+ masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch);
+ masm.loadValue(BaseIndex(scratch, offset, TimesOne), output.valueReg());
+ return true;
+}
+
bool CacheIRCompiler::emitGuardIsNativeObject(ObjOperandId objId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
diff --git a/js/src/jit/CacheIROps.yaml b/js/src/jit/CacheIROps.yaml
@@ -806,6 +806,14 @@
obj: ObjId
offset: RawInt32Field
+- name: LoadFixedSlotFromOffsetResult
+ shared: true
+ transpile: true
+ cost_estimate: 2
+ args:
+ obj: ObjId
+ offset: Int32Id
+
- name: LoadDynamicSlot
shared: true
transpile: true
@@ -815,6 +823,14 @@
obj: ObjId
slot: RawInt32Field
+- name: LoadDynamicSlotFromOffsetResult
+ shared: true
+ transpile: true
+ cost_estimate: 2
+ args:
+ obj: ObjId
+ offset: Int32Id
+
- name: GuardNoAllocationMetadataBuilder
shared: true
transpile: true
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
@@ -4302,6 +4302,16 @@ void CodeGenerator::visitLoadDynamicSlotV(LLoadDynamicSlotV* lir) {
masm.loadValue(Address(base, offset), dest);
}
+void CodeGenerator::visitLoadDynamicSlotFromOffset(
+ LLoadDynamicSlotFromOffset* lir) {
+ ValueOperand dest = ToOutValue(lir);
+ Register slots = ToRegister(lir->slots());
+ Register offset = ToRegister(lir->offset());
+
+ // slots[offset]
+ masm.loadValue(BaseIndex(slots, offset, TimesOne), dest);
+}
+
static ConstantOrRegister ToConstantOrRegister(const LAllocation* value,
MIRType valueType) {
if (value->isConstant()) {
@@ -18034,6 +18044,16 @@ void CodeGenerator::visitLoadFixedSlotT(LLoadFixedSlotT* ins) {
type, result);
}
+void CodeGenerator::visitLoadFixedSlotFromOffset(
+ LLoadFixedSlotFromOffset* lir) {
+ Register obj = ToRegister(lir->object());
+ Register offset = ToRegister(lir->offset());
+ ValueOperand out = ToOutValue(lir);
+
+ // obj[offset]
+ masm.loadValue(BaseIndex(obj, offset, TimesOne), out);
+}
+
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/GenerateMIRFiles.py b/js/src/jit/GenerateMIRFiles.py
@@ -59,6 +59,8 @@ type_policies = {
"Double": "DoublePolicy",
"String": "StringPolicy",
"Symbol": "SymbolPolicy",
+ "NoTypePolicy": "NoTypePolicy",
+ "Slots": "NoTypePolicy",
}
@@ -66,16 +68,22 @@ def decide_type_policy(types, no_type_policy):
if no_type_policy:
return "public NoTypePolicy::Data"
- if len(types) == 1:
- return f"public {type_policies[types[0]]}<0>::Data"
-
type_num = 0
mixed_type_policies = []
for mir_type in types:
policy = type_policies[mir_type]
+ if policy == "NoTypePolicy":
+ type_num += 1
+ continue
mixed_type_policies.append(f"{policy}<{type_num}>")
type_num += 1
+ if len(mixed_type_policies) == 0:
+ return "public NoTypePolicy::Data"
+
+ if len(mixed_type_policies) == 1:
+ return f"public {mixed_type_policies[0]}::Data"
+
return "public MixPolicy<{}>::Data".format(", ".join(mixed_type_policies))
diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp
@@ -4207,6 +4207,15 @@ void LIRGenerator::visitLoadDynamicSlot(MLoadDynamicSlot* ins) {
}
}
+void LIRGenerator::visitLoadDynamicSlotFromOffset(
+ MLoadDynamicSlotFromOffset* ins) {
+ MOZ_ASSERT(ins->slots()->type() == MIRType::Slots);
+
+ auto* lir = new (alloc()) LLoadDynamicSlotFromOffset(
+ useRegisterAtStart(ins->slots()), useRegisterAtStart(ins->offset()));
+ defineBox(lir, ins);
+}
+
void LIRGenerator::visitFunctionEnvironment(MFunctionEnvironment* ins) {
define(new (alloc())
LFunctionEnvironment(useRegisterAtStart(ins->function())),
@@ -5480,6 +5489,16 @@ void LIRGenerator::visitLoadFixedSlot(MLoadFixedSlot* ins) {
}
}
+void LIRGenerator::visitLoadFixedSlotFromOffset(MLoadFixedSlotFromOffset* ins) {
+ MDefinition* obj = ins->object();
+ MOZ_ASSERT(obj->type() == MIRType::Object);
+ MOZ_ASSERT(ins->type() == MIRType::Value);
+
+ auto* lir = new (alloc()) LLoadFixedSlotFromOffset(
+ useRegisterAtStart(obj), useRegisterAtStart(ins->offset()));
+ defineBox(lir, ins);
+}
+
void LIRGenerator::visitLoadFixedSlotAndUnbox(MLoadFixedSlotAndUnbox* ins) {
MDefinition* obj = ins->object();
MOZ_ASSERT(obj->type() == MIRType::Object);
diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp
@@ -7341,6 +7341,15 @@ AliasSet MGuardMultipleShapesToOffset::getAliasSet() const {
return AliasSet::Load(AliasSet::ObjectFields);
}
+AliasSet MLoadFixedSlotFromOffset::getAliasSet() const {
+ return AliasSet::Load(AliasSet::FixedSlot);
+}
+
+AliasSet MLoadDynamicSlotFromOffset::getAliasSet() const {
+ MOZ_ASSERT(slots()->type() == MIRType::Slots);
+ return AliasSet::Load(AliasSet::DynamicSlot);
+}
+
AliasSet MGuardGlobalGeneration::getAliasSet() const {
return AliasSet::Load(AliasSet::GlobalGenerationCounter);
}
diff --git a/js/src/jit/MIROps.yaml b/js/src/jit/MIROps.yaml
@@ -2185,6 +2185,16 @@
- name: LoadFixedSlot
gen_boilerplate: false
+- name: LoadFixedSlotFromOffset
+ operands:
+ object: Object
+ offset: Int32
+ result_type: Value
+ movable: true
+ congruent_to: if_operands_equal
+ alias_set: custom
+ generate_lir: true
+
- name: LoadFixedSlotAndUnbox
gen_boilerplate: false
@@ -2807,6 +2817,16 @@
- name: LoadDynamicSlot
gen_boilerplate: false
+- name: LoadDynamicSlotFromOffset
+ operands:
+ slots: Slots
+ offset: Int32
+ result_type: Value
+ movable: true
+ congruent_to: if_operands_equal
+ alias_set: custom
+ generate_lir: true
+
# Inline call to access a function's environment (scope chain).
- name: FunctionEnvironment
operands:
diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp
@@ -295,7 +295,9 @@ static bool IsObjectEscaped(MDefinition* ins, MInstruction* newObject,
// 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::LoadDynamicSlot ||
+ def->op() ==
+ MDefinition::Opcode::LoadDynamicSlotFromOffset);
}
#endif
break;
diff --git a/js/src/jit/StubFolding.cpp b/js/src/jit/StubFolding.cpp
@@ -50,6 +50,7 @@ static bool TryFoldingGuardShapes(JSContext* cx, ICFallbackStub* fallback,
// If all of these conditions hold, then we generate a single stub
// that covers all the existing cases by
// 1) replacing GuardShape with GuardMultipleShapes.
+ // 2) replacing Load/Store with equivalent LoadToOffset/StoreToOffset
uint32_t numActive = 0;
mozilla::Maybe<uint32_t> foldableShapeOffset;
@@ -242,6 +243,7 @@ static bool TryFoldingGuardShapes(JSContext* cx, ICFallbackStub* fallback,
// or
// (multiple distinct values in offsetList)
// - specific GuardShape with GuardMultipleShapesToOffset.
+ // - subsequent Load / Store with LoadToOffset / StoreToOffset
CacheIRWriter writer(cx);
CacheIRReader reader(stubInfo);
CacheIRCloner cloner(firstStub);
@@ -313,6 +315,34 @@ static bool TryFoldingGuardShapes(JSContext* cx, ICFallbackStub* fallback,
shapeSuccess = true;
break;
}
+ case CacheOp::LoadFixedSlotResult: {
+ auto [objId, offsetOffset] = reader.argsForLoadFixedSlotResult();
+ if (!hasSlotOffsets || offsetOffset != *foldableOffsetOffset) {
+ // Unrelated LoadFixedSlotResult
+ uint32_t offset = stubInfo->getStubRawWord(firstStub, offsetOffset);
+ writer.loadFixedSlotResult(objId, offset);
+ break;
+ }
+
+ MOZ_ASSERT(offsetId.isSome());
+ writer.loadFixedSlotFromOffsetResult(objId, offsetId.value());
+ offsetSuccess = true;
+ break;
+ }
+ case CacheOp::LoadDynamicSlotResult: {
+ auto [objId, offsetOffset] = reader.argsForLoadDynamicSlotResult();
+ if (!hasSlotOffsets || offsetOffset != *foldableOffsetOffset) {
+ // Unrelated LoadDynamicSlotResult
+ uint32_t offset = stubInfo->getStubRawWord(firstStub, offsetOffset);
+ writer.loadDynamicSlotResult(objId, offset);
+ break;
+ }
+
+ MOZ_ASSERT(offsetId.isSome());
+ writer.loadDynamicSlotFromOffsetResult(objId, offsetId.value());
+ offsetSuccess = true;
+ break;
+ }
default:
cloner.cloneOp(op, reader, writer);
break;
@@ -535,6 +565,36 @@ bool js::jit::AddToFoldedStub(JSContext* cx, const CacheIRWriter& writer,
stubReader.skip();
break;
}
+ case CacheOp::LoadFixedSlotFromOffsetResult:
+ case CacheOp::LoadDynamicSlotFromOffsetResult: {
+ // Check that the new stub has a corresponding
+ // Load{Fixed|Dynamic}SlotResult
+ if (stubOp == CacheOp::LoadFixedSlotFromOffsetResult &&
+ newOp != CacheOp::LoadFixedSlotResult) {
+ return false;
+ }
+ if (stubOp == CacheOp::LoadDynamicSlotFromOffsetResult &&
+ newOp != CacheOp::LoadDynamicSlotResult) {
+ 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();
+ break;
+ }
default: {
// Check that the op is the same.
if (newOp != stubOp) {
diff --git a/js/src/jit/WarpCacheIRTranspiler.cpp b/js/src/jit/WarpCacheIRTranspiler.cpp
@@ -1043,6 +1043,21 @@ bool WarpCacheIRTranspiler::emitLoadDynamicSlot(ValOperandId resultId,
return defineOperand(resultId, load);
}
+bool WarpCacheIRTranspiler::emitLoadDynamicSlotFromOffsetResult(
+ ObjOperandId objId, Int32OperandId offsetId) {
+ MDefinition* obj = getOperand(objId);
+ MDefinition* offset = getOperand(offsetId);
+
+ auto* slots = MSlots::New(alloc(), obj);
+ add(slots);
+
+ auto* load = MLoadDynamicSlotFromOffset::New(alloc(), slots, offset);
+ add(load);
+
+ pushResult(load);
+ return true;
+}
+
bool WarpCacheIRTranspiler::emitGuardDynamicSlotIsNotObject(
ObjOperandId objId, uint32_t slotOffset) {
size_t slotIndex = int32StubField(slotOffset);
@@ -1934,6 +1949,18 @@ bool WarpCacheIRTranspiler::emitLoadFixedSlot(ValOperandId resultId,
return defineOperand(resultId, load);
}
+bool WarpCacheIRTranspiler::emitLoadFixedSlotFromOffsetResult(
+ ObjOperandId objId, Int32OperandId offsetId) {
+ MDefinition* obj = getOperand(objId);
+ MDefinition* offset = getOperand(offsetId);
+
+ auto* ins = MLoadFixedSlotFromOffset::New(alloc(), obj, offset);
+ add(ins);
+
+ pushResult(ins);
+ return true;
+}
+
bool WarpCacheIRTranspiler::emitLoadFixedSlotResult(ObjOperandId objId,
uint32_t offsetOffset) {
int32_t offset = int32StubField(offsetOffset);