tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7b9ab846a3f373798772e13f9fd6e35453726d77
parent 739186d77d4815ead3989e862ef7d2e954e7cfd3
Author: André Bargull <andre.bargull@gmail.com>
Date:   Tue, 21 Oct 2025 07:05:39 +0000

Bug 1991402 - Part 4: Use InlinableNativeIRGenerator for inlinable native getters. r=jandem

Allow to create `InlinableNativeIRGenerator` with `GetPropIRGenerator`, so we
can inline get-accessor calls to native functions through the existing inlining
machinery.

Parts 6-12 will convert currently inlinable native accessors to use this new
mechanism.

Differential Revision: https://phabricator.services.mozilla.com/D266596

Diffstat:
Ajs/src/jit-test/tests/cacheir/inlinable-native-accessor-1.js | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/jit-test/tests/cacheir/inlinable-native-accessor-2.js | 30++++++++++++++++++++++++++++++
Ajs/src/jit-test/tests/cacheir/inlinable-native-accessor-3.js | 23+++++++++++++++++++++++
Ajs/src/jit-test/tests/cacheir/inlinable-native-accessor-4.js | 27+++++++++++++++++++++++++++
Mjs/src/jit/CacheIR.cpp | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mjs/src/jit/CacheIRGenerator.h | 55++++++++++++++++++++++++++++++++++++++++++++++++-------
6 files changed, 300 insertions(+), 21 deletions(-)

