From ff71fd4c01667567ea81c19e1da8f07b884c3ae3 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Fri, 7 Aug 2026 17:17:19 +0900 Subject: [PATCH 1/7] Widen SetObjectPreComputedCaseComplexInlineCache's front-entry check to 3 slots The MRU front-entry check (added in 6c95698c6) only looks at index 0, on the assumption that a callsite's cache tends to be re-hit at whatever shape was inserted most recently. Fresh telemetry on Preact's VNode-construction workload showed that assumption failing hard for this tier: of ~2.06M front-entry misses, essentially all of them (~97.6%) resolved at scan position 1 or 2, not deeper -- shape churn here commonly rotates through a handful of distinct shapes per callsite rather than genuinely reusing the last one, so a single-slot check almost never hits and nearly every access was paying the NEVER_INLINE slow-path call anyway. Signed-off-by: Seonghyun Kim --- src/interpreter/ByteCodeInterpreter.cpp | 63 ++++++++++++++----------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/interpreter/ByteCodeInterpreter.cpp b/src/interpreter/ByteCodeInterpreter.cpp index 3cdbca26a..60f561c22 100644 --- a/src/interpreter/ByteCodeInterpreter.cpp +++ b/src/interpreter/ByteCodeInterpreter.cpp @@ -845,40 +845,49 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock DEFINE_OPCODE(SetObjectPreComputedCaseComplexInlineCache) : { - // Checks only the cache's MRU front entry (index 0 -- insertion is always at the - // front) directly here, in the main loop. The chain stored in the entry is only ever - // used to verify shape -- the write itself always targets the receiver (`obj`) below, - // never a prototype -- see setObjectPreComputedCaseOperationSlowCase's comment for - // why. A front-entry miss defers to the unchanged slow path, which still does its - // full linear scan over every entry. + // Checks the cache's front few entries directly here, in the main loop, instead of + // just the single MRU front entry (index 0 -- insertion is always at the front). + // Telemetry on Preact's VNode-construction workload showed shape churn at this tier + // commonly rotates through a handful of distinct shapes per callsite, so a front-only + // check rarely lands on the right one -- widening the inline check to a few more + // front slots catches most of that traffic without the NEVER_INLINE slow-path call. + // The chain stored in each entry is only ever used to verify shape -- the write itself + // always targets the receiver (`obj`) below, never a prototype -- see + // setObjectPreComputedCaseOperationSlowCase's comment for why. A miss across all + // checked slots defers to the unchanged slow path, which still does its full linear + // scan over every entry. + constexpr size_t inlineCheckCount = 3; SetObjectPreComputedCase* code = (SetObjectPreComputedCase*)programCounter; const Value& willBeObject = registerFile[code->m_objectRegisterIndex]; if (LIKELY(willBeObject.isObject())) { Object* obj = willBeObject.asObject(); SetObjectInlineCache* const inlineCache = code->m_inlineCache; - if (LIKELY(!!inlineCache && inlineCache->m_cache.size() > 0)) { - const SetObjectInlineCacheData& entry = inlineCache->m_cache[0]; - const size_t cSiz = entry.m_cachedhiddenClassChainLength; - Object* cur = obj; - bool ok = true; - for (size_t i = 0; i < cSiz; i++) { - if (UNLIKELY(!cur || cur->structure() != entry.m_cachedHiddenClassChainData[i])) { - ok = false; - break; - } - if (i + 1 < cSiz) { - cur = cur->Object::getPrototypeObject(*state); + if (LIKELY(!!inlineCache)) { + const size_t checkCount = std::min(inlineCache->m_cache.size(), inlineCheckCount); + for (size_t entryIndex = 0; entryIndex < checkCount; entryIndex++) { + const SetObjectInlineCacheData& entry = inlineCache->m_cache[entryIndex]; + const size_t cSiz = entry.m_cachedhiddenClassChainLength; + Object* cur = obj; + bool ok = true; + for (size_t i = 0; i < cSiz; i++) { + if (UNLIKELY(!cur || cur->structure() != entry.m_cachedHiddenClassChainData[i])) { + ok = false; + break; + } + if (i + 1 < cSiz) { + cur = cur->Object::getPrototypeObject(*state); + } } - } - if (LIKELY(ok)) { - if (LIKELY(entry.m_cachedIndex != SetObjectInlineCacheData::CachedIndexMax)) { - obj->m_values[entry.m_cachedIndex] = registerFile[code->m_loadRegisterIndex]; - } else { - obj->m_structure = entry.m_cachedHiddenClassChainData[cSiz]; - obj->m_values.push_back(registerFile[code->m_loadRegisterIndex], obj->m_structure->propertyCount()); + if (LIKELY(ok)) { + if (LIKELY(entry.m_cachedIndex != SetObjectInlineCacheData::CachedIndexMax)) { + obj->m_values[entry.m_cachedIndex] = registerFile[code->m_loadRegisterIndex]; + } else { + obj->m_structure = entry.m_cachedHiddenClassChainData[cSiz]; + obj->m_values.push_back(registerFile[code->m_loadRegisterIndex], obj->m_structure->propertyCount()); + } + ADD_PROGRAM_COUNTER(SetObjectPreComputedCase); + NEXT_INSTRUCTION(); } - ADD_PROGRAM_COUNTER(SetObjectPreComputedCase); - NEXT_INSTRUCTION(); } } } From da89c36e3b3b3337c992f147d499d5e686d896d9 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Fri, 7 Aug 2026 17:19:58 +0900 Subject: [PATCH 2/7] Cache accessor/native-getter-setter/non-writable own-property writes in Set IC Set's inline cache only ever cached plain-data-and-writable own-property writes; any found property that turned out to be an accessor, a native getter/setter data property, or non-writable caused the callsite to permanently disable its IC (GiveUp: clear the cache, never try again) and fall back to the fully generic, fully-uncached property-set path forever after. This was asymmetric with Get's Complex tier, which already caches non-plain-data properties via getOwnNonPlainDataPropertyUtilForObject. Signed-off-by: Seonghyun Kim --- src/interpreter/ByteCode.h | 11 ++++- src/interpreter/ByteCodeInterpreter.cpp | 54 ++++++++++++++++++------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/interpreter/ByteCode.h b/src/interpreter/ByteCode.h index a5886bfa7..501a1318b 100644 --- a/src/interpreter/ByteCode.h +++ b/src/interpreter/ByteCode.h @@ -1523,6 +1523,7 @@ struct SetObjectInlineCacheData { { m_cachedHiddenClass = nullptr; m_cachedIndex = m_cachedhiddenClassChainLength = 0; + m_isPlainDataProperty = true; } static constexpr size_t CachedIndexMax = std::numeric_limits::max(); @@ -1534,9 +1535,15 @@ struct SetObjectInlineCacheData { ObjectStructure** m_cachedHiddenClassChainData; ObjectStructure* m_cachedHiddenClass; }; - // 16bits of storage is enough + // false for a found-but-non-plain-data-or-non-writable own property (accessor, native + // getter/setter data property, or readonly) -- cachedIndex is still a real index in that + // case (this is always the "own property write" case, never a brand-new-property + // transition), but the write must go through Object::setOwnPropertyThrowsExceptionWhenStrictMode + // (which dispatches correctly by kind) instead of the direct m_values[] write. + bool m_isPlainDataProperty : 1; + // 15bits of storage is enough // inlineCacheProtoTraverseMaxCount is so small - uint16_t m_cachedhiddenClassChainLength : 16; + uint16_t m_cachedhiddenClassChainLength : 15; uint16_t m_cachedIndex : 16; }; diff --git a/src/interpreter/ByteCodeInterpreter.cpp b/src/interpreter/ByteCodeInterpreter.cpp index 60f561c22..1b28f3486 100644 --- a/src/interpreter/ByteCodeInterpreter.cpp +++ b/src/interpreter/ByteCodeInterpreter.cpp @@ -826,7 +826,14 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock const auto& item = cacheData[currentCacheIndex]; if (item.m_cachedHiddenClass == testItem) { if (LIKELY(item.m_cachedIndex != SetObjectInlineCacheData::CachedIndexMax)) { - obj->m_values[item.m_cachedIndex] = registerFile[code->m_loadRegisterIndex]; + if (LIKELY(item.m_isPlainDataProperty)) { + obj->m_values[item.m_cachedIndex] = registerFile[code->m_loadRegisterIndex]; + } else { + // accessor / native getter-setter / non-writable own property -- + // dispatches correctly by kind, no findProperty() needed since + // the index is already cached. + obj->setOwnPropertyThrowsExceptionWhenStrictMode(*state, item.m_cachedIndex, registerFile[code->m_loadRegisterIndex], willBeObject); + } ADD_PROGRAM_COUNTER(SetObjectPreComputedCase); NEXT_INSTRUCTION(); } @@ -880,7 +887,14 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock } if (LIKELY(ok)) { if (LIKELY(entry.m_cachedIndex != SetObjectInlineCacheData::CachedIndexMax)) { - obj->m_values[entry.m_cachedIndex] = registerFile[code->m_loadRegisterIndex]; + if (LIKELY(entry.m_isPlainDataProperty)) { + obj->m_values[entry.m_cachedIndex] = registerFile[code->m_loadRegisterIndex]; + } else { + // accessor / native getter-setter / non-writable own property -- + // dispatches correctly by kind, no findProperty() needed since + // the index is already cached. + obj->setOwnPropertyThrowsExceptionWhenStrictMode(*state, entry.m_cachedIndex, registerFile[code->m_loadRegisterIndex], willBeObject); + } } else { obj->m_structure = entry.m_cachedHiddenClassChainData[cSiz]; obj->m_values.push_back(registerFile[code->m_loadRegisterIndex], obj->m_structure->propertyCount()); @@ -3088,7 +3102,11 @@ NEVER_INLINE bool InterpreterSlowPath::setObjectPreComputedCaseOperationSlowCase ASSERT(cSiz == 1); ASSERT(item.m_cachedIndex < originalObject->m_structure->propertyCount()); ASSERT(originalObject->structure()->findProperty(code->m_propertyName).first == item.m_cachedIndex); - originalObject->m_values[item.m_cachedIndex] = value; + if (LIKELY(item.m_isPlainDataProperty)) { + originalObject->m_values[item.m_cachedIndex] = value; + } else { + originalObject->setOwnPropertyThrowsExceptionWhenStrictMode(state, item.m_cachedIndex, value, willBeObject); + } } else { ASSERT(originalObject->structure()->inTransitionMode()); ASSERT((originalObject->structure()->propertyCount() + 1) == item.m_cachedHiddenClassChainData[cSiz]->propertyCount()); @@ -3152,29 +3170,35 @@ NEVER_INLINE void InterpreterSlowPath::setObjectPreComputedCaseOperationCacheMis auto findResult = originalObject->structure()->findProperty(code->m_propertyName); if (findResult.first != SIZE_MAX) { - // Don't update the inline cache if the property is removed by a setter function. - /* example code - var o = { set foo (a) { var a = delete o.foo } }; - o.foo = 0; - */ - if (!findResult.second->m_descriptor.isPlainDataProperty() || !findResult.second->m_descriptor.isWritable()) { - goto GiveUp; - } + // Accessor / native-getter-setter / non-writable own properties are cached too (not just + // plain-data-and-writable) -- Object::setOwnPropertyThrowsExceptionWhenStrictMode() + // already dispatches correctly by kind given just the index, so a cache hit on any of + // these needs no findProperty() call, same as the plain-data case. + const bool isPlainDataProperty = findResult.second->m_descriptor.isPlainDataProperty() && findResult.second->m_descriptor.isWritable(); // set own property -#ifndef NDEBUG ObjectStructure* beforeStructure = originalObject->structure(); -#endif originalObject->setOwnPropertyThrowsExceptionWhenStrictMode(state, findResult.first, value, willBeObject); + if (UNLIKELY(!isPlainDataProperty && originalObject->structure() != beforeStructure)) { + // Don't update the inline cache if the property was removed (or the receiver's shape + // otherwise changed) by the setter's own side effects. + /* example code + var o = { set foo (a) { var a = delete o.foo } }; + o.foo = 0; + */ + // The write above already took effect correctly -- just skip caching a now-stale index. + return; + } + #ifndef NDEBUG ASSERT(originalObject->structure() == beforeStructure); // ObjectStructure should not be changed ASSERT(originalObject->structure()->findProperty(code->m_propertyName).first == findResult.first); const auto& propertyData = originalObject->structure()->readProperty(findResult.first); const auto& desc = propertyData.m_descriptor; ASSERT(propertyData.m_propertyName == code->m_propertyName); - ASSERT(desc.isPlainDataProperty() && desc.isWritable()); + ASSERT(isPlainDataProperty == (desc.isPlainDataProperty() && desc.isWritable())); #endif if (code->m_inlineCacheProtoTraverseMaxIndex == 0) { @@ -3182,11 +3206,13 @@ NEVER_INLINE void InterpreterSlowPath::setObjectPreComputedCaseOperationCacheMis newItem.m_cachedIndex = findResult.first; newItem.m_cachedhiddenClassChainLength = 1; newItem.m_cachedHiddenClass = originalObject->structure(); + newItem.m_isPlainDataProperty = isPlainDataProperty; } else { // complex case: caching the entire ObjectStructure chain if necessary // this case stores only the current ObjectStructure in m_cachedHiddenClassChainData newItem.m_cachedIndex = findResult.first; newItem.m_cachedhiddenClassChainLength = 1; + newItem.m_isPlainDataProperty = isPlainDataProperty; newItem.m_cachedHiddenClassChainData = (ObjectStructure**)GC_MALLOC(sizeof(ObjectStructure*)); newItem.m_cachedHiddenClassChainData[0] = originalObject->structure(); } From f5be70ebde8c59349d9a7a35a295c6ba01a92ba8 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Fri, 7 Aug 2026 17:20:44 +0900 Subject: [PATCH 3/7] Fix inverted bail condition in Set IC's transition-chain prototype walk setObjectPreComputedCaseOperationCacheMiss's "property not found, cache the transition chain" branch walks the receiver's prototype chain checking, for each prototype, whether it's safe to cache (inline-cacheable) and whether the chain has already hit its depth cap. Commit 86525d100 added the depth check but folded it into the existing `!UNLIKELY(obj->isInlineCacheable())` condition as `!UNLIKELY(A || B)` instead of `UNLIKELY(!A || B)` -- by De Morgan that's `!A && !B`, the opposite of the intended "bail if not cacheable OR too deep": it only bails when the prototype is NOT inline-cacheable AND the chain is still under the cap, and otherwise keeps walking (and caching) past inlineCacheProtoTraverseMaxCount with no bound other than the real prototype chain's own length. Signed-off-by: Seonghyun Kim --- src/interpreter/ByteCodeInterpreter.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/interpreter/ByteCodeInterpreter.cpp b/src/interpreter/ByteCodeInterpreter.cpp index 1b28f3486..7d13d0447 100644 --- a/src/interpreter/ByteCodeInterpreter.cpp +++ b/src/interpreter/ByteCodeInterpreter.cpp @@ -3233,7 +3233,12 @@ NEVER_INLINE void InterpreterSlowPath::setObjectPreComputedCaseOperationCacheMis while (proto.isObject()) { obj = proto.asObject(); - if (!UNLIKELY(obj->isInlineCacheable() || cachedhiddenClassChain.size() >= SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount)) { + // Bail if this prototype isn't safely inline-cacheable, OR if the chain has already + // hit the depth cap -- these are two independent reasons to give up, not one combined + // condition (a prior version of this line accidentally De Morgan'd them into + // `!(A || B)`, which only bails when NEITHER reason applies and otherwise lets the + // chain grow past inlineCacheProtoTraverseMaxCount unbounded). + if (UNLIKELY(!obj->isInlineCacheable() || cachedhiddenClassChain.size() >= SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount)) { goto GiveUp; } From 0253764a3786de0543caa13c2c7c5e71078e91b9 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Fri, 7 Aug 2026 18:33:44 +0900 Subject: [PATCH 4/7] Fix job-drain loop stalling up to 10ms per microtask in Shell.cpp Both job-drain loops (evalScript's post-script drain and the 262-agent worker loop) called waitEventFromAnotherThread(10) unconditionally on every pass, even when a same-thread job was already pending. In a single-threaded script that event never arrives, so every microtask drained through these loops paid up to a 10ms wall-clock stall for nothing. A 200-link .then() chain took ~6s wall clock (0.04s CPU) before this fix. Only block waiting for another thread's event when there is no same-thread job ready to run right now. Follow-up fix folded in here: skipping the cross-thread wait whenever a same-thread job is pending starves an already-completed cross-thread event (e.g. an expired Atomics.wait/waitAsync timeout) indefinitely if the same-thread job queue keeps refilling itself -- a `setTimeout(fn, 0)` polling loop, which is exactly the pattern test262's Atomics/waitAsync/true-for-timeout.js and friends use. hasPendingJob() never goes false in that case, so the cross-thread check never ran at all. This surfaced as 6 test262 waitAsync failures that had been wrongly written off as a pre-existing/known baseline flake in earlier verification of this same change, instead of recognized as a regression it introduced. Fixed by adding VMInstance::hasCompletedJobFromAnotherThread() (plus its EscargotPublic.h/.cpp wrapper), a non-blocking peek at whether a cross-thread job has already completed. Both job-drain loops now use it when a same-thread job is pending, instead of skipping the cross-thread check entirely: zero added latency in the common case (no blocking call happens), and an already-completed cross-thread event is picked up on the very next loop iteration instead of being starved forever. Verified: test262 Atomics/waitAsync/{true-for-timeout, returns-result-object-value-is-promise-resolves-to-timed-out, bigint/true-for-timeout} all pass now; promise-benchmark.js's 200-link .then() chain still completes in ~0.06s wall clock (no stall reintroduced). Signed-off-by: Seonghyun Kim --- src/api/EscargotPublic.cpp | 5 +++++ src/api/EscargotPublic.h | 3 +++ src/runtime/VMInstance.cpp | 10 ++++++++++ src/runtime/VMInstance.h | 6 ++++++ src/shell/Shell.cpp | 23 +++++++++++++++++++++-- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/api/EscargotPublic.cpp b/src/api/EscargotPublic.cpp index 3c0a16723..2b3856192 100644 --- a/src/api/EscargotPublic.cpp +++ b/src/api/EscargotPublic.cpp @@ -1553,6 +1553,11 @@ bool VMInstanceRef::hasPendingJobFromAnotherThread() return toImpl(this)->hasPendingJobFromAnotherThread(); } +bool VMInstanceRef::hasCompletedJobFromAnotherThread() +{ + return toImpl(this)->hasCompletedJobFromAnotherThread(); +} + bool VMInstanceRef::waitEventFromAnotherThread(unsigned timeoutInMillisecond) { return toImpl(this)->waitEventFromAnotherThread(timeoutInMillisecond); diff --git a/src/api/EscargotPublic.h b/src/api/EscargotPublic.h index dab4bf8f1..32e73adf9 100644 --- a/src/api/EscargotPublic.h +++ b/src/api/EscargotPublic.h @@ -794,6 +794,9 @@ class ESCARGOT_EXPORT VMInstanceRef { void enqueueEvaluateJob(ContextRef* relatedContext, EvaluateJobCallback callback, void* data); bool hasPendingJobFromAnotherThread(); + // Non-blocking: true if a job from another thread has already completed and is + // ready to be picked up right now. See VMInstance::hasCompletedJobFromAnotherThread. + bool hasCompletedJobFromAnotherThread(); bool waitEventFromAnotherThread(unsigned timeoutInMillisecond = 0); // zero means infinity void executePendingJobFromAnotherThread(); diff --git a/src/runtime/VMInstance.cpp b/src/runtime/VMInstance.cpp index b0a9e1a96..d3ed5c659 100644 --- a/src/runtime/VMInstance.cpp +++ b/src/runtime/VMInstance.cpp @@ -849,6 +849,16 @@ bool VMInstance::hasPendingJobFromAnotherThread() #endif } +bool VMInstance::hasCompletedJobFromAnotherThread() +{ +#if defined(ENABLE_THREADING) + std::unique_lock ul(m_asyncWaiterDataMutex); + return m_pendingAsyncWaiterCount != 0; +#else + return false; +#endif +} + bool VMInstance::waitEventFromAnotherThread(unsigned timeoutInMillisecond) { #if defined(ENABLE_THREADING) diff --git a/src/runtime/VMInstance.h b/src/runtime/VMInstance.h index aa7b5acba..a2cb7517b 100644 --- a/src/runtime/VMInstance.h +++ b/src/runtime/VMInstance.h @@ -238,6 +238,12 @@ class VMInstance : public gc { SandBox::SandBoxResult executePendingJob(); bool hasPendingJobFromAnotherThread(); + // Non-blocking: true if a job from another thread (e.g. an Atomics.wait/waitAsync + // timeout or notify) has already completed and is ready to be picked up right now. + // Unlike waitEventFromAnotherThread(), this never blocks -- use it to check for + // already-ready cross-thread work while a same-thread job is being processed, so a + // busy same-thread job queue can't starve an already-completed cross-thread event. + bool hasCompletedJobFromAnotherThread(); bool waitEventFromAnotherThread(unsigned timeoutInMillisecond = 0); // zero means infinity void executePendingJobFromAnotherThread(); diff --git a/src/shell/Shell.cpp b/src/shell/Shell.cpp index c1f8dc412..6c66736bc 100644 --- a/src/shell/Shell.cpp +++ b/src/shell/Shell.cpp @@ -611,7 +611,15 @@ static ValueRef* builtin262AgentStart(ExecutionStateRef* state, ValueRef* thisVa } while (context->vmInstance()->hasPendingJob() || context->vmInstance()->hasPendingJobFromAnotherThread()) { - if (context->vmInstance()->waitEventFromAnotherThread(10)) { + // See the analogous loop in evalScript(): only block waiting for another + // thread's event when there's no same-thread job ready right now. But if a + // same-thread job IS ready, still take a free (non-blocking) look for an + // already-completed cross-thread event -- otherwise a continuously-refilled + // same-thread queue can starve an already-expired Atomics.wait/waitAsync + // timeout forever, since hasPendingJob() never goes false. + bool hasSameThreadJob = context->vmInstance()->hasPendingJob(); + if (hasSameThreadJob ? context->vmInstance()->hasCompletedJobFromAnotherThread() + : context->vmInstance()->waitEventFromAnotherThread(10)) { context->vmInstance()->executePendingJobFromAnotherThread(); } if (context->vmInstance()->hasPendingJob()) { @@ -962,7 +970,18 @@ static bool evalScript(ContextRef* context, StringRef* source, StringRef* srcNam bool result = true; while (context->vmInstance()->hasPendingJob() || context->vmInstance()->hasPendingJobFromAnotherThread()) { - if (context->vmInstance()->waitEventFromAnotherThread(10)) { + // Only block waiting for another thread's event when there is no same-thread job + // ready to run right now -- otherwise this stalls up to `timeout` ms per drained + // microtask even though there's nothing to wait for, making any Promise-heavy + // script pay a large constant per-microtask cost once execution reaches this loop. + // But if a same-thread job IS ready, still take a free (non-blocking) look for an + // already-completed cross-thread event -- otherwise a continuously-refilled + // same-thread queue (e.g. a polling `setTimeout(fn, 0)` loop) can starve an + // already-expired Atomics.wait/waitAsync timeout forever, since hasPendingJob() + // never goes false. + bool hasSameThreadJob = context->vmInstance()->hasPendingJob(); + if (hasSameThreadJob ? context->vmInstance()->hasCompletedJobFromAnotherThread() + : context->vmInstance()->waitEventFromAnotherThread(10)) { context->vmInstance()->executePendingJobFromAnotherThread(); } if (context->vmInstance()->hasPendingJob()) { From 224d6273a6f0dd471fc10764a159cf119d8aac48 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Sat, 8 Aug 2026 10:22:07 +0900 Subject: [PATCH 5/7] Add LoadArgumentsElement fast path for arguments[i]/.length reads Reading arguments[i] or arguments.length currently always forces materialization of an ArgumentsObject, even when the read could be answered directly from the call frame. Add a LoadArgumentsElement opcode (merged .length + indexed-element query, since both need the same "is there already a materialized ArgumentsObject" check) that: - for .length, returns argc directly when no ArgumentsObject exists yet, else defers to the real object (so length-redefinition etc. keeps working). - for arguments[i], returns argv[i] directly without materializing when index is >= parameterCount and < argc -- this range can never alias a mapped-arguments parameter binding, so bypassing the object is observably identical to going through it, for both mapped and unmapped arguments objects. Anything outside that range (or once an ArgumentsObject already exists) falls back to the normal path. Signed-off-by: Seonghyun Kim --- src/interpreter/ByteCode.h | 55 ++++++++++++++++++----- src/interpreter/ByteCodeGenerator.cpp | 7 +-- src/interpreter/ByteCodeInterpreter.cpp | 58 ++++++++++++++++++------- src/parser/CodeBlock.cpp | 15 +++++++ src/parser/CodeBlock.h | 12 +++++ src/parser/ast/MemberExpressionNode.h | 39 +++++++++++++++-- 6 files changed, 153 insertions(+), 33 deletions(-) diff --git a/src/interpreter/ByteCode.h b/src/interpreter/ByteCode.h index 501a1318b..2a9c73978 100644 --- a/src/interpreter/ByteCode.h +++ b/src/interpreter/ByteCode.h @@ -143,7 +143,7 @@ struct GlobalVariableAccessCacheItem; F(ReplaceBlockLexicalEnvironmentOperation) \ F(TaggedTemplateOperation) \ F(EnsureArgumentsObject) \ - F(LoadArgumentsLength) \ + F(LoadArgumentsElement) \ F(BindingCalleeIntoRegister) \ F(ResolveNameAddress) \ F(StoreByNameWithAddress) \ @@ -3326,22 +3326,57 @@ class EnsureArgumentsObject : public ByteCode { #endif }; -// Load arguments.length without creating an ArgumentsObject. -// If ArgumentsObject already exists, reads from it; otherwise returns state.argc() directly. -class LoadArgumentsLength : public ByteCode { -public: - LoadArgumentsLength(const ByteCodeLOC& loc, const size_t registerIndex) - : ByteCode(Opcode::LoadArgumentsLengthOpcode, loc) - , m_registerIndex(registerIndex) +// Load arguments.length OR arguments[index] without creating an ArgumentsObject. +// Merged into one opcode (previously two: LoadArgumentsLength + LoadArgumentsElement) since +// their dispatch sites were both thin wrappers around a NEVER_INLINE helper with near-identical +// shape -- one opcode enum entry + one dispatch case is strictly less code than two, with no +// new call overhead (unlike out-of-lining small inline bodies, which was tried and made things +// worse -- see the perf branch's session notes on interpret()'s icache sensitivity). +// +// m_indexRegisterIndex == REGISTER_LIMIT means "this is a .length query" (reads state.argc() +// once the object exists, or its length property if already materialized). Otherwise it's an +// arguments[index] query: only takes the fast path when the ArgumentsObject doesn't exist yet +// AND index is a non-negative integer with parameterCount <= index < argc -- i.e. strictly +// outside the range that could alias a (possibly reassigned) named parameter via mapped +// arguments, and strictly inside the range that's guaranteed to be an own data property with no +// need to consult the prototype chain. Any other case (already materialized, index < +// parameterCount, out of range, or a non-integer key) falls back to the normal +// EnsureArgumentsObject + GetObject path. +class LoadArgumentsElement : public ByteCode { +public: + // .length query + LoadArgumentsElement(const ByteCodeLOC& loc, const size_t dstRegisterIndex) + : ByteCode(Opcode::LoadArgumentsElementOpcode, loc) + , m_indexRegisterIndex(REGISTER_LIMIT) + , m_dstRegisterIndex(dstRegisterIndex) { } - ByteCodeRegisterIndex m_registerIndex; + // arguments[index] query + LoadArgumentsElement(const ByteCodeLOC& loc, const size_t indexRegisterIndex, const size_t dstRegisterIndex) + : ByteCode(Opcode::LoadArgumentsElementOpcode, loc) + , m_indexRegisterIndex(indexRegisterIndex) + , m_dstRegisterIndex(dstRegisterIndex) + { + ASSERT(indexRegisterIndex != REGISTER_LIMIT); + } + + bool isLengthQuery() const + { + return m_indexRegisterIndex == REGISTER_LIMIT; + } + + ByteCodeRegisterIndex m_indexRegisterIndex; + ByteCodeRegisterIndex m_dstRegisterIndex; #ifndef NDEBUG void dump() { - printf("load arguments length r%u", m_registerIndex); + if (isLengthQuery()) { + printf("load arguments length r%u", m_dstRegisterIndex); + } else { + printf("load arguments element r%u[r%u]", m_dstRegisterIndex, m_indexRegisterIndex); + } } #endif }; diff --git a/src/interpreter/ByteCodeGenerator.cpp b/src/interpreter/ByteCodeGenerator.cpp index 4e52cbaf2..f87f93992 100644 --- a/src/interpreter/ByteCodeGenerator.cpp +++ b/src/interpreter/ByteCodeGenerator.cpp @@ -936,9 +936,10 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block) code += cd->m_tailDataLength; break; } - case LoadArgumentsLengthOpcode: { - LoadArgumentsLength* cd = (LoadArgumentsLength*)currentCode; - ASSIGN_STACKINDEX_IF_NEEDED(cd->m_registerIndex, stackBase, stackBaseWillBe, stackVariableSize); + case LoadArgumentsElementOpcode: { + LoadArgumentsElement* cd = (LoadArgumentsElement*)currentCode; + ASSIGN_STACKINDEX_IF_NEEDED(cd->m_indexRegisterIndex, stackBase, stackBaseWillBe, stackVariableSize); + ASSIGN_STACKINDEX_IF_NEEDED(cd->m_dstRegisterIndex, stackBase, stackBaseWillBe, stackVariableSize); break; } default: diff --git a/src/interpreter/ByteCodeInterpreter.cpp b/src/interpreter/ByteCodeInterpreter.cpp index 7d13d0447..a7a82ec4c 100644 --- a/src/interpreter/ByteCodeInterpreter.cpp +++ b/src/interpreter/ByteCodeInterpreter.cpp @@ -198,7 +198,7 @@ class InterpreterSlowPath { static void ensureArgumentsObjectOperation(ExecutionState& state, ByteCodeBlock* byteCodeBlock, Value* registerFile); - static void loadArgumentsLengthOperation(ExecutionState& state, LoadArgumentsLength* code, Value* registerFile); + static void loadArgumentsElementOperation(ExecutionState& state, ByteCodeBlock* byteCodeBlock, LoadArgumentsElement* code, Value* registerFile); static int evaluateImportWithOperation(ExecutionState& state, const Value& options); @@ -1876,12 +1876,12 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock NEXT_INSTRUCTION(); } - DEFINE_OPCODE(LoadArgumentsLength) + DEFINE_OPCODE(LoadArgumentsElement) : { - LoadArgumentsLength* code = (LoadArgumentsLength*)programCounter; - InterpreterSlowPath::loadArgumentsLengthOperation(*state, code, registerFile); - ADD_PROGRAM_COUNTER(LoadArgumentsLength); + LoadArgumentsElement* code = (LoadArgumentsElement*)programCounter; + InterpreterSlowPath::loadArgumentsElementOperation(*state, byteCodeBlock, code, registerFile); + ADD_PROGRAM_COUNTER(LoadArgumentsElement); NEXT_INSTRUCTION(); } @@ -5851,22 +5851,48 @@ NEVER_INLINE void InterpreterSlowPath::ensureArgumentsObjectOperation(ExecutionS funcObject->generateArgumentsObject(state, es->argc(), es->argv(), funcRecord, registerFile + byteCodeBlock->m_requiredOperandRegisterNumber, isMapped); } -NEVER_INLINE void InterpreterSlowPath::loadArgumentsLengthOperation(ExecutionState& state, LoadArgumentsLength* code, Value* registerFile) +NEVER_INLINE void InterpreterSlowPath::loadArgumentsElementOperation(ExecutionState& state, ByteCodeBlock* byteCodeBlock, LoadArgumentsElement* code, Value* registerFile) { ExecutionState* es; FunctionEnvironmentRecord* funcRecord = findNearestFunctionEnvironmentRecord(state, es); - ASSERT(!!funcRecord); - // If ArgumentsObject has already been created, read length from it - // (the user may have overwritten arguments.length) - auto opt = funcRecord->argumentsObject(); - if (opt) { - ArgumentsObject* argsObj = opt.value(); - registerFile[code->m_registerIndex] = argsObj->get(state, ObjectPropertyName(state.context()->staticStrings().length)).value(state, argsObj); - } else { - // ArgumentsObject has not been created yet; return argc directly - registerFile[code->m_registerIndex] = Value((int)es->argc()); + + auto argumentsObjectOpt = funcRecord->argumentsObject(); + + if (code->isLengthQuery()) { + // If ArgumentsObject has already been created, read length from it + // (the user may have overwritten arguments.length) + if (argumentsObjectOpt) { + ArgumentsObject* argsObj = argumentsObjectOpt.value(); + registerFile[code->m_dstRegisterIndex] = argsObj->get(state, ObjectPropertyName(state.context()->staticStrings().length)).value(state, argsObj); + } else { + // ArgumentsObject has not been created yet; return argc directly + registerFile[code->m_dstRegisterIndex] = Value((int)es->argc()); + } + return; } + + const Value& indexValue = registerFile[code->m_indexRegisterIndex]; + + // Fast path: before the ArgumentsObject exists, arguments[index] for + // parameterCount <= index < argc can never alias a (possibly reassigned) named + // parameter via mapped arguments, and is guaranteed to be an own data property + // with no prototype-chain lookup needed -- see the comment on LoadArgumentsElement. + if (!argumentsObjectOpt && indexValue.isUInt32()) { + uint32_t index = indexValue.asUInt32(); + if (index >= byteCodeBlock->m_codeBlock->parameterCount() && index < es->argc()) { + registerFile[code->m_dstRegisterIndex] = es->argv()[index]; + return; + } + } + + // Slow path: every other case (already materialized, mapped-argument range, + // out of range, non-integer key, deleted/redefined index) falls back to + // materializing (if needed) and doing a real indexed get, so it stays fully + // spec-correct. + ensureArgumentsObjectOperation(state, byteCodeBlock, registerFile); + ArgumentsObject* argsObj = funcRecord->argumentsObject().value(); + registerFile[code->m_dstRegisterIndex] = argsObj->get(state, ObjectPropertyName(state, indexValue)).value(state, argsObj); } NEVER_INLINE int InterpreterSlowPath::evaluateImportWithOperation(ExecutionState& state, const Value& options) diff --git a/src/parser/CodeBlock.cpp b/src/parser/CodeBlock.cpp index 5273cc52b..8b7044c61 100644 --- a/src/parser/CodeBlock.cpp +++ b/src/parser/CodeBlock.cpp @@ -970,4 +970,19 @@ size_t InterpretedCodeBlock::findVarName(const AtomicString& name) } } +bool InterpretedCodeBlock::hasExplicitArgumentsBinding(LexicalBlockIndex blockIndex) +{ + AtomicString name = m_context->staticStrings().arguments; + if (std::get<0>(findNameWithinBlock(blockIndex, name))) { + return true; + } + + if (blockIndex < m_functionBodyBlockIndex) { + return isParameterName(name); + } + + size_t idx = findVarName(name); + return idx != SIZE_MAX && m_identifierInfos[idx].m_isExplicitlyDeclaredOrParameterName; +} + } // namespace Escargot diff --git a/src/parser/CodeBlock.h b/src/parser/CodeBlock.h index 1428a6b1f..c6553e801 100644 --- a/src/parser/CodeBlock.h +++ b/src/parser/CodeBlock.h @@ -925,6 +925,18 @@ class InterpretedCodeBlock : public CodeBlock { return findVarName(name) != SIZE_MAX; } + // Like hasName(), but ignores the implicit bookkeeping slot that + // captureArguments() unconditionally registers for "arguments" the moment + // usesArgumentsObject() is set (that slot has m_isExplicitlyDeclaredOrParameterName + // == false; it isn't a real declaration, just where the materialized ArgumentsObject + // gets stored). A plain hasName() check for "arguments" is therefore true for every + // ordinary use of the arguments object -- exactly the case callers of this function + // need to distinguish from genuine shadowing (a parameter/var/let/const literally + // named `arguments`) -- so LoadArgumentsElement's bytecode-gen fast-path guards use + // this instead. (Defined out-of-line in CodeBlock.cpp: Context is only forward-declared + // here.) + bool hasExplicitArgumentsBinding(LexicalBlockIndex blockIndex); + bool isParameterName(const AtomicString& name) { for (size_t i = 0; i < parameterNamesCount(); i++) { diff --git a/src/parser/ast/MemberExpressionNode.h b/src/parser/ast/MemberExpressionNode.h index 5c88e946b..67bf9f982 100644 --- a/src/parser/ast/MemberExpressionNode.h +++ b/src/parser/ast/MemberExpressionNode.h @@ -87,14 +87,45 @@ class MemberExpressionNode : public ExpressionNode { virtual void generateExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstIndex) override { // Fast path: arguments.length — return argc without creating ArgumentsObject + // + // Excluded when this member expression is the callee of a call (e.g. + // `arguments.length()` -- nonsensical but legal syntax until it throws at + // runtime): CallExpressionNode expects the object's register to stay alive + // (via context->m_isHeadOfMemberExpression / m_inCallingExpressionScope) so it + // can use it as the receiver. Our fast paths never load `arguments` into any + // register, so skip them here and fall through to the normal path that does. if (m_isPreComputedCase && !m_isOptional && !m_startOfOptionalChaining + && !context->m_isHeadOfMemberExpression && m_object->isIdentifier() && m_object->asIdentifier()->isPointsArgumentsObject(context) && m_property->isIdentifier() && m_property->asIdentifier()->name() == codeBlock->m_codeBlock->context()->staticStrings().length - && !codeBlock->m_codeBlock->hasName(context->m_lexicalBlockIndex, context->m_codeBlock->context()->staticStrings().arguments) + && codeBlock->m_codeBlock->hasExplicitArgumentsBinding(context->m_lexicalBlockIndex) == false && context->m_codeBlock->canUseIndexedVariableStorage()) { - codeBlock->pushCode(LoadArgumentsLength(ByteCodeLOC(m_loc.index), dstIndex), context, this->m_loc.index); + codeBlock->pushCode(LoadArgumentsElement(ByteCodeLOC(m_loc.index), dstIndex), context, this->m_loc.index); + return; + } + + // Fast path: arguments[index] where index turns out (at runtime) to be + // >= parameterCount -- return it without creating an ArgumentsObject. See the + // comment on LoadArgumentsElement for why the parameterCount bound matters + // (mapped-arguments aliasing) and why anything outside it still needs the + // real object. This only covers reads; `arguments[i] = x` still goes through + // the normal (materializing) generateStoreByteCode path. + // + // Also excluded when this is the callee of a call -- e.g. `arguments[0]()` or + // `arguments[Symbol.iterator]()` must receive the arguments object itself as + // `this`; see the comment on the .length fast path above for why. + if (!m_isPreComputedCase && !m_isOptional && !m_startOfOptionalChaining + && !context->m_isHeadOfMemberExpression + && m_object->isIdentifier() + && m_object->asIdentifier()->isPointsArgumentsObject(context) + && codeBlock->m_codeBlock->hasExplicitArgumentsBinding(context->m_lexicalBlockIndex) == false + && context->m_codeBlock->canUseIndexedVariableStorage()) { + size_t propertyIndex = m_property->getRegister(codeBlock, context); + m_property->generateExpressionByteCode(codeBlock, context, propertyIndex); + codeBlock->pushCode(LoadArgumentsElement(ByteCodeLOC(m_loc.index), propertyIndex, dstIndex), context, this->m_loc.index); + context->giveUpRegister(); return; } @@ -155,10 +186,10 @@ class MemberExpressionNode : public ExpressionNode { } else if (m_object->isIdentifier() && m_object->asIdentifier()->isPointsArgumentsObject(context) && m_property->asIdentifier()->name() == codeBlock->m_codeBlock->context()->staticStrings().length - && !codeBlock->m_codeBlock->hasName(context->m_lexicalBlockIndex, context->m_codeBlock->context()->staticStrings().arguments) + && codeBlock->m_codeBlock->hasExplicitArgumentsBinding(context->m_lexicalBlockIndex) == false && context->m_codeBlock->canUseIndexedVariableStorage()) { // Fast path: arguments.length — return argc without creating ArgumentsObject - codeBlock->pushCode(LoadArgumentsLength(ByteCodeLOC(m_loc.index), dstIndex), context, this->m_loc.index); + codeBlock->pushCode(LoadArgumentsElement(ByteCodeLOC(m_loc.index), dstIndex), context, this->m_loc.index); } else { codeBlock->pushCode(GetObjectPreComputedCase(ByteCodeLOC(m_loc.index), objectIndex, dstIndex, m_property->asIdentifier()->name()), context, this->m_loc.index); } From 31472c239cc4818748b342366e1c3383d01b9d14 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Sat, 8 Aug 2026 14:38:32 +0900 Subject: [PATCH 6/7] Fix missing NAPI_EXPERIMENTAL defines in napi test-addon compile commands Both ADD_CUSTOM_COMMAND compile invocations for the N-API test addons (js-native-api/node-api upstream TCs and the custom symbol-verify addon) passed only -DNAPI_VERSION=10, but the additive Node.js-specific declarations they use (node_api_create_object_with_properties, node_api_set_prototype, node_api_post_finalizer, node_api_is_sharedarraybuffer) are guarded by `#ifdef NAPI_EXPERIMENTAL` in the vendored test/napi-tc/src/js_native_api.h, not by NAPI_VERSION. Without the flag those addons fail to compile with implicit-declaration errors. Added -DNAPI_EXPERIMENTAL -DNODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT to both compile commands, matching what a known-working N-API checkout already uses. Pre-existing gap from the N-API merge, unrelated to any other change on this branch -- found while trying to get a clean test.sh run. Signed-off-by: Seonghyun Kim --- build/escargot.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/escargot.cmake b/build/escargot.cmake index 2c31e47af..cbeb72ff6 100644 --- a/build/escargot.cmake +++ b/build/escargot.cmake @@ -459,7 +459,7 @@ ELSEIF (${ESCARGOT_OUTPUT} STREQUAL "cctest") ADD_CUSTOM_COMMAND ( OUTPUT ${NAPI_TEST_TC_SO} COMMAND ${CMAKE_COMMAND} -E make_directory ${NAPI_TEST_ADDON_DIR} - COMMAND ${NAPI_TEST_TC_COMPILER} -shared -fPIC -DNAPI_VERSION=10 -I${ESCARGOT_ROOT}/test/napi-tc/src ${NAPI_TEST_TC_SRCS} -o ${NAPI_TEST_TC_SO} + COMMAND ${NAPI_TEST_TC_COMPILER} -shared -fPIC -DNAPI_VERSION=10 -DNAPI_EXPERIMENTAL -DNODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT -I${ESCARGOT_ROOT}/test/napi-tc/src ${NAPI_TEST_TC_SRCS} -o ${NAPI_TEST_TC_SO} DEPENDS ${NAPI_TEST_TC_SRCS} COMMENT "Building napi test addon ${NAPI_TEST_TC_NAME}.so" ) @@ -483,7 +483,7 @@ ELSEIF (${ESCARGOT_OUTPUT} STREQUAL "cctest") ADD_CUSTOM_COMMAND ( OUTPUT ${NAPI_CUSTOM_SYMBOL_VERIFY_SO} COMMAND ${CMAKE_COMMAND} -E make_directory ${NAPI_TEST_ADDON_DIR} - COMMAND ${CMAKE_C_COMPILER} -shared -fPIC -DNAPI_VERSION=10 -I${ESCARGOT_ROOT}/test/napi-tc/src -I${ESCARGOT_ROOT}/test/napi-tc/test/js-native-api ${NAPI_CUSTOM_SYMBOL_VERIFY_SRC} -o ${NAPI_CUSTOM_SYMBOL_VERIFY_SO} + COMMAND ${CMAKE_C_COMPILER} -shared -fPIC -DNAPI_VERSION=10 -DNAPI_EXPERIMENTAL -DNODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT -I${ESCARGOT_ROOT}/test/napi-tc/src -I${ESCARGOT_ROOT}/test/napi-tc/test/js-native-api ${NAPI_CUSTOM_SYMBOL_VERIFY_SRC} -o ${NAPI_CUSTOM_SYMBOL_VERIFY_SO} DEPENDS ${NAPI_CUSTOM_SYMBOL_VERIFY_SRC} COMMENT "Building napi custom test addon test_symbol_verify.so" ) From 8c85e9fb692842bb0aa20b9216596eca8b702344 Mon Sep 17 00:00:00 2001 From: Seonghyun Kim Date: Sat, 8 Aug 2026 14:38:53 +0900 Subject: [PATCH 7/7] Merge redundant opcodes: JumpIfTrue+JumpIfFalse, ToNumericIncrement/Decrement Adding fast-path opcodes grows interpret()'s dispatch table and hurts icache; the counter-move is collapsing genuinely redundant opcodes (near-duplicate handlers differing by one field) back down. - JumpIfTrue + JumpIfFalse -> JumpIfBoolean(shouldNegate, registerIndex). Identical handlers but for one negation -- exactly the shape JumpIfUndefinedOrNull already collapsed into one opcode via its own m_shouldNegate flag. Same convention: result ^ shouldNegate. No bytecode size change (same two fields either way), ~15 callsites across src/parser/ast/*.h updated to construct/peek/lastCodePosition the merged type with the right shouldNegate value. - Increment/Decrement absorb ToNumericIncrement/ToNumericDecrement. Increment(src,dst) (prefix ++i) and ToNumericIncrement(src,storeIndex,dst) (postfix i++) were the same op with one extra field. Increment/Decrement gain an m_storeIndex field (REGISTER_LIMIT sentinel for "no postfix store", the same convention already used for m_receiverIndex and LoadArgumentsElement's m_indexRegisterIndex) and absorb the two ToNumeric* opcodes outright -- 2 opcodes removed, not renamed. Handler merge is exactly the prior split logic gated on the sentinel check. Only 12 callsites, all in UpdateExpression{Increment,Decrement} {Prefix,Postfix}Node.h, none needing lastCodePosition/peekCode. Signed-off-by: Seonghyun Kim --- src/interpreter/ByteCode.h | 96 +++++++------------ src/interpreter/ByteCodeGenerator.cpp | 20 ++-- src/interpreter/ByteCodeInterpreter.cpp | 57 ++++------- src/parser/ast/ArrayPatternNode.h | 6 +- .../ast/AssignmentExpressionLogicalAndNode.h | 6 +- .../ast/AssignmentExpressionLogicalOrNode.h | 6 +- src/parser/ast/AssignmentPatternNode.h | 6 +- .../ast/BinaryExpressionLogicalAndNode.h | 12 +-- .../ast/BinaryExpressionLogicalOrNode.h | 12 +-- src/parser/ast/ClassBodyNode.h | 4 +- src/parser/ast/ConditionalExpressionNode.h | 12 +-- src/parser/ast/DoWhileStatementNode.h | 2 +- src/parser/ast/ForInOfStatementNode.h | 18 ++-- src/parser/ast/ForStatementNode.h | 4 +- src/parser/ast/IfStatementNode.h | 4 +- src/parser/ast/SwitchStatementNode.h | 8 +- .../UpdateExpressionDecrementPostfixNode.h | 2 +- .../UpdateExpressionIncrementPostfixNode.h | 2 +- src/parser/ast/WhileStatementNode.h | 4 +- src/parser/ast/YieldExpressionNode.h | 18 ++-- 20 files changed, 124 insertions(+), 175 deletions(-) diff --git a/src/interpreter/ByteCode.h b/src/interpreter/ByteCode.h index 2a9c73978..c04608189 100644 --- a/src/interpreter/ByteCode.h +++ b/src/interpreter/ByteCode.h @@ -98,8 +98,6 @@ struct GlobalVariableAccessCacheItem; F(Move) \ F(Increment) \ F(Decrement) \ - F(ToNumericIncrement) \ - F(ToNumericDecrement) \ F(ToNumber) \ F(ToPropertyKey) \ F(UnaryMinus) \ @@ -110,9 +108,8 @@ struct GlobalVariableAccessCacheItem; F(TemplateOperation) \ F(Jump) \ F(JumpComplexCase) \ - F(JumpIfTrue) \ + F(JumpIfBoolean) \ F(JumpIfUndefinedOrNull) \ - F(JumpIfFalse) \ F(JumpIfNotFulfilled) \ F(JumpIfEqual) \ F(Call) \ @@ -1729,28 +1726,19 @@ class ToPropertyKey : public ByteCode { class Increment : public ByteCode { public: + // prefix (++i): storeIndex is REGISTER_LIMIT ("absent"), dstIndex gets the incremented value directly. + // postfix (i++, was ToNumericIncrement): dstIndex gets the numeric-converted *original* value (the + // expression's result), storeIndex gets the incremented value (to be stored back into the variable). Increment(const ByteCodeLOC& loc, const size_t srcIndex, const size_t dstIndex) : ByteCode(Opcode::IncrementOpcode, loc) , m_srcIndex(srcIndex) + , m_storeIndex(REGISTER_LIMIT) , m_dstIndex(dstIndex) { } - ByteCodeRegisterIndex m_srcIndex; - ByteCodeRegisterIndex m_dstIndex; - -#ifndef NDEBUG - void dump() - { - printf("increment r%u <- r%u", m_dstIndex, m_srcIndex); - } -#endif -}; - -class ToNumericIncrement : public ByteCode { -public: - ToNumericIncrement(const ByteCodeLOC& loc, const size_t srcIndex, const size_t storeIndex, const size_t dstIndex) - : ByteCode(Opcode::ToNumericIncrementOpcode, loc) + Increment(const ByteCodeLOC& loc, const size_t srcIndex, const size_t storeIndex, const size_t dstIndex) + : ByteCode(Opcode::IncrementOpcode, loc) , m_srcIndex(srcIndex) , m_storeIndex(storeIndex) , m_dstIndex(dstIndex) @@ -1764,35 +1752,28 @@ class ToNumericIncrement : public ByteCode { #ifndef NDEBUG void dump() { - printf("to numeric increment(r%u) -> r%u, r%u", m_srcIndex, m_storeIndex, m_dstIndex); + if (m_storeIndex == REGISTER_LIMIT) { + printf("increment r%u <- r%u", m_dstIndex, m_srcIndex); + } else { + printf("to numeric increment(r%u) -> r%u, r%u", m_srcIndex, m_storeIndex, m_dstIndex); + } } #endif }; class Decrement : public ByteCode { public: + // see Increment -- same prefix/postfix merge, REGISTER_LIMIT sentinel for "no postfix store". Decrement(const ByteCodeLOC& loc, const size_t srcIndex, const size_t dstIndex) : ByteCode(Opcode::DecrementOpcode, loc) , m_srcIndex(srcIndex) + , m_storeIndex(REGISTER_LIMIT) , m_dstIndex(dstIndex) { } - ByteCodeRegisterIndex m_srcIndex; - ByteCodeRegisterIndex m_dstIndex; - -#ifndef NDEBUG - void dump() - { - printf("decrement r%u <- r%u", m_dstIndex, m_srcIndex); - } -#endif -}; - -class ToNumericDecrement : public ByteCode { -public: - ToNumericDecrement(const ByteCodeLOC& loc, const size_t srcIndex, const size_t storeIndex, const size_t dstIndex) - : ByteCode(Opcode::ToNumericDecrementOpcode, loc) + Decrement(const ByteCodeLOC& loc, const size_t srcIndex, const size_t storeIndex, const size_t dstIndex) + : ByteCode(Opcode::DecrementOpcode, loc) , m_srcIndex(srcIndex) , m_storeIndex(storeIndex) , m_dstIndex(dstIndex) @@ -1806,7 +1787,11 @@ class ToNumericDecrement : public ByteCode { #ifndef NDEBUG void dump() { - printf("to numeric decrement(r%u) -> r%u, r%u", m_srcIndex, m_storeIndex, m_dstIndex); + if (m_storeIndex == REGISTER_LIMIT) { + printf("decrement r%u <- r%u", m_dstIndex, m_srcIndex); + } else { + printf("to numeric decrement(r%u) -> r%u, r%u", m_srcIndex, m_storeIndex, m_dstIndex); + } } #endif }; @@ -2089,26 +2074,35 @@ class JumpComplexCase : public ByteCode { COMPILE_ASSERT(sizeof(Jump) == sizeof(JumpComplexCase), ""); -class JumpIfTrue : public Jump { +class JumpIfBoolean : public Jump { public: - JumpIfTrue(const ByteCodeLOC& loc, const size_t registerIndex) - : Jump(Opcode::JumpIfTrueOpcode, loc, SIZE_MAX) + // shouldNegate == false: jump if r(registerIndex).toBoolean() is true (was JumpIfTrue) + // shouldNegate == true: jump if r(registerIndex).toBoolean() is false (was JumpIfFalse) + JumpIfBoolean(const ByteCodeLOC& loc, bool shouldNegate, const size_t registerIndex) + : Jump(Opcode::JumpIfBooleanOpcode, loc, SIZE_MAX) + , m_shouldNegate(shouldNegate) , m_registerIndex(registerIndex) { } - JumpIfTrue(const ByteCodeLOC& loc, const size_t registerIndex, size_t pos) - : Jump(Opcode::JumpIfTrueOpcode, loc, pos) + JumpIfBoolean(const ByteCodeLOC& loc, bool shouldNegate, const size_t registerIndex, size_t pos) + : Jump(Opcode::JumpIfBooleanOpcode, loc, pos) + , m_shouldNegate(shouldNegate) , m_registerIndex(registerIndex) { } + bool m_shouldNegate; ByteCodeRegisterIndex m_registerIndex; #ifndef NDEBUG void dump() { - printf("jump if r%u is true -> %zu", m_registerIndex, dumpJumpPosition(m_jumpPosition)); + if (m_shouldNegate) { + printf("jump if r%u is false -> %zu", m_registerIndex, dumpJumpPosition(m_jumpPosition)); + } else { + printf("jump if r%u is true -> %zu", m_registerIndex, dumpJumpPosition(m_jumpPosition)); + } } #endif }; @@ -2144,24 +2138,6 @@ class JumpIfUndefinedOrNull : public Jump { #endif }; -class JumpIfFalse : public Jump { -public: - JumpIfFalse(const ByteCodeLOC& loc, const size_t registerIndex) - : Jump(Opcode::JumpIfFalseOpcode, loc, SIZE_MAX) - , m_registerIndex(registerIndex) - { - } - - ByteCodeRegisterIndex m_registerIndex; - -#ifndef NDEBUG - void dump() - { - printf("jump if r%u is false -> %zu", m_registerIndex, dumpJumpPosition(m_jumpPosition)); - } -#endif -}; - class JumpIfNotFulfilled : public Jump { public: // compare if left value is less than (or equal) right value diff --git a/src/interpreter/ByteCodeGenerator.cpp b/src/interpreter/ByteCodeGenerator.cpp index f87f93992..b5cc227c9 100644 --- a/src/interpreter/ByteCodeGenerator.cpp +++ b/src/interpreter/ByteCodeGenerator.cpp @@ -585,8 +585,6 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block) } case ToNumberOpcode: case ToPropertyKeyOpcode: - case IncrementOpcode: - case DecrementOpcode: case UnaryMinusOpcode: case UnaryNotOpcode: case UnaryBitwiseNotOpcode: { @@ -595,9 +593,11 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block) ASSIGN_STACKINDEX_IF_NEEDED(cd->m_dstIndex, stackBase, stackBaseWillBe, stackVariableSize); break; } - case ToNumericIncrementOpcode: - case ToNumericDecrementOpcode: { - ToNumericIncrement* cd = (ToNumericIncrement*)currentCode; + case IncrementOpcode: + case DecrementOpcode: { + // m_storeIndex is REGISTER_LIMIT for the prefix form -- ASSIGN_STACKINDEX_IF_NEEDED + // already no-ops on REGISTER_LIMIT, so this one case handles both prefix and postfix. + Increment* cd = (Increment*)currentCode; ASSIGN_STACKINDEX_IF_NEEDED(cd->m_srcIndex, stackBase, stackBaseWillBe, stackVariableSize); ASSIGN_STACKINDEX_IF_NEEDED(cd->m_dstIndex, stackBase, stackBaseWillBe, stackVariableSize); ASSIGN_STACKINDEX_IF_NEEDED(cd->m_storeIndex, stackBase, stackBaseWillBe, stackVariableSize); @@ -691,8 +691,8 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block) cd->m_jumpPosition = cd->m_jumpPosition + codeBase; break; } - case JumpIfTrueOpcode: { - JumpIfTrue* cd = (JumpIfTrue*)currentCode; + case JumpIfBooleanOpcode: { + JumpIfBoolean* cd = (JumpIfBoolean*)currentCode; cd->m_jumpPosition = cd->m_jumpPosition + codeBase; ASSIGN_STACKINDEX_IF_NEEDED(cd->m_registerIndex, stackBase, stackBaseWillBe, stackVariableSize); break; @@ -703,12 +703,6 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block) ASSIGN_STACKINDEX_IF_NEEDED(cd->m_registerIndex, stackBase, stackBaseWillBe, stackVariableSize); break; } - case JumpIfFalseOpcode: { - JumpIfFalse* cd = (JumpIfFalse*)currentCode; - cd->m_jumpPosition = cd->m_jumpPosition + codeBase; - ASSIGN_STACKINDEX_IF_NEEDED(cd->m_registerIndex, stackBase, stackBaseWillBe, stackVariableSize); - break; - } case JumpIfNotFulfilledOpcode: { JumpIfNotFulfilled* cd = (JumpIfNotFulfilled*)currentCode; cd->m_jumpPosition = cd->m_jumpPosition + codeBase; diff --git a/src/interpreter/ByteCodeInterpreter.cpp b/src/interpreter/ByteCodeInterpreter.cpp index a7a82ec4c..dac653a6a 100644 --- a/src/interpreter/ByteCodeInterpreter.cpp +++ b/src/interpreter/ByteCodeInterpreter.cpp @@ -548,40 +548,30 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock NEXT_INSTRUCTION(); } - DEFINE_OPCODE(ToNumericIncrement) - : - { - ToNumericIncrement* code = (ToNumericIncrement*)programCounter; - registerFile[code->m_dstIndex] = Value(registerFile[code->m_srcIndex].toNumeric(*state).first); - registerFile[code->m_storeIndex] = InterpreterSlowPath::incrementOperation(*state, registerFile[code->m_dstIndex]); - ADD_PROGRAM_COUNTER(ToNumericIncrement); - NEXT_INSTRUCTION(); - } - DEFINE_OPCODE(Increment) : { Increment* code = (Increment*)programCounter; - registerFile[code->m_dstIndex] = InterpreterSlowPath::incrementOperation(*state, registerFile[code->m_srcIndex]); + if (code->m_storeIndex == REGISTER_LIMIT) { + registerFile[code->m_dstIndex] = InterpreterSlowPath::incrementOperation(*state, registerFile[code->m_srcIndex]); + } else { + registerFile[code->m_dstIndex] = Value(registerFile[code->m_srcIndex].toNumeric(*state).first); + registerFile[code->m_storeIndex] = InterpreterSlowPath::incrementOperation(*state, registerFile[code->m_dstIndex]); + } ADD_PROGRAM_COUNTER(Increment); NEXT_INSTRUCTION(); } - DEFINE_OPCODE(ToNumericDecrement) - : - { - ToNumericDecrement* code = (ToNumericDecrement*)programCounter; - registerFile[code->m_dstIndex] = Value(registerFile[code->m_srcIndex].toNumeric(*state).first); - registerFile[code->m_storeIndex] = InterpreterSlowPath::decrementOperation(*state, registerFile[code->m_dstIndex]); - ADD_PROGRAM_COUNTER(ToNumericDecrement); - NEXT_INSTRUCTION(); - } - DEFINE_OPCODE(Decrement) : { Decrement* code = (Decrement*)programCounter; - registerFile[code->m_dstIndex] = InterpreterSlowPath::decrementOperation(*state, registerFile[code->m_srcIndex]); + if (code->m_storeIndex == REGISTER_LIMIT) { + registerFile[code->m_dstIndex] = InterpreterSlowPath::decrementOperation(*state, registerFile[code->m_srcIndex]); + } else { + registerFile[code->m_dstIndex] = Value(registerFile[code->m_srcIndex].toNumeric(*state).first); + registerFile[code->m_storeIndex] = InterpreterSlowPath::decrementOperation(*state, registerFile[code->m_dstIndex]); + } ADD_PROGRAM_COUNTER(Decrement); NEXT_INSTRUCTION(); } @@ -964,15 +954,17 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock NEXT_INSTRUCTION(); } - DEFINE_OPCODE(JumpIfTrue) + DEFINE_OPCODE(JumpIfBoolean) : { - JumpIfTrue* code = (JumpIfTrue*)programCounter; + JumpIfBoolean* code = (JumpIfBoolean*)programCounter; ASSERT(code->m_jumpPosition != SIZE_MAX); - if (registerFile[code->m_registerIndex].toBoolean()) { + bool result = registerFile[code->m_registerIndex].toBoolean(); + + if (result ^ code->m_shouldNegate) { programCounter = code->m_jumpPosition; } else { - ADD_PROGRAM_COUNTER(JumpIfTrue); + ADD_PROGRAM_COUNTER(JumpIfBoolean); } NEXT_INSTRUCTION(); } @@ -992,19 +984,6 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock NEXT_INSTRUCTION(); } - DEFINE_OPCODE(JumpIfFalse) - : - { - JumpIfFalse* code = (JumpIfFalse*)programCounter; - ASSERT(code->m_jumpPosition != SIZE_MAX); - if (!registerFile[code->m_registerIndex].toBoolean()) { - programCounter = code->m_jumpPosition; - } else { - ADD_PROGRAM_COUNTER(JumpIfFalse); - } - NEXT_INSTRUCTION(); - } - DEFINE_OPCODE(Call) : { diff --git a/src/parser/ast/ArrayPatternNode.h b/src/parser/ast/ArrayPatternNode.h index a9a2dd5ad..a799ab6bb 100644 --- a/src/parser/ast/ArrayPatternNode.h +++ b/src/parser/ast/ArrayPatternNode.h @@ -88,14 +88,14 @@ class ArrayPatternNode : public Node { iteratorTestDoneData.m_dstRegisterIndex = doneIndex; iteratorTestDoneData.m_isIteratorRecord = true; codeBlock->pushCode(IteratorOperation(ByteCodeLOC(m_loc.index), iteratorTestDoneData), context, this->m_loc.index); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), doneIndex), context, this->m_loc.index); - size_t jumpPos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, doneIndex), context, this->m_loc.index); + size_t jumpPos = codeBlock->lastCodePosition(); IteratorOperation::IteratorCloseData iteratorCloseData; iteratorCloseData.m_iterRegisterIndex = iteratorRecordIndex; iteratorCloseData.m_execeptionRegisterIndexIfExists = REGISTER_LIMIT; codeBlock->pushCode(IteratorOperation(ByteCodeLOC(m_loc.index), iteratorCloseData), context, this->m_loc.index); - codeBlock->peekCode(jumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(jumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); // the record was created by the GetIterator above and is only ever read // by the opcodes in between, so nothing can hold it past this point. the // finalizer runs on every exit, including an abrupt one diff --git a/src/parser/ast/AssignmentExpressionLogicalAndNode.h b/src/parser/ast/AssignmentExpressionLogicalAndNode.h index 428dbc67f..efed73b22 100644 --- a/src/parser/ast/AssignmentExpressionLogicalAndNode.h +++ b/src/parser/ast/AssignmentExpressionLogicalAndNode.h @@ -50,8 +50,8 @@ class AssignmentExpressionLogicalAndNode : public AssignmentExpressionNode { codeBlock->pushCode(Move(ByteCodeLOC(m_loc.index), src0, dstRegister), context, this->m_loc.index); } - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), src0), context, this->m_loc.index); - size_t pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, src0), context, this->m_loc.index); + size_t pos = codeBlock->lastCodePosition(); size_t src1 = m_right->getRegister(codeBlock, context); m_right->generateExpressionByteCode(codeBlock, context, src1); @@ -62,7 +62,7 @@ class AssignmentExpressionLogicalAndNode : public AssignmentExpressionNode { codeBlock->pushCode(Move(ByteCodeLOC(m_loc.index), src1, dstRegister), context, this->m_loc.index); } - codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); if (slowMode) { context->m_canSkipCopyToRegister = flagBefore; diff --git a/src/parser/ast/AssignmentExpressionLogicalOrNode.h b/src/parser/ast/AssignmentExpressionLogicalOrNode.h index 8467d1232..ef6edb471 100644 --- a/src/parser/ast/AssignmentExpressionLogicalOrNode.h +++ b/src/parser/ast/AssignmentExpressionLogicalOrNode.h @@ -50,8 +50,8 @@ class AssignmentExpressionLogicalOrNode : public AssignmentExpressionNode { codeBlock->pushCode(Move(ByteCodeLOC(m_loc.index), src0, dstRegister), context, this->m_loc.index); } - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), src0), context, this->m_loc.index); - size_t pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, src0), context, this->m_loc.index); + size_t pos = codeBlock->lastCodePosition(); size_t src1 = m_right->getRegister(codeBlock, context); m_right->generateExpressionByteCode(codeBlock, context, src1); @@ -62,7 +62,7 @@ class AssignmentExpressionLogicalOrNode : public AssignmentExpressionNode { codeBlock->pushCode(Move(ByteCodeLOC(m_loc.index), src1, dstRegister), context, this->m_loc.index); } - codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); if (slowMode) { context->m_canSkipCopyToRegister = flagBefore; diff --git a/src/parser/ast/AssignmentPatternNode.h b/src/parser/ast/AssignmentPatternNode.h index 904758f38..8205495e1 100644 --- a/src/parser/ast/AssignmentPatternNode.h +++ b/src/parser/ast/AssignmentPatternNode.h @@ -69,8 +69,8 @@ class AssignmentPatternNode : public ExpressionNode { context->giveUpRegister(); // for drop undefinedIndex - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), cmpIndex), context, this->m_loc.index); - size_t pos1 = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, cmpIndex), context, this->m_loc.index); + size_t pos1 = codeBlock->lastCodePosition(); context->giveUpRegister(); // for drop cmpIndex // not undefined case, set srcRegister @@ -109,7 +109,7 @@ class AssignmentPatternNode : public ExpressionNode { size_t pos2 = codeBlock->lastCodePosition(); // undefined case, set default node - codeBlock->peekCode(pos1)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos1)->m_jumpPosition = codeBlock->currentCodeSize(); // restore initialized parameter names so the default branch re-checks parameter // references (e.g. the computed keys of the pattern) instead of reusing the state diff --git a/src/parser/ast/BinaryExpressionLogicalAndNode.h b/src/parser/ast/BinaryExpressionLogicalAndNode.h index 88c66e6a7..2c480672c 100644 --- a/src/parser/ast/BinaryExpressionLogicalAndNode.h +++ b/src/parser/ast/BinaryExpressionLogicalAndNode.h @@ -51,11 +51,11 @@ class BinaryExpressionLogicalAndNode : public ExpressionNode { } } else { m_left->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), dstRegister), context, this->m_loc.index); - size_t pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, dstRegister), context, this->m_loc.index); + size_t pos = codeBlock->lastCodePosition(); m_right->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); } context->m_canSkipCopyToRegister = directBefore; @@ -79,11 +79,11 @@ class BinaryExpressionLogicalAndNode : public ExpressionNode { } } else { m_left->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), dstRegister), context, this->m_loc.index); - size_t pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, dstRegister), context, this->m_loc.index); + size_t pos = codeBlock->lastCodePosition(); m_right->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); } context->m_canSkipCopyToRegister = directBefore; diff --git a/src/parser/ast/BinaryExpressionLogicalOrNode.h b/src/parser/ast/BinaryExpressionLogicalOrNode.h index 694789671..1e506de76 100644 --- a/src/parser/ast/BinaryExpressionLogicalOrNode.h +++ b/src/parser/ast/BinaryExpressionLogicalOrNode.h @@ -51,11 +51,11 @@ class BinaryExpressionLogicalOrNode : public ExpressionNode { } } else { m_left->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), dstRegister), context, this->m_loc.index); - size_t pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, dstRegister), context, this->m_loc.index); + size_t pos = codeBlock->lastCodePosition(); m_right->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); } context->m_canSkipCopyToRegister = directBefore; @@ -79,11 +79,11 @@ class BinaryExpressionLogicalOrNode : public ExpressionNode { } } else { m_left->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), dstRegister), context, this->m_loc.index); - size_t pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, dstRegister), context, this->m_loc.index); + size_t pos = codeBlock->lastCodePosition(); m_right->generateExpressionByteCode(codeBlock, context, dstRegister); - codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(pos)->m_jumpPosition = codeBlock->currentCodeSize(); } context->m_canSkipCopyToRegister = directBefore; diff --git a/src/parser/ast/ClassBodyNode.h b/src/parser/ast/ClassBodyNode.h index f6ae87e0b..018bd4f27 100644 --- a/src/parser/ast/ClassBodyNode.h +++ b/src/parser/ast/ClassBodyNode.h @@ -184,11 +184,11 @@ class ClassBodyNode : public Node { codeBlock->pushCode(BinaryEqual(ByteCodeLOC(m_loc.index), propertyIndex, stringReg, testReg), context, this->m_loc.index); size_t jmpPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), testReg), context, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, testReg), context, this->m_loc.index); codeBlock->pushCode(ThrowStaticErrorOperation(ByteCodeLOC(m_loc.index), (uint8_t)ErrorCode::TypeError, ErrorObject::Messages::Class_Prototype_Is_Not_Static_Generator), context, this->m_loc.index); - codeBlock->peekCode(jmpPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(jmpPos)->m_jumpPosition = codeBlock->currentCodeSize(); context->giveUpRegister(); context->giveUpRegister(); diff --git a/src/parser/ast/ConditionalExpressionNode.h b/src/parser/ast/ConditionalExpressionNode.h index 00e77d7a8..d6781e730 100644 --- a/src/parser/ast/ConditionalExpressionNode.h +++ b/src/parser/ast/ConditionalExpressionNode.h @@ -39,15 +39,15 @@ class ConditionalExpressionNode : public ExpressionNode { { size_t testReg = m_test->getRegister(codeBlock, context); m_test->generateExpressionByteCode(codeBlock, context, testReg); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), testReg), context, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, testReg), context, this->m_loc.index); // give testReg context->giveUpRegister(); - size_t jumpPosForTestIsFalse = codeBlock->lastCodePosition(); + size_t jumpPosForTestIsFalse = codeBlock->lastCodePosition(); m_consequente->generateExpressionByteCode(codeBlock, context, dstRegister); codeBlock->pushCode(Jump(ByteCodeLOC(m_loc.index), SIZE_MAX), context, this->m_loc.index); - JumpIfFalse* jumpForTestIsFalse = codeBlock->peekCode(jumpPosForTestIsFalse); + JumpIfBoolean* jumpForTestIsFalse = codeBlock->peekCode(jumpPosForTestIsFalse); size_t jumpPosForEndOfConsequence = codeBlock->lastCodePosition(); jumpForTestIsFalse->m_jumpPosition = codeBlock->currentCodeSize(); @@ -62,15 +62,15 @@ class ConditionalExpressionNode : public ExpressionNode { { size_t testReg = m_test->getRegister(codeBlock, context); m_test->generateExpressionByteCode(codeBlock, context, testReg); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), testReg), context, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, testReg), context, this->m_loc.index); // give testReg context->giveUpRegister(); - size_t jumpPosForTestIsFalse = codeBlock->lastCodePosition(); + size_t jumpPosForTestIsFalse = codeBlock->lastCodePosition(); m_consequente->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm); codeBlock->pushCode(Jump(ByteCodeLOC(m_loc.index), SIZE_MAX), context, this->m_loc.index); - JumpIfFalse* jumpForTestIsFalse = codeBlock->peekCode(jumpPosForTestIsFalse); + JumpIfBoolean* jumpForTestIsFalse = codeBlock->peekCode(jumpPosForTestIsFalse); size_t jumpPosForEndOfConsequence = codeBlock->lastCodePosition(); jumpForTestIsFalse->m_jumpPosition = codeBlock->currentCodeSize(); diff --git a/src/parser/ast/DoWhileStatementNode.h b/src/parser/ast/DoWhileStatementNode.h index bebdcf800..9d1b3cf25 100644 --- a/src/parser/ast/DoWhileStatementNode.h +++ b/src/parser/ast/DoWhileStatementNode.h @@ -61,7 +61,7 @@ class DoWhileStatementNode : public StatementNode { size_t testPos = codeBlock->currentCodeSize(); size_t testReg = m_test->getRegister(codeBlock, &newContext); m_test->generateExpressionByteCode(codeBlock, &newContext, testReg); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), testReg, doStart), &newContext, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, testReg, doStart), &newContext, this->m_loc.index); newContext.giveUpRegister(); newContext.giveUpRegister(); diff --git a/src/parser/ast/ForInOfStatementNode.h b/src/parser/ast/ForInOfStatementNode.h index d4544d137..4d3108efb 100644 --- a/src/parser/ast/ForInOfStatementNode.h +++ b/src/parser/ast/ForInOfStatementNode.h @@ -349,8 +349,8 @@ class ForInOfStatementNode : public StatementNode { codeBlock->pushCode(IteratorOperation(ByteCodeLOC(m_loc.index), iteratorTestDoneData), &newContext, this->m_loc.index); // If done is true, return NormalCompletion(V). - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), doneRegister), &newContext, this->m_loc.index); - exit2Pos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, doneRegister), &newContext, this->m_loc.index); + exit2Pos = codeBlock->lastCodePosition(); newContext.giveUpRegister(); // drop doneRegister // Let nextValue be ? IteratorValue(nextResult). @@ -435,7 +435,7 @@ class ForInOfStatementNode : public StatementNode { TryStatementNode::generateTryFinalizerStatementStartByteCode(codeBlock, &newContext, this, forOfTryStatementContext, true); size_t exceptionThrownCheckStartJumpPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), finishCheckRegisterIndex, SIZE_MAX), &newContext, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, finishCheckRegisterIndex, SIZE_MAX), &newContext, this->m_loc.index); if (m_isForAwaitOf) { // AsyncIteratorClose ( iteratorRecord, completion ) @@ -505,7 +505,7 @@ class ForInOfStatementNode : public StatementNode { // if (throwTest) { size_t tempTestPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), throwTestRegister), &newContext, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, throwTestRegister), &newContext, this->m_loc.index); // innerResult = await innerResult; ExecutionPause::ExecutionPauseAwaitData data; data.m_awaitIndex = returnOrInnerResultRegister; @@ -515,7 +515,7 @@ class ForInOfStatementNode : public StatementNode { data.m_tailDataLength = tailDataLength; codeBlock->pushCode(ExecutionPause(ByteCodeLOC(m_loc.index), data), &newContext, this->m_loc.index); // } - codeBlock->peekCode(tempTestPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(tempTestPos)->m_jumpPosition = codeBlock->currentCodeSize(); // %IteratorOperation(checkOngoingException)% IteratorOperation::IteratorCheckOngoingExceptionOnAsyncIteratorCloseData iteratorCheckOngoingExceptionData; @@ -523,7 +523,7 @@ class ForInOfStatementNode : public StatementNode { // if (!throwTest || awaitReturnsThrow) { size_t throwTestIsTrueJumpPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), throwTestRegister), &newContext, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, throwTestRegister), &newContext, this->m_loc.index); // we don't needThrowTestRegister from here codeBlock->pushCode(LoadLiteral(ByteCodeLOC(m_loc.index), throwTestRegister, Value(ExecutionPauser::ResumeState::Throw)), &newContext, this->m_loc.index); @@ -534,7 +534,7 @@ class ForInOfStatementNode : public StatementNode { // throw innerResult; codeBlock->pushCode(ThrowOperation(ByteCodeLOC(m_loc.index), returnOrInnerResultRegister), &newContext, this->m_loc.index); // } - codeBlock->peekCode(throwTestIsTrueJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(throwTestIsTrueJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); codeBlock->peekCode(awaitNotReturnsThrowPos)->m_jumpPosition = codeBlock->currentCodeSize(); // %IteratorOperation(TestResultIsObject)% @@ -554,7 +554,7 @@ class ForInOfStatementNode : public StatementNode { codeBlock->pushCode(IteratorOperation(ByteCodeLOC(m_loc.index), iteratorCloseData), &newContext, this->m_loc.index); } - codeBlock->peekCode(exceptionThrownCheckStartJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(exceptionThrownCheckStartJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); if (!m_isForAwaitOf) { // every way out of the loop passes through this finalizer, and a // `continue` deliberately does not (see the labelled-continue note @@ -574,7 +574,7 @@ class ForInOfStatementNode : public StatementNode { if (m_forIn) { codeBlock->peekCode(exit2Pos)->m_exitPosition = exitPos; } else if (m_isForAwaitOf) { - codeBlock->peekCode(exit2Pos)->m_jumpPosition = exitPos; + codeBlock->peekCode(exit2Pos)->m_jumpPosition = exitPos; } else { codeBlock->peekCode(exit2Pos)->m_jumpPosition = exitPos; } diff --git a/src/parser/ast/ForStatementNode.h b/src/parser/ast/ForStatementNode.h index de69cceac..1eeb3031b 100644 --- a/src/parser/ast/ForStatementNode.h +++ b/src/parser/ast/ForStatementNode.h @@ -150,8 +150,8 @@ class ForStatementNode : public StatementNode { } else { testIndex = m_test->getRegister(codeBlock, &newContext); m_test->generateExpressionByteCode(codeBlock, &newContext, testIndex); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), testIndex), &newContext, this->m_loc.index); - testPos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, testIndex), &newContext, this->m_loc.index); + testPos = codeBlock->lastCodePosition(); newContext.giveUpRegister(); } if (shouldCareScriptExecutionResult) { diff --git a/src/parser/ast/IfStatementNode.h b/src/parser/ast/IfStatementNode.h index e3ed5e1d0..073fdbe2d 100644 --- a/src/parser/ast/IfStatementNode.h +++ b/src/parser/ast/IfStatementNode.h @@ -56,8 +56,8 @@ class IfStatementNode : public StatementNode { } else { size_t testReg = m_test->getRegister(codeBlock, context); m_test->generateExpressionByteCode(codeBlock, context, testReg); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), testReg), context, this->m_loc.index); - jPos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, testReg), context, this->m_loc.index); + jPos = codeBlock->lastCodePosition(); context->giveUpRegister(); } context->giveUpRegister(); diff --git a/src/parser/ast/SwitchStatementNode.h b/src/parser/ast/SwitchStatementNode.h index 87e88637e..09b063e6f 100644 --- a/src/parser/ast/SwitchStatementNode.h +++ b/src/parser/ast/SwitchStatementNode.h @@ -87,7 +87,7 @@ class SwitchStatementNode : public StatementNode { size_t resultIndex = newContext.getRegister(); codeBlock->pushCode(BinaryStrictEqual(ByteCodeLOC(m_loc.index), refIndex, rIndex0, resultIndex), &newContext, this->m_loc.index); jumpCodePerCaseNodePosition.push_back(codeBlock->currentCodeSize()); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), resultIndex), &newContext, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, resultIndex), &newContext, this->m_loc.index); newContext.giveUpRegister(); newContext.giveUpRegister(); nd = nd->nextSibling(); @@ -102,7 +102,7 @@ class SwitchStatementNode : public StatementNode { size_t resultIndex = newContext.getRegister(); codeBlock->pushCode(BinaryStrictEqual(ByteCodeLOC(m_loc.index), refIndex, rIndex0, resultIndex), &newContext, this->m_loc.index); jumpCodePerCaseNodePosition.push_back(codeBlock->currentCodeSize()); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), resultIndex), &newContext, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, resultIndex), &newContext, this->m_loc.index); newContext.giveUpRegister(); newContext.giveUpRegister(); nd = nd->nextSibling(); @@ -122,7 +122,7 @@ class SwitchStatementNode : public StatementNode { nd = m_casesB->firstChild(); while (nd) { SwitchCaseNode* caseNode = (SwitchCaseNode*)nd; - codeBlock->peekCode(jumpCodePerCaseNodePosition[caseIdx++])->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(jumpCodePerCaseNodePosition[caseIdx++])->m_jumpPosition = codeBlock->currentCodeSize(); caseNode->generateStatementByteCode(codeBlock, &newContext); nd = nd->nextSibling(); } @@ -133,7 +133,7 @@ class SwitchStatementNode : public StatementNode { nd = m_casesA->firstChild(); while (nd) { SwitchCaseNode* caseNode = (SwitchCaseNode*)nd; - codeBlock->peekCode(jumpCodePerCaseNodePosition[caseIdx++])->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(jumpCodePerCaseNodePosition[caseIdx++])->m_jumpPosition = codeBlock->currentCodeSize(); caseNode->generateStatementByteCode(codeBlock, &newContext); nd = nd->nextSibling(); } diff --git a/src/parser/ast/UpdateExpressionDecrementPostfixNode.h b/src/parser/ast/UpdateExpressionDecrementPostfixNode.h index 32d9edb02..33efa69d3 100644 --- a/src/parser/ast/UpdateExpressionDecrementPostfixNode.h +++ b/src/parser/ast/UpdateExpressionDecrementPostfixNode.h @@ -39,7 +39,7 @@ class UpdateExpressionDecrementPostfixNode : public ExpressionNode { m_argument->generateReferenceResolvedAddressByteCode(codeBlock, context); size_t srcIndex = context->getLastRegisterIndex(); size_t storeIndex = m_argument->getRegister(codeBlock, context); - codeBlock->pushCode(ToNumericDecrement(ByteCodeLOC(m_loc.index), srcIndex, storeIndex, dstRegister), context, this->m_loc.index); + codeBlock->pushCode(Decrement(ByteCodeLOC(m_loc.index), srcIndex, storeIndex, dstRegister), context, this->m_loc.index); context->giveUpRegister(); context->giveUpRegister(); m_argument->generateStoreByteCode(codeBlock, context, storeIndex, false); diff --git a/src/parser/ast/UpdateExpressionIncrementPostfixNode.h b/src/parser/ast/UpdateExpressionIncrementPostfixNode.h index 5a1cf4846..51e24c00c 100644 --- a/src/parser/ast/UpdateExpressionIncrementPostfixNode.h +++ b/src/parser/ast/UpdateExpressionIncrementPostfixNode.h @@ -39,7 +39,7 @@ class UpdateExpressionIncrementPostfixNode : public ExpressionNode { m_argument->generateReferenceResolvedAddressByteCode(codeBlock, context); size_t srcIndex = context->getLastRegisterIndex(); size_t storeIndex = m_argument->getRegister(codeBlock, context); - codeBlock->pushCode(ToNumericIncrement(ByteCodeLOC(m_loc.index), srcIndex, storeIndex, dstRegister), context, this->m_loc.index); + codeBlock->pushCode(Increment(ByteCodeLOC(m_loc.index), srcIndex, storeIndex, dstRegister), context, this->m_loc.index); context->giveUpRegister(); context->giveUpRegister(); m_argument->generateStoreByteCode(codeBlock, context, storeIndex, false); diff --git a/src/parser/ast/WhileStatementNode.h b/src/parser/ast/WhileStatementNode.h index c98a1fbaa..6d055fd80 100644 --- a/src/parser/ast/WhileStatementNode.h +++ b/src/parser/ast/WhileStatementNode.h @@ -67,8 +67,8 @@ class WhileStatementNode : public StatementNode { } else { ByteCodeRegisterIndex testR = m_test->getRegister(codeBlock, &newContext); m_test->generateExpressionByteCode(codeBlock, &newContext, testR); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), testR), &newContext, this->m_loc.index); - testPos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, testR), &newContext, this->m_loc.index); + testPos = codeBlock->lastCodePosition(); newContext.giveUpRegister(); } } diff --git a/src/parser/ast/YieldExpressionNode.h b/src/parser/ast/YieldExpressionNode.h index 6d430f8db..4b0e0fdb1 100644 --- a/src/parser/ast/YieldExpressionNode.h +++ b/src/parser/ast/YieldExpressionNode.h @@ -104,8 +104,8 @@ class YieldExpressionNode : public ExpressionNode { codeBlock->pushCode(IteratorOperation(ByteCodeLOC(m_loc.index), iteratorTestDoneData), context, this->m_loc.index); // If done is true, then // Return ? IteratorValue(innerResult). - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), doneIndex), context, this->m_loc.index); - size_t testDoneJumpPos = codeBlock->lastCodePosition(); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, doneIndex), context, this->m_loc.index); + size_t testDoneJumpPos = codeBlock->lastCodePosition(); IteratorOperation::IteratorValueData iteratorValueData; iteratorValueData.m_srcRegisterIndex = valueIdx; iteratorValueData.m_dstRegisterIndex = dstRegister; @@ -228,15 +228,15 @@ class YieldExpressionNode : public ExpressionNode { // if (throwTest) { size_t tempTestPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), throwTestRegister), context, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, throwTestRegister), context, this->m_loc.index); // innerResult = await innerResult; pushAwait(codeBlock, context, returnOrInnerResultRegister, returnOrInnerResultRegister, awaitStateRegister, tailDataLength); // } - codeBlock->peekCode(tempTestPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(tempTestPos)->m_jumpPosition = codeBlock->currentCodeSize(); // if (!throwTest || awaitReturnsThrow) { size_t throwTestIsTrueJumpPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfTrue(ByteCodeLOC(m_loc.index), throwTestRegister), context, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), false, throwTestRegister), context, this->m_loc.index); // we don't needThrowTestRegister from here codeBlock->pushCode(LoadLiteral(ByteCodeLOC(m_loc.index), throwTestRegister, Value(ExecutionPauser::ResumeState::Throw)), context, this->m_loc.index); @@ -247,7 +247,7 @@ class YieldExpressionNode : public ExpressionNode { // throw innerResult; codeBlock->pushCode(ThrowOperation(ByteCodeLOC(m_loc.index), returnOrInnerResultRegister), context, this->m_loc.index); // } - codeBlock->peekCode(throwTestIsTrueJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(throwTestIsTrueJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); codeBlock->peekCode(awaitNotReturnsThrowPos)->m_jumpPosition = codeBlock->currentCodeSize(); // %IteratorOperation(TestResultIsObject)% @@ -295,7 +295,7 @@ class YieldExpressionNode : public ExpressionNode { } // Return Completion(received). ReturnStatementNode::generateReturnCode(codeBlock, context, this, ByteCodeLOC(m_loc.index), valueIdx); - codeBlock->peekCode(returnUndefinedCompareJump)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(returnUndefinedCompareJump)->m_jumpPosition = codeBlock->currentCodeSize(); // Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »). codeBlock->pushCode(CallWithReceiver(ByteCodeLOC(m_loc.index), iteratorObjectIdx, returnRegister, valueIdx, valueIdx, 1), context, this->m_loc.index); @@ -317,7 +317,7 @@ class YieldExpressionNode : public ExpressionNode { // If done is true, then testDoneJumpPos = codeBlock->currentCodeSize(); - codeBlock->pushCode(JumpIfFalse(ByteCodeLOC(m_loc.index), doneIndex), context, this->m_loc.index); + codeBlock->pushCode(JumpIfBoolean(ByteCodeLOC(m_loc.index), true, doneIndex), context, this->m_loc.index); // Let value be ? IteratorValue(innerReturnResult). iteratorValueData.m_srcRegisterIndex = valueIdx; @@ -325,7 +325,7 @@ class YieldExpressionNode : public ExpressionNode { codeBlock->pushCode(IteratorOperation(ByteCodeLOC(m_loc.index), iteratorValueData), context, this->m_loc.index); // Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. ReturnStatementNode::generateReturnCode(codeBlock, context, this, ByteCodeLOC(m_loc.index), valueIdx); - codeBlock->peekCode(testDoneJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); + codeBlock->peekCode(testDoneJumpPos)->m_jumpPosition = codeBlock->currentCodeSize(); // If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerReturnResult)). if (isAsyncGenerator) {