From 6a77919060ae5d80b24a0cde854ae794b8b28228 Mon Sep 17 00:00:00 2001 From: Iain Ireland Date: Wed, 7 Apr 2021 07:03:11 +0000 Subject: [PATCH] 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 --- js/src/jit/CacheIR.cpp | 50 +++++++++++++++++++++++++++--- js/src/jit/CacheIR.h | 8 +++++ js/src/jit/CacheIRCompiler.cpp | 26 ++++++++++++++++ js/src/jit/CacheIROps.yaml | 10 ++++++ js/src/jit/CacheIRSpewer.cpp | 6 ++++ js/src/jit/GenerateCacheIRFiles.py | 4 +++ js/src/jit/MacroAssembler.cpp | 35 +++++++++++++++++++++ js/src/jit/MacroAssembler.h | 5 +++ 8 files changed, 139 insertions(+), 5 deletions(-) diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index de8758924564..c3efc0bb0ea7 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -11003,16 +11003,46 @@ void NewObjectIRGenerator::trackAttached(const char* name) { #endif } -AttachDecision NewObjectIRGenerator::tryAttachStub() { - AutoAssertNoPendingException aanpe(cx_); - if (templateObject_->as().hasDynamicSlots()) { - trackAttached(IRGenerator::NotAttached); +AttachDecision NewObjectIRGenerator::tryAttachPlainObject() { + NativeObject* nativeObj = &templateObject_->as(); + MOZ_ASSERT(nativeObj->is()); + + // 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().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); diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h index 0169f2766e6f..7b30d665a371 100644 --- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -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. diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp index dba5492a1434..420d670f228b 100644 --- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -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) { diff --git a/js/src/jit/CacheIROps.yaml b/js/src/jit/CacheIROps.yaml index 7baf0f26183c..7ff73c7d465d 100644 --- a/js/src/jit/CacheIROps.yaml +++ b/js/src/jit/CacheIROps.yaml @@ -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 diff --git a/js/src/jit/CacheIRSpewer.cpp b/js/src/jit/CacheIRSpewer.cpp index 7cd7201a1b0c..3561fc0c0c76 100644 --- a/js/src/jit/CacheIRSpewer.cpp +++ b/js/src/jit/CacheIRSpewer.cpp @@ -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) {} diff --git a/js/src/jit/GenerateCacheIRFiles.py b/js/src/jit/GenerateCacheIRFiles.py index bb0c2b277af8..9469a5d0937b 100644 --- a/js/src/jit/GenerateCacheIRFiles.py +++ b/js/src/jit/GenerateCacheIRFiles.py @@ -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(reader.pointer())"), "StaticStringImm": ("const char*", "", "reinterpret_cast(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, } diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index c8b6da1a471f..1ff4f891a5d8 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -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, diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index b653be96f8c1..b5ac2985177a 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -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);