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:
Iain Ireland 2021-04-07 07:03:11 +00:00
Родитель 305b07e914
Коммит 6a77919060
8 изменённых файлов: 139 добавлений и 5 удалений

Просмотреть файл

@ -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);