Skip to content

Instantly share code, notes, and snippets.

@jdahlin
Created March 23, 2026 23:04
Show Gist options
  • Select an option

  • Save jdahlin/010f06b9077381ffac927b4ce3679b7a to your computer and use it in GitHub Desktop.

Select an option

Save jdahlin/010f06b9077381ffac927b4ce3679b7a to your computer and use it in GitHub Desktop.
diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp
index 5b617d25d2..c87122aa21 100644
--- a/Libraries/LibJS/Bytecode/Interpreter.cpp
+++ b/Libraries/LibJS/Bytecode/Interpreter.cpp
@@ -158,6 +158,8 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, GC::Ptr<Environ
// 9. Suspend the currently running execution context.
// 10. Push scriptContext onto the execution context stack; scriptContext is now the running execution context.
+ if (executable)
+ script_context->initialize_constant_values(*executable);
TRY(vm.push_execution_context(*script_context, {}));
// 13. If result.[[Type]] is normal, then
@@ -292,6 +294,8 @@ ExecutionContext* Interpreter::push_inline_frame(
}
callee_context->private_environment = callee_function.m_private_environment;
+ callee_context->initialize_constant_values(callee_executable);
+
// Fast-path push onto execution context stack (avoids Vector::append growth check preventing inlining).
auto& ec_stack = vm().execution_context_stack();
if (ec_stack.size() < ec_stack.capacity()) [[likely]]
@@ -309,12 +313,7 @@ ExecutionContext* Interpreter::push_inline_frame(
// in cross-realm calls (e.g. iframe <-> parent).
callee_context->executable = callee_executable;
- // Copy constants (memcpy avoids aliasing issues with the scalar loop).
- auto* values = callee_context->registers_and_constants_and_locals_and_arguments();
- if (auto count = callee_executable.constants.size())
- memcpy(values + callee_executable.registers_and_locals_count,
- callee_executable.constants.data(),
- count * sizeof(Value));
+ auto* values = callee_context->registers_and_constants_and_locals_and_arguments_span().data();
// Set this value register.
values[Register::this_value().index()] = callee_context->this_value.value_or(js_special_empty_value());
@@ -830,10 +829,10 @@ ThrowCompletionOr<Value> Interpreter::run_executable(ExecutionContext& context,
// NOTE: This is how we "push" a new execution context onto the interpreter stack.
TemporaryChange restore_running_execution_context { m_running_execution_context, &context };
- context.executable = executable;
-
VERIFY(executable.registers_and_locals_count + executable.constants.size() == executable.registers_and_locals_and_constants_count);
VERIFY(executable.registers_and_locals_and_constants_count <= context.registers_and_constants_and_locals_and_arguments_span().size());
+ ASSERT(!context.executable || context.executable == &executable);
+ context.executable = executable;
// NOTE: We only copy the `this` value from ExecutionContext if it's not already set.
// If we are re-entering an async/generator context, the `this` value
@@ -842,12 +841,7 @@ ThrowCompletionOr<Value> Interpreter::run_executable(ExecutionContext& context,
if (reg(Register::this_value()).is_special_empty_value())
reg(Register::this_value()) = context.this_value.value_or(js_special_empty_value());
- // NB: Layout is [registers | locals | constants | arguments], so constants start after registers+locals.
- auto* values = context.registers_and_constants_and_locals_and_arguments();
- if (auto count = executable.constants.size())
- memcpy(values + executable.registers_and_locals_count,
- executable.constants.data(),
- count * sizeof(Value));
+ auto* values = context.registers_and_constants_and_locals_and_arguments_span().data();
run_bytecode(entry_point);
diff --git a/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Libraries/LibJS/Runtime/AbstractOperations.cpp
index 3a5506ee63..d2e6e782dd 100644
--- a/Libraries/LibJS/Runtime/AbstractOperations.cpp
+++ b/Libraries/LibJS/Runtime/AbstractOperations.cpp
@@ -761,6 +761,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
eval_context->private_environment = private_environment;
// 29. Push evalContext onto the execution context stack; evalContext is now the running execution context.
+ eval_context->initialize_constant_values(*executable);
TRY(vm.push_execution_context(*eval_context, {}));
// NOTE: We use a ScopeGuard to automatically pop the execution context when any of the `TRY`s below return a throw completion.
diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
index c81fed1c48..6ef70f26af 100644
--- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
+++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
@@ -364,6 +364,7 @@ void ECMAScriptFunctionObject::prepare_for_ordinary_call(VM& vm, ExecutionContex
// 11. If callerContext is not already suspended, suspend callerContext.
// 12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
+ callee_context.initialize_constant_values(*bytecode_executable());
// NOTE: We don't check for stack overflow here. The bytecode interpreter will do it anyway
// when entering the function we're about to call.
diff --git a/Libraries/LibJS/Runtime/ExecutionContext.cpp b/Libraries/LibJS/Runtime/ExecutionContext.cpp
index 32f758c512..9d3bc13a01 100644
--- a/Libraries/LibJS/Runtime/ExecutionContext.cpp
+++ b/Libraries/LibJS/Runtime/ExecutionContext.cpp
@@ -118,13 +118,23 @@ NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
copy->this_value = this_value;
copy->executable = executable;
copy->passed_argument_count = passed_argument_count;
- copy->registers_and_constants_and_locals_and_arguments_count = registers_and_constants_and_locals_and_arguments_count;
for (size_t i = 0; i < registers_and_constants_and_locals_and_arguments_count; ++i)
copy->registers_and_constants_and_locals_and_arguments()[i] = registers_and_constants_and_locals_and_arguments()[i];
- copy->argument_count = argument_count;
return copy;
}
+void ExecutionContext::initialize_constant_values(Bytecode::Executable& executable_)
+{
+ executable = executable_;
+ VERIFY(executable_.registers_and_locals_and_constants_count + argument_count == registers_and_constants_and_locals_and_arguments_count);
+ if (auto count = executable_.constants.size()) {
+ auto* values = registers_and_constants_and_locals_and_arguments();
+ memcpy(values + executable_.registers_and_locals_count,
+ executable_.constants.data(),
+ count * sizeof(Value));
+ }
+}
+
void ExecutionContext::visit_edges(Cell::Visitor& visitor)
{
visitor.visit(function);
diff --git a/Libraries/LibJS/Runtime/ExecutionContext.h b/Libraries/LibJS/Runtime/ExecutionContext.h
index 12ab059dbd..6c8d96d767 100644
--- a/Libraries/LibJS/Runtime/ExecutionContext.h
+++ b/Libraries/LibJS/Runtime/ExecutionContext.h
@@ -112,6 +112,8 @@ public:
return registers_and_constants_and_locals_and_arguments() + (registers_and_constants_and_locals_and_arguments_count - argument_count);
}
+ void initialize_constant_values(Bytecode::Executable&);
+
// Non-standard: Inline frame linkage for the bytecode interpreter.
// When a JS-to-JS call is inlined in the dispatch loop, these fields
// allow the Return handler to restore the caller's frame.
diff --git a/Libraries/LibJS/Runtime/NativeFunction.cpp b/Libraries/LibJS/Runtime/NativeFunction.cpp
index 659d9771c2..0f98a2e069 100644
--- a/Libraries/LibJS/Runtime/NativeFunction.cpp
+++ b/Libraries/LibJS/Runtime/NativeFunction.cpp
@@ -149,6 +149,8 @@ ThrowCompletionOr<Value> NativeFunction::internal_call(ExecutionContext& callee_
// </8.> --------------------------------------------------------------------------
// 9. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
+ if (is<NativeJavaScriptBackedFunction>(*this))
+ callee_context.initialize_constant_values(as<NativeJavaScriptBackedFunction>(*this).bytecode_executable());
TRY(vm.push_execution_context(callee_context, {}));
// 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to the specification of F. thisArgument is the this value, argumentsList provides the named parameters, and the NewTarget value is undefined.
@@ -200,6 +202,8 @@ ThrowCompletionOr<GC::Ref<Object>> NativeFunction::internal_construct(ExecutionC
// </8.> --------------------------------------------------------------------------
// 9. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
+ if (is<NativeJavaScriptBackedFunction>(*this))
+ callee_context.initialize_constant_values(as<NativeJavaScriptBackedFunction>(*this).bytecode_executable());
TRY(vm.push_execution_context(callee_context, {}));
// 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to the specification of F. The this value is uninitialized, argumentsList provides the named parameters, and newTarget provides the NewTarget value.
diff --git a/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Libraries/LibJS/Runtime/ShadowRealm.cpp
index 2ee1644974..59ed666e38 100644
--- a/Libraries/LibJS/Runtime/ShadowRealm.cpp
+++ b/Libraries/LibJS/Runtime/ShadowRealm.cpp
@@ -146,6 +146,7 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, Value source, Realm&
auto variable_environment = eval_context->variable_environment;
// 9. Push evalContext onto the execution context stack; evalContext is now the running execution context.
+ eval_context->initialize_constant_values(*executable);
TRY(vm.push_execution_context(*eval_context, {}));
// 10. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, null, strictEval)).
diff --git a/Libraries/LibJS/SourceTextModule.cpp b/Libraries/LibJS/SourceTextModule.cpp
index c96c3b98a0..f351738c9a 100644
--- a/Libraries/LibJS/SourceTextModule.cpp
+++ b/Libraries/LibJS/SourceTextModule.cpp
@@ -539,6 +539,8 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<Promise
// 8. Suspend the currently running execution context.
// NOTE: Done by the push of execution context in steps below.
+ if (m_executable)
+ module_context->initialize_constant_values(*m_executable);
// 9. If module.[[HasTLA]] is false, then
if (!m_has_top_level_await) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment