зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1701787: Add NewPlainObjectResult r=jandem
When allocating a plain object with no reserved slots, the information that we need from the template object is minimal: the shape, the AllocKind, and the number of fixed/dynamic slots. To make it possible to share code between NewObject ICs, this patch reads the relevant information out of the template object in NewObjectIRGenerator and encodes it into the CacheIR. We generate code using a new masm function that effectively inlines `initGCThing` and `initGCSlots` into `createGCObject`. Conveniently, every `JSOp::NewObject` is a plain object with no reserved slots, so this patch reduces the total number of baseline ICs compiled by 2/3. In a future patch we can get rid of NewObjectIRGenerator::tryAttachTemplateObject completely. The old code didn't support dynamic slots. It's simple enough that I just went ahead and implemented it. In a quick browser experiment, ~8-10% of NewObject ICs had dynamic slots. To limit the length of the unrolled loop, I capped support at 16, which covers ~2/3 of cases with dynamic slots. (Because we always initialize the entire capacity, which is always a power of 2, this just means we support 16 fixed + 7 dynamic, and 16 fixed + 15 dynamic.) We could also consider a follow-up patch that generates an actual loop for dynamic slots and removes the cap. Differential Revision: https://phabricator.services.mozilla.com/D110341
This commit is contained in:
Родитель
305b07e914
Коммит
6a77919060
|
@ -11003,16 +11003,46 @@ void NewObjectIRGenerator::trackAttached(const char* name) {
|
|||
#endif
|
||||
}
|
||||
|
||||
AttachDecision NewObjectIRGenerator::tryAttachStub() {
|
||||
AutoAssertNoPendingException aanpe(cx_);
|
||||
if (templateObject_->as<NativeObject>().hasDynamicSlots()) {
|
||||
trackAttached(IRGenerator::NotAttached);
|
||||
AttachDecision NewObjectIRGenerator::tryAttachPlainObject() {
|
||||
NativeObject* nativeObj = &templateObject_->as<NativeObject>();
|
||||
MOZ_ASSERT(nativeObj->is<PlainObject>());
|
||||
|
||||
// We use an unrolled loop when initializing slots. To avoid generating
|
||||
// too much code, put a limit on the number of dynamic slots.
|
||||
if (nativeObj->numDynamicSlots() > NativeObject::MAX_FIXED_SLOTS) {
|
||||
return AttachDecision::NoAction;
|
||||
}
|
||||
|
||||
// Stub doesn't support metadata builder
|
||||
if (cx_->realm()->hasAllocationMetadataBuilder()) {
|
||||
return AttachDecision::NoAction;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!nativeObj->hasPrivate());
|
||||
MOZ_ASSERT(!nativeObj->hasDynamicElements());
|
||||
MOZ_ASSERT(!nativeObj->isSharedMemory());
|
||||
|
||||
uint32_t numFixedSlots = nativeObj->numUsedFixedSlots();
|
||||
uint32_t numDynamicSlots = nativeObj->numDynamicSlots();
|
||||
gc::AllocKind allocKind = nativeObj->asTenured().getAllocKind();
|
||||
Shape* shape = nativeObj->lastProperty();
|
||||
|
||||
writer.guardNoAllocationMetadataBuilder();
|
||||
writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, shape);
|
||||
|
||||
writer.returnFromIC();
|
||||
|
||||
trackAttached("NewPlainObject");
|
||||
return AttachDecision::Attach;
|
||||
}
|
||||
|
||||
AttachDecision NewObjectIRGenerator::tryAttachTemplateObject() {
|
||||
if (templateObject_->as<NativeObject>().hasDynamicSlots()) {
|
||||
return AttachDecision::NoAction;
|
||||
}
|
||||
|
||||
// Stub doesn't support metadata builder
|
||||
if (cx_->realm()->hasAllocationMetadataBuilder()) {
|
||||
trackAttached(IRGenerator::NotAttached);
|
||||
return AttachDecision::NoAction;
|
||||
}
|
||||
|
||||
|
@ -11031,6 +11061,16 @@ AttachDecision NewObjectIRGenerator::tryAttachStub() {
|
|||
return AttachDecision::Attach;
|
||||
}
|
||||
|
||||
AttachDecision NewObjectIRGenerator::tryAttachStub() {
|
||||
AutoAssertNoPendingException aanpe(cx_);
|
||||
|
||||
TRY_ATTACH(tryAttachPlainObject());
|
||||
TRY_ATTACH(tryAttachTemplateObject());
|
||||
|
||||
trackAttached(IRGenerator::NotAttached);
|
||||
return AttachDecision::NoAction;
|
||||
}
|
||||
|
||||
#ifdef JS_SIMULATOR
|
||||
bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
|
|
@ -742,6 +742,11 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter {
|
|||
buffer_.writeByte(uint8_t(kind));
|
||||
}
|
||||
|
||||
void writeAllocKindImm(gc::AllocKind kind) {
|
||||
static_assert(unsigned(gc::AllocKind::LIMIT) <= UINT8_MAX);
|
||||
buffer_.writeByte(uint8_t(kind));
|
||||
}
|
||||
|
||||
uint32_t newOperandId() { return nextOperandId_++; }
|
||||
|
||||
CacheIRWriter(const CacheIRWriter&) = delete;
|
||||
|
@ -1142,6 +1147,7 @@ class MOZ_RAII CacheIRReader {
|
|||
wasm::ValType::Kind wasmValType() {
|
||||
return wasm::ValType::Kind(buffer_.readByte());
|
||||
}
|
||||
gc::AllocKind allocKind() { return gc::AllocKind(buffer_.readByte()); }
|
||||
|
||||
Scalar::Type scalarType() { return Scalar::Type(buffer_.readByte()); }
|
||||
uint32_t rttValueKey() { return buffer_.readByte(); }
|
||||
|
@ -1920,6 +1926,8 @@ class MOZ_RAII NewObjectIRGenerator : public IRGenerator {
|
|||
ICState::Mode, JSOp op, HandleObject templateObj);
|
||||
|
||||
AttachDecision tryAttachStub();
|
||||
AttachDecision tryAttachPlainObject();
|
||||
AttachDecision tryAttachTemplateObject();
|
||||
};
|
||||
|
||||
// Retrieve Xray JIT info set by the embedder.
|
||||
|
|
|
@ -6101,6 +6101,32 @@ bool CacheIRCompiler::emitLoadNewObjectFromTemplateResult(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CacheIRCompiler::emitNewPlainObjectResult(uint32_t numFixedSlots,
|
||||
uint32_t numDynamicSlots,
|
||||
gc::AllocKind allocKind,
|
||||
uint32_t shapeOffset) {
|
||||
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
|
||||
AutoOutputRegister output(*this);
|
||||
AutoScratchRegister obj(allocator, masm);
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
AutoScratchRegisterMaybeOutput shape(allocator, masm, output);
|
||||
|
||||
StubFieldOffset shapeSlot(shapeOffset, StubField::Type::Shape);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
emitLoadStubField(shapeSlot, shape);
|
||||
masm.createPlainGCObject(obj, shape, scratch, shape, numFixedSlots,
|
||||
numDynamicSlots, allocKind, gc::DefaultHeap,
|
||||
failure->label());
|
||||
|
||||
masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CacheIRCompiler::emitComparePointerResultShared(JSOp op,
|
||||
TypedOperandId lhsId,
|
||||
TypedOperandId rhsId) {
|
||||
|
|
|
@ -2468,6 +2468,16 @@
|
|||
disambiguationIdHi: UInt32Imm
|
||||
disambiguationIdLo: UInt32Imm
|
||||
|
||||
- name: NewPlainObjectResult
|
||||
shared: true
|
||||
transpile: false
|
||||
cost_estimate: 4
|
||||
args:
|
||||
numFixedSlots: UInt32Imm
|
||||
numDynamicSlots: UInt32Imm
|
||||
allocKind: AllocKindImm
|
||||
shape: ShapeField
|
||||
|
||||
- name: CallStringConcatResult
|
||||
shared: true
|
||||
transpile: true
|
||||
|
|
|
@ -113,6 +113,9 @@ class MOZ_RAII CacheIROpsJitSpewer {
|
|||
void spewWasmValTypeImm(const char* name, wasm::ValType::Kind kind) {
|
||||
out_.printf("%s WasmValTypeKind(%u)", name, unsigned(kind));
|
||||
}
|
||||
void spewAllocKindImm(const char* name, gc::AllocKind kind) {
|
||||
out_.printf("%s AllocKind(%u)", name, unsigned(kind));
|
||||
}
|
||||
|
||||
public:
|
||||
CacheIROpsJitSpewer(GenericPrinter& out, const char* prefix)
|
||||
|
@ -248,6 +251,9 @@ class MOZ_RAII CacheIROpsJSONSpewer {
|
|||
void spewWasmValTypeImm(const char* name, wasm::ValType::Kind kind) {
|
||||
spewArgImpl(name, "Imm", unsigned(kind));
|
||||
}
|
||||
void spewAllocKindImm(const char* name, gc::AllocKind kind) {
|
||||
spewArgImpl(name, "Imm", unsigned(kind));
|
||||
}
|
||||
|
||||
public:
|
||||
explicit CacheIROpsJSONSpewer(JSONPrinter& j) : j_(j) {}
|
||||
|
|
|
@ -102,6 +102,7 @@ arg_writer_info = {
|
|||
"UInt32Imm": ("uint32_t", "writeUInt32Imm"),
|
||||
"JSNativeImm": ("JSNative", "writeJSNativeImm"),
|
||||
"StaticStringImm": ("const char*", "writeStaticStringImm"),
|
||||
"AllocKindImm": ("gc::AllocKind", "writeAllocKindImm"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -197,6 +198,7 @@ arg_reader_info = {
|
|||
"UInt32Imm": ("uint32_t", "", "reader.uint32Immediate()"),
|
||||
"JSNativeImm": ("JSNative", "", "reinterpret_cast<JSNative>(reader.pointer())"),
|
||||
"StaticStringImm": ("const char*", "", "reinterpret_cast<char*>(reader.pointer())"),
|
||||
"AllocKindImm": ("gc::AllocKind", "", "reader.allocKind()"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -278,6 +280,7 @@ arg_spewer_method = {
|
|||
"UInt32Imm": "spewUInt32Imm",
|
||||
"JSNativeImm": "spewJSNativeImm",
|
||||
"StaticStringImm": "spewStaticStringImm",
|
||||
"AllocKindImm": "spewAllocKindImm",
|
||||
}
|
||||
|
||||
|
||||
|
@ -410,6 +413,7 @@ arg_length = {
|
|||
"UInt32Imm": 4,
|
||||
"JSNativeImm": "sizeof(uintptr_t)",
|
||||
"StaticStringImm": "sizeof(uintptr_t)",
|
||||
"AllocKindImm": 1,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -440,6 +440,41 @@ void MacroAssembler::createGCObject(Register obj, Register temp,
|
|||
initGCThing(obj, temp, templateObj, initContents);
|
||||
}
|
||||
|
||||
void MacroAssembler::createPlainGCObject(
|
||||
Register result, Register shape, Register temp, Register temp2,
|
||||
uint32_t numFixedSlots, uint32_t numDynamicSlots, gc::AllocKind allocKind,
|
||||
gc::InitialHeap initialHeap, Label* fail) {
|
||||
MOZ_ASSERT(gc::IsObjectAllocKind(allocKind));
|
||||
MOZ_ASSERT(shape != temp, "shape can overlap with temp2, but not temp");
|
||||
|
||||
// Allocate object.
|
||||
allocateObject(result, temp, allocKind, numDynamicSlots, initialHeap, fail);
|
||||
|
||||
// Initialize shape field.
|
||||
storePtr(shape, Address(result, JSObject::offsetOfShape()));
|
||||
|
||||
// If the object has dynamic slots, allocateObject will initialize
|
||||
// the slots field. If not, we must initialize it now.
|
||||
if (numDynamicSlots == 0) {
|
||||
storePtr(ImmPtr(emptyObjectSlots),
|
||||
Address(result, NativeObject::offsetOfSlots()));
|
||||
}
|
||||
|
||||
// Initialize elements field.
|
||||
storePtr(ImmPtr(emptyObjectElements),
|
||||
Address(result, NativeObject::offsetOfElements()));
|
||||
|
||||
// Initialize fixed slots.
|
||||
fillSlotsWithUndefined(Address(result, NativeObject::getFixedSlotOffset(0)),
|
||||
temp, 0, numFixedSlots);
|
||||
|
||||
// Initialize dynamic slots.
|
||||
if (numDynamicSlots > 0) {
|
||||
loadPtr(Address(result, NativeObject::offsetOfSlots()), temp2);
|
||||
fillSlotsWithUndefined(Address(temp2, 0), temp, 0, numDynamicSlots);
|
||||
}
|
||||
}
|
||||
|
||||
// Inline version of Nursery::allocateString.
|
||||
void MacroAssembler::nurseryAllocateString(Register result, Register temp,
|
||||
gc::AllocKind allocKind,
|
||||
|
|
|
@ -4636,6 +4636,11 @@ class MacroAssembler : public MacroAssemblerSpecific {
|
|||
gc::InitialHeap initialHeap, Label* fail,
|
||||
bool initContents = true);
|
||||
|
||||
void createPlainGCObject(Register result, Register shape, Register temp,
|
||||
Register temp2, uint32_t numFixedSlots,
|
||||
uint32_t numDynamicSlots, gc::AllocKind allocKind,
|
||||
gc::InitialHeap initialHeap, Label* fail);
|
||||
|
||||
void initGCThing(Register obj, Register temp,
|
||||
const TemplateObject& templateObj, bool initContents = true);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче