diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/jit/IonIC.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit/IonIC.cpp')
-rw-r--r-- | js/src/jit/IonIC.cpp | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/js/src/jit/IonIC.cpp b/js/src/jit/IonIC.cpp new file mode 100644 index 0000000000..55f3bbea6c --- /dev/null +++ b/js/src/jit/IonIC.cpp @@ -0,0 +1,740 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/IonIC.h" + +#include "jit/CacheIRCompiler.h" +#include "jit/CacheIRGenerator.h" +#include "jit/IonScript.h" +#include "jit/VMFunctions.h" +#include "util/DiagnosticAssertions.h" +#include "vm/EqualityOperations.h" +#include "vm/Iteration.h" + +#include "vm/Interpreter-inl.h" +#include "vm/JSScript-inl.h" + +using namespace js; +using namespace js::jit; + +void IonIC::resetCodeRaw(IonScript* ionScript) { + codeRaw_ = fallbackAddr(ionScript); +} + +uint8_t* IonIC::fallbackAddr(IonScript* ionScript) const { + return ionScript->method()->raw() + fallbackOffset_; +} + +uint8_t* IonIC::rejoinAddr(IonScript* ionScript) const { + return ionScript->method()->raw() + rejoinOffset_; +} + +Register IonIC::scratchRegisterForEntryJump() { + switch (kind_) { + case CacheKind::GetProp: + case CacheKind::GetElem: + return asGetPropertyIC()->output().scratchReg(); + case CacheKind::GetPropSuper: + case CacheKind::GetElemSuper: + return asGetPropSuperIC()->output().scratchReg(); + case CacheKind::SetProp: + case CacheKind::SetElem: + return asSetPropertyIC()->temp(); + case CacheKind::GetName: + return asGetNameIC()->temp(); + case CacheKind::BindName: + return asBindNameIC()->temp(); + case CacheKind::In: + return asInIC()->temp(); + case CacheKind::HasOwn: + return asHasOwnIC()->output(); + case CacheKind::CheckPrivateField: + return asCheckPrivateFieldIC()->output(); + case CacheKind::GetIterator: + return asGetIteratorIC()->temp1(); + case CacheKind::OptimizeSpreadCall: + return asOptimizeSpreadCallIC()->temp(); + case CacheKind::InstanceOf: + return asInstanceOfIC()->output(); + case CacheKind::UnaryArith: + return asUnaryArithIC()->output().scratchReg(); + case CacheKind::ToPropertyKey: + return asToPropertyKeyIC()->output().scratchReg(); + case CacheKind::BinaryArith: + return asBinaryArithIC()->output().scratchReg(); + case CacheKind::Compare: + return asCompareIC()->output(); + case CacheKind::CloseIter: + return asCloseIterIC()->temp(); + case CacheKind::OptimizeGetIterator: + return asOptimizeGetIteratorIC()->temp(); + case CacheKind::Call: + case CacheKind::TypeOf: + case CacheKind::ToBool: + case CacheKind::GetIntrinsic: + case CacheKind::NewArray: + case CacheKind::NewObject: + MOZ_CRASH("Unsupported IC"); + } + + MOZ_CRASH("Invalid kind"); +} + +void IonIC::discardStubs(Zone* zone, IonScript* ionScript) { + if (firstStub_) { + // We are removing edges from IonIC to gcthings. Perform a write barrier to + // let the GC know about those edges. + PreWriteBarrier(zone, ionScript); + } + +#ifdef JS_CRASH_DIAGNOSTICS + IonICStub* stub = firstStub_; + while (stub) { + IonICStub* next = stub->next(); + stub->poison(); + stub = next; + } +#endif + + firstStub_ = nullptr; + resetCodeRaw(ionScript); + state_.trackUnlinkedAllStubs(); +} + +void IonIC::reset(Zone* zone, IonScript* ionScript) { + discardStubs(zone, ionScript); + state_.reset(); +} + +void IonIC::trace(JSTracer* trc, IonScript* ionScript) { + if (script_) { + TraceManuallyBarrieredEdge(trc, &script_, "IonIC::script_"); + } + + uint8_t* nextCodeRaw = codeRaw_; + for (IonICStub* stub = firstStub_; stub; stub = stub->next()) { + JitCode* code = JitCode::FromExecutable(nextCodeRaw); + TraceManuallyBarrieredEdge(trc, &code, "ion-ic-code"); + + TraceCacheIRStub(trc, stub, stub->stubInfo()); + + nextCodeRaw = stub->nextCodeRaw(); + } + + MOZ_ASSERT(nextCodeRaw == fallbackAddr(ionScript)); +} + +// This helper handles ICState updates/transitions while attaching CacheIR +// stubs. +template <typename IRGenerator, typename... Args> +static void TryAttachIonStub(JSContext* cx, IonIC* ic, IonScript* ionScript, + Args&&... args) { + if (ic->state().maybeTransition()) { + ic->discardStubs(cx->zone(), ionScript); + } + + if (ic->state().canAttachStub()) { + RootedScript script(cx, ic->script()); + bool attached = false; + IRGenerator gen(cx, script, ic->pc(), ic->state(), + std::forward<Args>(args)...); + switch (gen.tryAttachStub()) { + case AttachDecision::Attach: + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, + &attached); + break; + case AttachDecision::NoAction: + break; + case AttachDecision::TemporarilyUnoptimizable: + attached = true; + break; + case AttachDecision::Deferred: + MOZ_ASSERT_UNREACHABLE("Not expected in generic TryAttachIonStub"); + break; + } + if (!attached) { + ic->state().trackNotAttached(); + } + } +} + +/* static */ +bool IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript, + IonGetPropertyIC* ic, HandleValue val, + HandleValue idVal, MutableHandleValue res) { + IonScript* ionScript = outerScript->ionScript(); + + // Optimized-arguments and other magic values must not escape to Ion ICs. + MOZ_ASSERT(!val.isMagic()); + + TryAttachIonStub<GetPropIRGenerator>(cx, ic, ionScript, ic->kind(), val, + idVal); + + if (ic->kind() == CacheKind::GetProp) { + Rooted<PropertyName*> name(cx, idVal.toString()->asAtom().asPropertyName()); + if (!GetProperty(cx, val, name, res)) { + return false; + } + } else { + MOZ_ASSERT(ic->kind() == CacheKind::GetElem); + if (!GetElementOperation(cx, val, idVal, res)) { + return false; + } + } + + return true; +} + +/* static */ +bool IonGetPropSuperIC::update(JSContext* cx, HandleScript outerScript, + IonGetPropSuperIC* ic, HandleObject obj, + HandleValue receiver, HandleValue idVal, + MutableHandleValue res) { + IonScript* ionScript = outerScript->ionScript(); + + if (ic->state().maybeTransition()) { + ic->discardStubs(cx->zone(), ionScript); + } + + RootedValue val(cx, ObjectValue(*obj)); + + TryAttachIonStub<GetPropIRGenerator>(cx, ic, ionScript, ic->kind(), val, + idVal); + + if (ic->kind() == CacheKind::GetPropSuper) { + Rooted<PropertyName*> name(cx, idVal.toString()->asAtom().asPropertyName()); + if (!GetProperty(cx, obj, receiver, name, res)) { + return false; + } + } else { + MOZ_ASSERT(ic->kind() == CacheKind::GetElemSuper); + + JSOp op = JSOp(*ic->pc()); + MOZ_ASSERT(op == JSOp::GetElemSuper); + + if (!GetObjectElementOperation(cx, op, obj, receiver, idVal, res)) { + return false; + } + } + + return true; +} + +/* static */ +bool IonSetPropertyIC::update(JSContext* cx, HandleScript outerScript, + IonSetPropertyIC* ic, HandleObject obj, + HandleValue idVal, HandleValue rhs) { + using DeferType = SetPropIRGenerator::DeferType; + + Rooted<Shape*> oldShape(cx); + IonScript* ionScript = outerScript->ionScript(); + + bool attached = false; + DeferType deferType = DeferType::None; + + if (ic->state().maybeTransition()) { + ic->discardStubs(cx->zone(), ionScript); + } + + if (ic->state().canAttachStub()) { + oldShape = obj->shape(); + + RootedValue objv(cx, ObjectValue(*obj)); + RootedScript script(cx, ic->script()); + jsbytecode* pc = ic->pc(); + + SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state(), objv, idVal, + rhs); + switch (gen.tryAttachStub()) { + case AttachDecision::Attach: + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, + &attached); + break; + case AttachDecision::NoAction: + break; + case AttachDecision::TemporarilyUnoptimizable: + attached = true; + break; + case AttachDecision::Deferred: + deferType = gen.deferType(); + MOZ_ASSERT(deferType != DeferType::None); + break; + } + } + + jsbytecode* pc = ic->pc(); + if (ic->kind() == CacheKind::SetElem) { + if (JSOp(*pc) == JSOp::InitElemInc) { + if (!InitElemIncOperation(cx, obj.as<ArrayObject>(), idVal.toInt32(), + rhs)) { + return false; + } + } else if (IsPropertyInitOp(JSOp(*pc))) { + if (!InitElemOperation(cx, pc, obj, idVal, rhs)) { + return false; + } + } else { + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc))); + if (!SetObjectElement(cx, obj, idVal, rhs, ic->strict())) { + return false; + } + } + } else { + MOZ_ASSERT(ic->kind() == CacheKind::SetProp); + + if (JSOp(*pc) == JSOp::InitGLexical) { + RootedScript script(cx, ic->script()); + MOZ_ASSERT(!script->hasNonSyntacticScope()); + InitGlobalLexicalOperation(cx, &cx->global()->lexicalEnvironment(), + script, pc, rhs); + } else if (IsPropertyInitOp(JSOp(*pc))) { + Rooted<PropertyName*> name(cx, + idVal.toString()->asAtom().asPropertyName()); + if (!InitPropertyOperation(cx, pc, obj, name, rhs)) { + return false; + } + } else { + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc))); + Rooted<PropertyName*> name(cx, + idVal.toString()->asAtom().asPropertyName()); + if (!SetProperty(cx, obj, name, rhs, ic->strict(), pc)) { + return false; + } + } + } + + if (attached) { + return true; + } + + // The SetProperty call might have entered this IC recursively, so try + // to transition. + if (ic->state().maybeTransition()) { + ic->discardStubs(cx->zone(), ionScript); + } + + bool canAttachStub = ic->state().canAttachStub(); + if (deferType != DeferType::None && canAttachStub) { + RootedValue objv(cx, ObjectValue(*obj)); + RootedScript script(cx, ic->script()); + jsbytecode* pc = ic->pc(); + SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state(), objv, idVal, + rhs); + MOZ_ASSERT(deferType == DeferType::AddSlot); + AttachDecision decision = gen.tryAttachAddSlotStub(oldShape); + + switch (decision) { + case AttachDecision::Attach: + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, + &attached); + break; + case AttachDecision::NoAction: + gen.trackAttached(IRGenerator::NotAttached); + break; + case AttachDecision::TemporarilyUnoptimizable: + case AttachDecision::Deferred: + MOZ_ASSERT_UNREACHABLE("Invalid attach result"); + break; + } + } + if (!attached && canAttachStub) { + ic->state().trackNotAttached(); + } + + return true; +} + +/* static */ +bool IonGetNameIC::update(JSContext* cx, HandleScript outerScript, + IonGetNameIC* ic, HandleObject envChain, + MutableHandleValue res) { + IonScript* ionScript = outerScript->ionScript(); + jsbytecode* pc = ic->pc(); + Rooted<PropertyName*> name(cx, ic->script()->getName(pc)); + + TryAttachIonStub<GetNameIRGenerator>(cx, ic, ionScript, envChain, name); + + RootedObject obj(cx); + RootedObject holder(cx); + PropertyResult prop; + if (!LookupName(cx, name, envChain, &obj, &holder, &prop)) { + return false; + } + + if (JSOp(*GetNextPc(pc)) == JSOp::Typeof) { + return FetchName<GetNameMode::TypeOf>(cx, obj, holder, name, prop, res); + } + + return FetchName<GetNameMode::Normal>(cx, obj, holder, name, prop, res); +} + +/* static */ +JSObject* IonBindNameIC::update(JSContext* cx, HandleScript outerScript, + IonBindNameIC* ic, HandleObject envChain) { + IonScript* ionScript = outerScript->ionScript(); + jsbytecode* pc = ic->pc(); + Rooted<PropertyName*> name(cx, ic->script()->getName(pc)); + + TryAttachIonStub<BindNameIRGenerator>(cx, ic, ionScript, envChain, name); + + RootedObject holder(cx); + if (!LookupNameUnqualified(cx, name, envChain, &holder)) { + return nullptr; + } + + return holder; +} + +/* static */ +JSObject* IonGetIteratorIC::update(JSContext* cx, HandleScript outerScript, + IonGetIteratorIC* ic, HandleValue value) { + IonScript* ionScript = outerScript->ionScript(); + + TryAttachIonStub<GetIteratorIRGenerator>(cx, ic, ionScript, value); + + PropertyIteratorObject* iterObj = ValueToIterator(cx, value); + if (!iterObj) { + return nullptr; + } + + return iterObj; +} + +/* static */ +bool IonOptimizeSpreadCallIC::update(JSContext* cx, HandleScript outerScript, + IonOptimizeSpreadCallIC* ic, + HandleValue value, + MutableHandleValue result) { + IonScript* ionScript = outerScript->ionScript(); + + TryAttachIonStub<OptimizeSpreadCallIRGenerator>(cx, ic, ionScript, value); + + return OptimizeSpreadCall(cx, value, result); +} + +/* static */ +bool IonHasOwnIC::update(JSContext* cx, HandleScript outerScript, + IonHasOwnIC* ic, HandleValue val, HandleValue idVal, + int32_t* res) { + IonScript* ionScript = outerScript->ionScript(); + + TryAttachIonStub<HasPropIRGenerator>(cx, ic, ionScript, CacheKind::HasOwn, + idVal, val); + + bool found; + if (!HasOwnProperty(cx, val, idVal, &found)) { + return false; + } + + *res = found; + return true; +} + +/* static */ +bool IonCheckPrivateFieldIC::update(JSContext* cx, HandleScript outerScript, + IonCheckPrivateFieldIC* ic, HandleValue val, + HandleValue idVal, bool* res) { + IonScript* ionScript = outerScript->ionScript(); + jsbytecode* pc = ic->pc(); + + TryAttachIonStub<CheckPrivateFieldIRGenerator>( + cx, ic, ionScript, CacheKind::CheckPrivateField, idVal, val); + + return CheckPrivateFieldOperation(cx, pc, val, idVal, res); +} + +/* static */ +bool IonInIC::update(JSContext* cx, HandleScript outerScript, IonInIC* ic, + HandleValue key, HandleObject obj, bool* res) { + IonScript* ionScript = outerScript->ionScript(); + RootedValue objV(cx, ObjectValue(*obj)); + + TryAttachIonStub<HasPropIRGenerator>(cx, ic, ionScript, CacheKind::In, key, + objV); + + return OperatorIn(cx, key, obj, res); +} +/* static */ +bool IonInstanceOfIC::update(JSContext* cx, HandleScript outerScript, + IonInstanceOfIC* ic, HandleValue lhs, + HandleObject rhs, bool* res) { + IonScript* ionScript = outerScript->ionScript(); + + TryAttachIonStub<InstanceOfIRGenerator>(cx, ic, ionScript, lhs, rhs); + + return InstanceofOperator(cx, rhs, lhs, res); +} + +/* static */ +bool IonToPropertyKeyIC::update(JSContext* cx, HandleScript outerScript, + IonToPropertyKeyIC* ic, HandleValue val, + MutableHandleValue res) { + IonScript* ionScript = outerScript->ionScript(); + + TryAttachIonStub<ToPropertyKeyIRGenerator>(cx, ic, ionScript, val); + + return ToPropertyKeyOperation(cx, val, res); +} + +/* static */ +bool IonCloseIterIC::update(JSContext* cx, HandleScript outerScript, + IonCloseIterIC* ic, HandleObject iter) { + IonScript* ionScript = outerScript->ionScript(); + CompletionKind kind = ic->completionKind(); + + TryAttachIonStub<CloseIterIRGenerator>(cx, ic, ionScript, iter, kind); + + return CloseIterOperation(cx, iter, kind); +} + +/* static */ +bool IonOptimizeGetIteratorIC::update(JSContext* cx, HandleScript outerScript, + IonOptimizeGetIteratorIC* ic, + HandleValue value, bool* result) { + IonScript* ionScript = outerScript->ionScript(); + + TryAttachIonStub<OptimizeGetIteratorIRGenerator>(cx, ic, ionScript, value); + + return OptimizeGetIterator(cx, value, result); +} + +/* static */ +bool IonUnaryArithIC::update(JSContext* cx, HandleScript outerScript, + IonUnaryArithIC* ic, HandleValue val, + MutableHandleValue res) { + IonScript* ionScript = outerScript->ionScript(); + RootedScript script(cx, ic->script()); + jsbytecode* pc = ic->pc(); + JSOp op = JSOp(*pc); + + switch (op) { + case JSOp::BitNot: { + res.set(val); + if (!BitNot(cx, res, res)) { + return false; + } + break; + } + case JSOp::Pos: { + res.set(val); + if (!ToNumber(cx, res)) { + return false; + } + break; + } + case JSOp::Neg: { + res.set(val); + if (!NegOperation(cx, res, res)) { + return false; + } + break; + } + case JSOp::Inc: { + if (!IncOperation(cx, val, res)) { + return false; + } + break; + } + case JSOp::Dec: { + if (!DecOperation(cx, val, res)) { + return false; + } + break; + } + case JSOp::ToNumeric: { + res.set(val); + if (!ToNumeric(cx, res)) { + return false; + } + break; + } + default: + MOZ_CRASH("Unexpected op"); + } + MOZ_ASSERT(res.isNumeric()); + + TryAttachIonStub<UnaryArithIRGenerator>(cx, ic, ionScript, op, val, res); + + return true; +} + +/* static */ +bool IonBinaryArithIC::update(JSContext* cx, HandleScript outerScript, + IonBinaryArithIC* ic, HandleValue lhs, + HandleValue rhs, MutableHandleValue ret) { + IonScript* ionScript = outerScript->ionScript(); + RootedScript script(cx, ic->script()); + jsbytecode* pc = ic->pc(); + JSOp op = JSOp(*pc); + + // Don't pass lhs/rhs directly, we need the original values when + // generating stubs. + RootedValue lhsCopy(cx, lhs); + RootedValue rhsCopy(cx, rhs); + + // Perform the compare operation. + switch (op) { + case JSOp::Add: + // Do an add. + if (!AddValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Sub: + if (!SubValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Mul: + if (!MulValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Div: + if (!DivValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Mod: + if (!ModValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Pow: + if (!PowValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::BitOr: { + if (!BitOr(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::BitXor: { + if (!BitXor(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::BitAnd: { + if (!BitAnd(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::Lsh: { + if (!BitLsh(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::Rsh: { + if (!BitRsh(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::Ursh: { + if (!UrshValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + default: + MOZ_CRASH("Unhandled binary arith op"); + } + + TryAttachIonStub<BinaryArithIRGenerator>(cx, ic, ionScript, op, lhs, rhs, + ret); + + return true; +} + +/* static */ +bool IonCompareIC::update(JSContext* cx, HandleScript outerScript, + IonCompareIC* ic, HandleValue lhs, HandleValue rhs, + bool* res) { + IonScript* ionScript = outerScript->ionScript(); + RootedScript script(cx, ic->script()); + jsbytecode* pc = ic->pc(); + JSOp op = JSOp(*pc); + + // Don't pass lhs/rhs directly, we need the original values when + // generating stubs. + RootedValue lhsCopy(cx, lhs); + RootedValue rhsCopy(cx, rhs); + + // Perform the compare operation. + switch (op) { + case JSOp::Lt: + if (!LessThan(cx, &lhsCopy, &rhsCopy, res)) { + return false; + } + break; + case JSOp::Le: + if (!LessThanOrEqual(cx, &lhsCopy, &rhsCopy, res)) { + return false; + } + break; + case JSOp::Gt: + if (!GreaterThan(cx, &lhsCopy, &rhsCopy, res)) { + return false; + } + break; + case JSOp::Ge: + if (!GreaterThanOrEqual(cx, &lhsCopy, &rhsCopy, res)) { + return false; + } + break; + case JSOp::Eq: + if (!js::LooselyEqual(cx, lhsCopy, rhsCopy, res)) { + return false; + } + break; + case JSOp::Ne: + if (!js::LooselyEqual(cx, lhsCopy, rhsCopy, res)) { + return false; + } + *res = !*res; + break; + case JSOp::StrictEq: + if (!js::StrictlyEqual(cx, lhsCopy, rhsCopy, res)) { + return false; + } + break; + case JSOp::StrictNe: + if (!js::StrictlyEqual(cx, lhsCopy, rhsCopy, res)) { + return false; + } + *res = !*res; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unhandled ion compare op"); + return false; + } + + TryAttachIonStub<CompareIRGenerator>(cx, ic, ionScript, op, lhs, rhs); + + return true; +} + +uint8_t* IonICStub::stubDataStart() { + return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset(); +} + +void IonIC::attachStub(IonICStub* newStub, JitCode* code) { + MOZ_ASSERT(newStub); + MOZ_ASSERT(code); + + if (firstStub_) { + newStub->setNext(firstStub_, codeRaw_); + } + firstStub_ = newStub; + codeRaw_ = code->raw(); + + state_.trackAttached(); +} |