diff --git a/js/src/jit-test/tests/cacheir/inlinable-native-accessor-1.js b/js/src/jit-test/tests/cacheir/inlinable-native-accessor-1.js @@ -0,0 +1,91 @@ +// Test calling an inlinable native accessor property as a normal function. + +// Set.prototype.size is an inlinable getter accessor. +var SetSize = Object.getOwnPropertyDescriptor(Set.prototype, "size").get; + +// Install "size" getter as a normal method on Set.prototype. +Set.prototype.getSize = SetSize; + +var sets = [ + new Set(), + new Set([1, 2, 3]), +]; + +// Call inlinable accessor as normal method. +function testInlinableAccessorAsMethod() { + for (var i = 0; i < 100; ++i) { + var set = sets[i & 1]; + assertEq(set.getSize(), set.size); + } +} +testInlinableAccessorAsMethod(); + +// Call inlinable accessor as through FunCall. +function testInlinableAccessorWithFunCall() { + for (var i = 0; i < 100; ++i) { + var set = sets[i & 1]; + assertEq(SetSize.call(set), set.size); + } +} +testInlinableAccessorWithFunCall(); + +// Call inlinable accessor as through FunApply. +function testInlinableAccessorWithFunApply() { + for (var i = 0; i < 100; ++i) { + var set = sets[i & 1]; + assertEq(SetSize.apply(set), set.size); + } +} +testInlinableAccessorWithFunApply(); + +// Call inlinable accessor as through bound FunCall. +function testInlinableAccessorWithBoundFunCall() { + var callSetSize = Function.prototype.call.bind(SetSize); + + for (var i = 0; i < 100; ++i) { + var set = sets[i & 1]; + assertEq(callSetSize(set), set.size); + } +} +testInlinableAccessorWithBoundFunCall(); + +// Call inlinable accessor as through bound FunCall. +function testInlinableAccessorWithBoundFunApply() { + var applySetSize = Function.prototype.apply.bind(SetSize); + + for (var i = 0; i < 100; ++i) { + var set = sets[i & 1]; + assertEq(applySetSize(set), set.size); + } +} +testInlinableAccessorWithBoundFunApply(); + +// Call inlinable accessor as bound function. +function testBoundInlinableAccessor() { + var boundSetSize = SetSize.bind(sets[0]); + + for (var i = 0; i < 100; ++i) { + assertEq(boundSetSize(), sets[0].size); + } +} +testBoundInlinableAccessor(); + +// Call inlinable accessor as bound function through FunCall. +function testBoundInlinableAccessorWithFunCall() { + var boundSetSize = SetSize.bind(sets[0]); + + for (var i = 0; i < 100; ++i) { + assertEq(boundSetSize.call(), sets[0].size); + } +} +testBoundInlinableAccessorWithFunCall(); + +// Call inlinable accessor as bound function through FunApply. +function testBoundInlinableAccessorWithFunApply() { + var boundSetSize = SetSize.bind(sets[0]); + + for (var i = 0; i < 100; ++i) { + assertEq(boundSetSize.apply(), sets[0].size); + } +} +testInlinableAccessorWithBoundFunApply(); diff --git a/js/src/jit-test/tests/cacheir/inlinable-native-accessor-2.js b/js/src/jit-test/tests/cacheir/inlinable-native-accessor-2.js @@ -0,0 +1,30 @@ +// Test calling an inlinable native function as an accessor property. + +// Install the inlinable Number.prototype.toString method as an accessor property. +Object.defineProperty(Number.prototype, "tostr", { + get: Number.prototype.toString, +}); + +function testWithPrimitive() { + var key = "tostr"; + for (var i = 0; i < 100; ++i) { + assertEq(i.tostr, i.toString()); + assertEq(i[key], i.toString()); + } +} +testWithPrimitive(); + +// Install the inlinable Date.prototype.getTime method as an accessor property. +Object.defineProperty(Date.prototype, "time", { + get: Date.prototype.getTime, +}); + +function testWithObject() { + var key = "time"; + for (var i = 0; i < 100; ++i) { + var d = new Date(i); + assertEq(d.time, d.getTime()); + assertEq(d[key], d.getTime()); + } +} +testWithObject(); diff --git a/js/src/jit-test/tests/cacheir/inlinable-native-accessor-3.js b/js/src/jit-test/tests/cacheir/inlinable-native-accessor-3.js @@ -0,0 +1,23 @@ +// Test calling an inlinable native function as an accessor on a WindowProxy. + +var window = newGlobal({useWindowProxy: true}); + +// GetPropIRGenerator::tryAttachWindowProxy only attaches a stub if the current +// script global matches the window proxy, therefore we have to evaluate the +// test in |window|'s global environment. +window.eval(` +var window = this; + +// Use Math.random because it can be called with any |this| value. +Object.defineProperty(window, "random", { + get: Math.random, +}); + +function testRandom() { + for (var i = 0; i < 100; ++i) { + var r = window.random; + assertEq(0 <= r && r < 1, true); + } +} +testRandom(); +`); diff --git a/js/src/jit-test/tests/cacheir/inlinable-native-accessor-4.js b/js/src/jit-test/tests/cacheir/inlinable-native-accessor-4.js @@ -0,0 +1,27 @@ +// Test calling an inlinable native function as an accessor when the native function uses alloc-sites. + +function testObject() { + var obj = Object.defineProperty({}, "object", { + get: Object, + }); + + for (var i = 0; i < 100; i++) { + var o = obj.object; + assertEq(typeof o, "object"); + assertEq(o !== null, true); + } +} +testObject(); + +function testArray() { + var obj = Object.defineProperty({}, "array", { + get: Array, + }); + + for (var i = 0; i < 100; i++) { + var a = obj.array; + assertEq(a.length, 0); + assertEq(Array.isArray(a), true); + } +} +testArray(); diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp @@ -1113,12 +1113,22 @@ void GetPropIRGenerator::emitCallGetterResultGuards(NativeObject* obj, } void GetPropIRGenerator::emitCallGetterResult(NativeGetPropKind kind, - NativeObject* obj, - NativeObject* holder, HandleId id, - PropertyInfo prop, + Handle<NativeObject*> obj, + Handle<NativeObject*> holder, + HandleId id, PropertyInfo prop, ObjOperandId objId, ValOperandId receiverId) { emitCallGetterResultGuards(obj, holder, id, prop, objId); + + if (kind == NativeGetPropKind::NativeGetter && + mode_ == ICState::Mode::Specialized) { + auto attached = tryAttachInlinableNativeGetter(holder, prop, receiverId); + if (attached != AttachDecision::NoAction) { + MOZ_ASSERT(attached == AttachDecision::Attach); + return; + } + } + emitCallGetterResultNoGuards(kind, obj, holder, prop, receiverId); } @@ -1251,10 +1261,10 @@ AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj, HandleId id, ValOperandId receiverId) { Maybe<PropertyInfo> prop; - NativeObject* holder = nullptr; + Rooted<NativeObject*> holder(cx_); NativeGetPropKind kind = - CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + CanAttachNativeGetProp(cx_, obj, id, holder.address(), &prop, pc_); switch (kind) { case NativeGetPropKind::None: return AttachDecision::NoAction; @@ -1282,7 +1292,7 @@ AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj, } case NativeGetPropKind::ScriptedGetter: case NativeGetPropKind::NativeGetter: { - auto* nobj = &obj->as<NativeObject>(); + auto nobj = obj.as<NativeObject>(); MOZ_ASSERT(!IsWindow(nobj)); // If we're in megamorphic mode, we assume that a specialized @@ -1383,11 +1393,11 @@ AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, } // Now try to do the lookup on the Window (the current global). - GlobalObject* windowObj = cx_->global(); - NativeObject* holder = nullptr; + Handle<GlobalObject*> windowObj = cx_->global(); + Rooted<NativeObject*> holder(cx_); Maybe<PropertyInfo> prop; NativeGetPropKind kind = - CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_); + CanAttachNativeGetProp(cx_, windowObj, id, holder.address(), &prop, pc_); switch (kind) { case NativeGetPropKind::None: return AttachDecision::NoAction; @@ -2432,6 +2442,31 @@ AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj, return AttachDecision::NoAction; } +AttachDecision GetPropIRGenerator::tryAttachInlinableNativeGetter( + Handle<NativeObject*> holder, PropertyInfo prop, ValOperandId receiverId) { + MOZ_ASSERT(mode_ == ICState::Mode::Specialized); + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + Rooted<JSFunction*> target(cx_, &holder->getGetter(prop)->as<JSFunction>()); + MOZ_ASSERT(target->isNativeWithoutJitEntry()); + + Handle<Value> thisValue = val_; + + bool isSpread = false; + bool isSameRealm = cx_->realm() == target->realm(); + bool isConstructing = false; + CallFlags flags(isConstructing, isSpread, isSameRealm); + + // Check for specific native-function optimizations. + InlinableNativeIRGenerator nativeGen(*this, target, thisValue, flags, + receiverId); + return nativeGen.tryAttachStub(); +} + AttachDecision GetPropIRGenerator::tryAttachTypedArray(HandleObject obj, ObjOperandId objId, HandleId id) { @@ -3012,10 +3047,10 @@ AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, return AttachDecision::NoAction; } - NativeObject* holder = nullptr; + Rooted<NativeObject*> holder(cx_); Maybe<PropertyInfo> prop; NativeGetPropKind kind = - CanAttachNativeGetProp(cx_, proto, id, &holder, &prop, pc_); + CanAttachNativeGetProp(cx_, proto, id, holder.address(), &prop, pc_); if (kind == NativeGetPropKind::None) { return AttachDecision::NoAction; } @@ -3027,7 +3062,7 @@ AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, } maybeEmitIdGuard(id); - auto* nproto = &proto->as<NativeObject>(); + Rooted<NativeObject*> nproto(cx_, &proto->as<NativeObject>()); ObjOperandId protoId = writer.loadObject(nproto); switch (kind) { @@ -6534,6 +6569,15 @@ ObjOperandId InlinableNativeIRGenerator::emitNativeCalleeGuard( // native from a different realm. MOZ_ASSERT(target_->isNativeWithoutJitEntry()); + // Guards already emitted in GetPropIRGenerator::emitCallGetterResultGuards. + if (isAccessorOp()) { + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); + MOZ_ASSERT(!flags_.isConstructing()); + MOZ_ASSERT(!isCalleeBoundFunction()); + MOZ_ASSERT(!isTargetBoundFunction()); + return ObjOperandId(); + } + ValOperandId calleeValId; switch (flags_.getArgFormat()) { case CallFlags::Standard: @@ -6697,7 +6741,9 @@ ObjOperandId InlinableNativeIRGenerator::emitLoadArgsArray() { } MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray); - return generator_.emitFunApplyArgsGuard(flags_.getArgFormat()).ref(); + return gen_.as<CallIRGenerator*>() + ->emitFunApplyArgsGuard(flags_.getArgFormat()) + .ref(); } ValOperandId InlinableNativeIRGenerator::loadBoundArgument( @@ -6719,6 +6765,15 @@ ValOperandId InlinableNativeIRGenerator::loadBoundArgument( } ValOperandId InlinableNativeIRGenerator::loadThis(ObjOperandId calleeId) { + // Accessor operations use the receiver as their |this| value. + if (isAccessorOp()) { + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); + MOZ_ASSERT(!isTargetBoundFunction()); + MOZ_ASSERT(!isCalleeBoundFunction()); + MOZ_ASSERT(receiverId_.valid()); + return receiverId_; + } + switch (flags_.getArgFormat()) { case CallFlags::Standard: case CallFlags::Spread: @@ -6798,6 +6853,7 @@ ValOperandId InlinableNativeIRGenerator::loadArgument(ObjOperandId calleeId, flags_.getArgFormat() == CallFlags::FunApplyNullUndefined); MOZ_ASSERT_IF(flags_.getArgFormat() == CallFlags::FunApplyNullUndefined, isTargetBoundFunction() && hasBoundArguments()); + MOZ_ASSERT(!isAccessorOp(), "get property operations don't have arguments"); // Check if the |this| value is stored in the bound arguments. bool thisFromBoundArgs = flags_.getArgFormat() == CallFlags::FunCall && @@ -12061,6 +12117,11 @@ AttachDecision InlinableNativeIRGenerator::tryAttachObjectConstructor() { gc::AllocSite* site = nullptr; PlainObject* templateObj = nullptr; if (args_.length() == 0) { + // Don't optimize if we can't create an alloc-site. + if (!BytecodeOpCanHaveAllocSite(op())) { + return AttachDecision::NoAction; + } + // Stub doesn't support metadata builder if (cx_->realm()->hasAllocationMetadataBuilder()) { return AttachDecision::NoAction; @@ -12112,6 +12173,11 @@ AttachDecision InlinableNativeIRGenerator::tryAttachObjectConstructor() { } AttachDecision InlinableNativeIRGenerator::tryAttachArrayConstructor() { + // Don't optimize if we can't create an alloc-site. + if (!BytecodeOpCanHaveAllocSite(op())) { + return AttachDecision::NoAction; + } + // Only optimize the |Array()| and |Array(n)| cases (with or without |new|) // for now. Note that self-hosted code calls this without |new| via std_Array. if (args_.length() > 1) { @@ -12925,7 +12991,8 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() { MOZ_ASSERT(target_->isNativeWithoutJitEntry()); // Special case functions are only optimized for normal calls. - if (!BytecodeCallOpCanHaveInlinableNative(op())) { + if (!BytecodeCallOpCanHaveInlinableNative(op()) && + !BytecodeGetOpCanHaveInlinableNative(op())) { return AttachDecision::NoAction; } diff --git a/js/src/jit/CacheIRGenerator.h b/js/src/jit/CacheIRGenerator.h @@ -166,8 +166,13 @@ class MOZ_RAII GetPropIRGenerator : public IRGenerator { HandleValue val_; HandleValue idVal_; + friend class InlinableNativeIRGenerator; + AttachDecision tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId receiverId); + AttachDecision tryAttachInlinableNativeGetter(Handle<NativeObject*> holder, + PropertyInfo prop, + ValOperandId receiverId); AttachDecision tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id); AttachDecision tryAttachTypedArray(HandleObject obj, ObjOperandId objId, @@ -280,8 +285,8 @@ class MOZ_RAII GetPropIRGenerator : public IRGenerator { void emitCallGetterResultGuards(NativeObject* obj, NativeObject* holder, HandleId id, PropertyInfo prop, ObjOperandId objId); - void emitCallGetterResult(NativeGetPropKind kind, NativeObject* obj, - NativeObject* holder, HandleId id, + void emitCallGetterResult(NativeGetPropKind kind, Handle<NativeObject*> obj, + Handle<NativeObject*> holder, HandleId id, PropertyInfo prop, ObjOperandId objId, ValOperandId receiverId); void emitCallDOMGetterResult(NativeObject* obj, NativeObject* holder, @@ -650,7 +655,8 @@ class MOZ_RAII CallIRGenerator : public IRGenerator { }; class MOZ_RAII InlinableNativeIRGenerator { - CallIRGenerator& generator_; + mozilla::Variant<CallIRGenerator*, GetPropIRGenerator*> gen_; + IRGenerator& generator_; CacheIRWriter& writer; JSContext* cx_; @@ -663,6 +669,9 @@ class MOZ_RAII InlinableNativeIRGenerator { CallFlags flags_; uint32_t stackArgc_; + // |this| for inlined accesor operations. + ValOperandId receiverId_; + HandleScript script() const { return generator_.script_; } JSObject* callee() const { return callee_; } bool isFirstStub() const { return generator_.isFirstStub_; } @@ -670,6 +679,9 @@ class MOZ_RAII InlinableNativeIRGenerator { JSOp op() const { return generator_.jsop(); } uint32_t stackArgc() const { return stackArgc_; } + // Inlined native accessor for GetProp or GetElem operations. + bool isAccessorOp() const { return !IsInvokeOp(op()); } + bool isCalleeBoundFunction() const; BoundFunctionObject* boundCallee() const; @@ -700,6 +712,10 @@ class MOZ_RAII InlinableNativeIRGenerator { bool hasBoundArguments() const; Int32OperandId initializeInputOperand() { + // Input operands are already initialized for inlined accessors. + if (isAccessorOp()) { + return Int32OperandId(); + } return Int32OperandId(writer.setInputOperandId(0)); } @@ -880,7 +896,8 @@ class MOZ_RAII InlinableNativeIRGenerator { #endif void trackAttached(const char* name /* must be a C string literal */) { - return generator_.trackAttached(name); + return gen_.match( + [&](auto* generator) { return generator->trackAttached(name); }); } public: @@ -889,7 +906,8 @@ class MOZ_RAII InlinableNativeIRGenerator { HandleValue thisValue, HandleValueArray args, CallFlags flags, Handle<BoundFunctionObject*> boundTarget = nullptr) - : generator_(generator), + : gen_(&generator), + generator_(generator), writer(generator.writer), cx_(generator.cx_), callee_(callee), @@ -899,7 +917,25 @@ class MOZ_RAII InlinableNativeIRGenerator { args_(args), boundTarget_(boundTarget), flags_(flags), - stackArgc_(generator.argc_) {} + stackArgc_(generator.argc_), + receiverId_() {} + + InlinableNativeIRGenerator(GetPropIRGenerator& generator, + HandleFunction target, HandleValue thisValue, + CallFlags flags, ValOperandId receiverId) + : gen_(&generator), + generator_(generator), + writer(generator.writer), + cx_(generator.cx_), + callee_(target), + target_(target), + newTarget_(JS::NullHandleValue), + thisval_(thisValue), + args_(HandleValueArray::empty()), + boundTarget_(nullptr), + flags_(flags), + stackArgc_(0), + receiverId_(receiverId) {} AttachDecision tryAttachStub(); }; @@ -1088,13 +1124,18 @@ class MOZ_RAII LambdaIRGenerator : public IRGenerator { AttachDecision tryAttachFunctionClone(); }; -// Returns true for bytecode ops that can use InlinableNativeIRGenerator. +// Returns true for bytecode call ops that can use InlinableNativeIRGenerator. inline bool BytecodeCallOpCanHaveInlinableNative(JSOp op) { return op == JSOp::Call || op == JSOp::CallContent || op == JSOp::New || op == JSOp::NewContent || op == JSOp::CallIgnoresRv || op == JSOp::SpreadCall; } +// Returns true for bytecode get ops that can use InlinableNativeIRGenerator. +inline bool BytecodeGetOpCanHaveInlinableNative(JSOp op) { + return op == JSOp::GetProp || op == JSOp::GetElem; +} + inline bool BytecodeOpCanHaveAllocSite(JSOp op) { return BytecodeCallOpCanHaveInlinableNative(op) || op == JSOp::NewArray || op == JSOp::NewObject || op == JSOp::NewInit || op == JSOp::CallIter ||