LibJS: Cache dynamic environment coordinates

Dynamic environment binding opcodes lost the old coordinate warmup.
They were split away from the static coordinate opcodes. Hot closures
and eval-sensitive functions then resolved the same binding by name on
every execution, which regressed JS benchmark throughput badly.

Give each dynamic environment opcode a per-executable coordinate cache
slot. The cache keeps the bytecode stream immutable while letting both
interpreters take a direct declarative environment fast path after the
first lookup. Keep the existing eval invalidation behavior and only warm
caches for declarative-only chains so with environments continue to
observe object shadowing.

Reject cached bytecode that uses the no-cache sentinel for dynamic
environment coordinate cache operands, since execution indexes those
cache arrays unconditionally.

Rebaseline bytecode expectations for the instruction size changes. Add
coverage for with-object shadowing across repeated dynamic lookups and
for rejecting corrupt dynamic environment cache indices.
This commit is contained in:
Andreas Kling 2026-05-19 11:22:33 +02:00 committed by Andreas Kling
parent 73e7cacd78
commit 4ac744082b
21 changed files with 379 additions and 35 deletions

View file

@ -275,11 +275,17 @@ i64 asm_try_inline_call(VM*, u32 pc);
i64 asm_try_put_by_id_cache(VM*, u32 pc);
i64 asm_try_get_by_id_cache(VM*, u32 pc);
i64 asm_slow_path_initialize_lexical_binding(VM*, u32 pc);
i64 asm_slow_path_dynamic_initialize_lexical_binding(VM*, u32 pc);
i64 asm_slow_path_dynamic_initialize_variable_binding(VM*, u32 pc);
i64 asm_try_get_by_value_typed_array(VM*, u32 pc);
i64 asm_slow_path_get_initialized_binding(VM*, u32 pc);
i64 asm_slow_path_dynamic_get_initialized_binding(VM*, u32 pc);
i64 asm_slow_path_get_binding(VM*, u32 pc);
i64 asm_slow_path_dynamic_get_binding(VM*, u32 pc);
i64 asm_slow_path_set_lexical_binding(VM*, u32 pc);
i64 asm_slow_path_dynamic_set_lexical_binding(VM*, u32 pc);
i64 asm_slow_path_dynamic_set_variable_binding(VM*, u32 pc);
i64 asm_slow_path_bitwise_not(VM*, u32 pc);
i64 asm_slow_path_unary_plus(VM*, u32 pc);
i64 asm_slow_path_throw_if_tdz(VM*, u32 pc);
@ -288,6 +294,7 @@ i64 asm_slow_path_throw_if_nullish(VM*, u32 pc);
i64 asm_slow_path_loosely_equals(VM*, u32 pc);
i64 asm_slow_path_loosely_inequals(VM*, u32 pc);
i64 asm_slow_path_get_callee_and_this(VM*, u32 pc);
i64 asm_slow_path_dynamic_get_callee_and_this(VM*, u32 pc);
i64 asm_try_put_by_value_typed_array(VM*, u32 pc);
i64 asm_slow_path_get_private_by_id(VM*, u32 pc);
i64 asm_slow_path_put_private_by_id(VM*, u32 pc);
@ -619,6 +626,11 @@ i64 asm_slow_path_get_initialized_binding(VM* vm, u32 pc)
return slow_path_throwing<Op::GetInitializedBinding>(*vm, pc);
}
i64 asm_slow_path_dynamic_get_initialized_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicGetInitializedBinding>(*vm, pc);
}
i64 asm_slow_path_loosely_equals(VM* vm, u32 pc)
{
return slow_path_throwing<Op::LooselyEquals>(*vm, pc);
@ -634,6 +646,11 @@ i64 asm_slow_path_get_callee_and_this(VM* vm, u32 pc)
return slow_path_throwing<Op::GetCalleeAndThisFromEnvironment>(*vm, pc);
}
i64 asm_slow_path_dynamic_get_callee_and_this(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicGetCalleeAndThisFromEnvironment>(*vm, pc);
}
i64 asm_slow_path_postfix_increment(VM* vm, u32 pc)
{
return slow_path_throwing<Op::PostfixIncrement>(*vm, pc);
@ -1009,16 +1026,41 @@ i64 asm_slow_path_get_binding(VM* vm, u32 pc)
return slow_path_throwing<Op::GetBinding>(*vm, pc);
}
i64 asm_slow_path_dynamic_get_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicGetBinding>(*vm, pc);
}
i64 asm_slow_path_initialize_lexical_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::InitializeLexicalBinding>(*vm, pc);
}
i64 asm_slow_path_dynamic_initialize_lexical_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicInitializeLexicalBinding>(*vm, pc);
}
i64 asm_slow_path_dynamic_initialize_variable_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicInitializeVariableBinding>(*vm, pc);
}
i64 asm_slow_path_set_lexical_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::SetLexicalBinding>(*vm, pc);
}
i64 asm_slow_path_dynamic_set_lexical_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicSetLexicalBinding>(*vm, pc);
}
i64 asm_slow_path_dynamic_set_variable_binding(VM* vm, u32 pc)
{
return slow_path_throwing<Op::DynamicSetVariableBinding>(*vm, pc);
}
i64 asm_slow_path_bitwise_not(VM* vm, u32 pc)
{
return slow_path_throwing<Op::BitwiseNot>(*vm, pc);

View file

@ -460,6 +460,39 @@ macro walk_env_chain(m_cache_field, target_env, bind_index, fail_label)
assert_nonzero target_env
end
# Walk the environment chain using a runtime EnvironmentCoordinate cache.
# The cache lives in Executable::environment_coordinate_caches so bytecode can
# remain immutable while dynamic binding lookups still warm up.
macro walk_cached_env_chain(m_environment_field, target_env, bind_index, fail_label)
temp exe, caches, coord_addr, hops, sentinel, flag
load32 coord_addr, [pb, pc, m_cache]
mul coord_addr, coord_addr, ENVIRONMENT_COORDINATE_SIZE
load64 exe, [exec_ctx, EXECUTION_CONTEXT_EXECUTABLE]
load64 caches, [exe, EXECUTABLE_ENVIRONMENT_COORDINATE_CACHES_DATA]
add coord_addr, caches
load_pair32 hops, bind_index, [coord_addr, ENVIRONMENT_COORDINATE_HOPS], [coord_addr, ENVIRONMENT_COORDINATE_INDEX]
mov sentinel, ENVIRONMENT_COORDINATE_INVALID
branch_eq hops, sentinel, fail_label
load64 target_env, [exec_ctx, m_environment_field]
assert_nonzero target_env
branch_zero hops, .walk_done
.walk_loop:
load8 flag, [target_env, ENVIRONMENT_DECLARATIVE]
branch_zero flag, fail_label
load8 flag, [target_env, ENVIRONMENT_SCREWED_BY_EVAL]
branch_nonzero flag, fail_label
load64 target_env, [target_env, ENVIRONMENT_OUTER]
branch_zero target_env, fail_label
sub hops, 1
branch_nonzero hops, .walk_loop
.walk_done:
assert_nonzero target_env
load8 flag, [target_env, ENVIRONMENT_DECLARATIVE]
branch_zero flag, fail_label
load8 flag, [target_env, ENVIRONMENT_SCREWED_BY_EVAL]
branch_nonzero flag, fail_label
end
# Pop an inline frame and resume the caller without bouncing through C++.
# The asm-managed JS-to-JS call fast path currently only inlines Call, never
# CallConstruct, so caller_is_construct is always false for asm-managed inline
@ -1060,6 +1093,16 @@ handler GetBinding
call_slow_path asm_slow_path_get_binding
end
handler DynamicGetBinding
temp env, idx, binding_values, value, empty
walk_cached_env_chain EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT, env, idx, .slow
check_binding_initialized env, idx, binding_values, value, empty, .slow
store_operand m_dst, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_get_binding
end
# Inline environment chain walk + direct binding value load.
handler GetInitializedBinding
temp env, idx, binding_values, value
@ -1071,6 +1114,16 @@ handler GetInitializedBinding
call_slow_path asm_slow_path_get_initialized_binding
end
handler DynamicGetInitializedBinding
temp env, idx, binding_values, value
walk_cached_env_chain EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT, env, idx, .slow
load_binding_value env, idx, binding_values, value
store_operand m_dst, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_get_initialized_binding
end
# Inline environment chain walk + initialize binding.
handler InitializeLexicalBinding
temp env, idx, value, binding_values
@ -1082,6 +1135,26 @@ handler InitializeLexicalBinding
call_slow_path asm_slow_path_initialize_lexical_binding
end
handler DynamicInitializeLexicalBinding
temp env, idx, value, binding_values
walk_cached_env_chain EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT, env, idx, .slow
load_operand value, m_src
store_binding_value env, idx, binding_values, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_initialize_lexical_binding
end
handler DynamicInitializeVariableBinding
temp env, idx, value, binding_values
walk_cached_env_chain EXECUTION_CONTEXT_VARIABLE_ENVIRONMENT, env, idx, .slow
load_operand value, m_src
store_binding_value env, idx, binding_values, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_initialize_variable_binding
end
# Inline environment chain walk + set mutable binding.
handler SetLexicalBinding
temp env, idx, flag, value, binding_values, flags, empty
@ -1098,6 +1171,36 @@ handler SetLexicalBinding
call_slow_path asm_slow_path_set_lexical_binding
end
handler DynamicSetLexicalBinding
temp env, idx, flag, value, binding_values, flags, empty
walk_cached_env_chain EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT, env, idx, .slow
check_binding_initialized env, idx, binding_values, value, empty, .slow
# Check mutable
load_binding_flags env, idx, flags, flag
and flag, BINDING_FLAG_MUTABLE
branch_zero flag, .slow
load_operand value, m_src
store_binding_value env, idx, binding_values, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_set_lexical_binding
end
handler DynamicSetVariableBinding
temp env, idx, flag, value, binding_values, flags, empty
walk_cached_env_chain EXECUTION_CONTEXT_VARIABLE_ENVIRONMENT, env, idx, .slow
check_binding_initialized env, idx, binding_values, value, empty, .slow
# Check mutable
load_binding_flags env, idx, flags, flag
and flag, BINDING_FLAG_MUTABLE
branch_zero flag, .slow
load_operand value, m_src
store_binding_value env, idx, binding_values, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_set_variable_binding
end
# x++: save original to dst first, then increment src in-place.
handler PostfixIncrement
temp value, tag, int_value, dst
@ -1426,6 +1529,19 @@ handler GetCalleeAndThisFromEnvironment
call_slow_path asm_slow_path_get_callee_and_this
end
handler DynamicGetCalleeAndThisFromEnvironment
temp env, idx, binding_values, value, empty
walk_cached_env_chain EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT, env, idx, .slow
check_binding_initialized env, idx, binding_values, value, empty, .slow
store_operand m_callee, value
# this = undefined for cached declarative environment references.
mov value, UNDEFINED_SHIFTED
store_operand m_this_value, value
dispatch_next
.slow:
call_slow_path asm_slow_path_dynamic_get_callee_and_this
end
handler LooselyEquals
temp lhs, rhs
load_operand lhs, m_lhs

View file

@ -124,6 +124,7 @@ int main()
EMIT_OFFSET(EXECUTABLE_CONSTANTS, Executable, constants);
EMIT_OFFSET(EXECUTABLE_PROPERTY_LOOKUP_CACHES, Executable, property_lookup_caches);
EMIT_OFFSET(EXECUTABLE_GLOBAL_VARIABLE_CACHES, Executable, global_variable_caches);
EMIT_OFFSET(EXECUTABLE_ENVIRONMENT_COORDINATE_CACHES, Executable, environment_coordinate_caches);
EMIT_OFFSET(EXECUTABLE_REGISTERS_AND_LOCALS_COUNT, Executable, registers_and_locals_count);
EMIT_OFFSET(EXECUTABLE_REGISTERS_AND_LOCALS_AND_CONSTANTS_COUNT, Executable, registers_and_locals_and_constants_count);
EMIT_OFFSET(EXECUTABLE_ASM_CONSTANTS_SIZE, Executable, asm_constants_size);
@ -211,6 +212,7 @@ int main()
outln("const EXECUTABLE_BYTECODE_DATA = {}", offsetof(Executable, bytecode) + InstructionStream::data_member_offset());
outln("const EXECUTABLE_PROPERTY_LOOKUP_CACHES_DATA = {}", offsetof(Executable, property_lookup_caches) + vec_data);
outln("const EXECUTABLE_GLOBAL_VARIABLE_CACHES_DATA = {}", offsetof(Executable, global_variable_caches) + vec_data);
outln("const EXECUTABLE_ENVIRONMENT_COORDINATE_CACHES_DATA = {}", offsetof(Executable, environment_coordinate_caches) + vec_data);
outln("const EXECUTABLE_CONSTANTS_DATA = {}", offsetof(Executable, constants) + vec_data);
outln("const EXECUTABLE_CONSTANTS_SIZE = {}", offsetof(Executable, constants) + vec_size);
outln("const OBJECT_PROPERTY_ITERATOR_CACHE_DATA_PROPERTY_VALUES_DATA = {}", offsetof(ObjectPropertyIteratorCacheData, m_property_values) + vec_data);
@ -322,6 +324,7 @@ int main()
// Environment layout
outln("\n# Environment layout");
EMIT_OFFSET(ENVIRONMENT_SCREWED_BY_EVAL, Environment, m_permanently_screwed_by_eval);
EMIT_OFFSET(ENVIRONMENT_DECLARATIVE, Environment, m_declarative);
EMIT_OFFSET(ENVIRONMENT_OUTER, Environment, m_outer_environment);
// DeclarativeEnvironment binding storage layout
@ -342,6 +345,7 @@ int main()
outln("const ENVIRONMENT_COORDINATE_HOPS = {}", offsetof(EnvironmentCoordinate, hops));
outln("const ENVIRONMENT_COORDINATE_INDEX = {}", offsetof(EnvironmentCoordinate, index));
outln("const ENVIRONMENT_COORDINATE_INVALID = 0x{:X}", EnvironmentCoordinate::invalid_marker);
EMIT_SIZEOF(ENVIRONMENT_COORDINATE_SIZE, EnvironmentCoordinate);
// TypedArrayBase layout
outln("\n# TypedArrayBase layout");

View file

@ -476,6 +476,7 @@ op DynamicGetCalleeAndThisFromEnvironment < Instruction
m_callee: Operand
m_this_value: Operand
m_identifier: IdentifierTableIndex
m_cache: EnvironmentCoordinateCacheIndex
endop
op GetCompletionFields < Instruction
@ -564,6 +565,7 @@ endop
op DynamicGetBinding < Instruction
m_dst: Operand
m_identifier: IdentifierTableIndex
m_cache: EnvironmentCoordinateCacheIndex
endop
op GetInitializedBinding < Instruction
@ -575,6 +577,7 @@ endop
op DynamicGetInitializedBinding < Instruction
m_dst: Operand
m_identifier: IdentifierTableIndex
m_cache: EnvironmentCoordinateCacheIndex
endop
op GreaterThan < Instruction
@ -620,6 +623,7 @@ endop
op DynamicInitializeLexicalBinding < Instruction
m_identifier: IdentifierTableIndex
m_src: Operand
m_cache: EnvironmentCoordinateCacheIndex
endop
op InitializeVariableBinding < Instruction
@ -631,6 +635,7 @@ endop
op DynamicInitializeVariableBinding < Instruction
m_identifier: IdentifierTableIndex
m_src: Operand
m_cache: EnvironmentCoordinateCacheIndex
endop
op InstanceOf < Instruction
@ -1050,6 +1055,7 @@ endop
op DynamicSetLexicalBinding < Instruction
m_identifier: IdentifierTableIndex
m_src: Operand
m_cache: EnvironmentCoordinateCacheIndex
endop
op SetVariableBinding < Instruction
@ -1061,6 +1067,7 @@ endop
op DynamicSetVariableBinding < Instruction
m_identifier: IdentifierTableIndex
m_src: Operand
m_cache: EnvironmentCoordinateCacheIndex
endop
op StrictlyEquals < Instruction
@ -1138,6 +1145,7 @@ endop
op DynamicTypeofBinding < Instruction
m_dst: Operand
m_identifier: IdentifierTableIndex
m_cache: EnvironmentCoordinateCacheIndex
endop
op UnaryMinus < Instruction

View file

@ -262,6 +262,7 @@ Executable::Executable(
NonnullRefPtr<SourceCode const> source_code,
size_t number_of_property_lookup_caches,
size_t number_of_global_variable_caches,
size_t number_of_environment_coordinate_caches,
size_t number_of_template_object_caches,
size_t number_of_object_shape_caches,
size_t number_of_object_property_iterator_caches,
@ -280,6 +281,7 @@ Executable::Executable(
{
property_lookup_caches.resize(number_of_property_lookup_caches);
global_variable_caches.resize(number_of_global_variable_caches);
environment_coordinate_caches.resize(number_of_environment_coordinate_caches);
template_object_caches.resize(number_of_template_object_caches);
object_shape_caches.resize(number_of_object_shape_caches);
object_property_iterator_caches.resize(number_of_object_property_iterator_caches);
@ -563,6 +565,7 @@ size_t Executable::external_memory_size() const
for (auto const& cache : property_lookup_caches)
size = saturating_add_external_memory_size(size, cache.external_memory_size());
size = saturating_add_external_memory_size(size, vector_external_memory_size(global_variable_caches));
size = saturating_add_external_memory_size(size, vector_external_memory_size(environment_coordinate_caches));
size = saturating_add_external_memory_size(size, vector_external_memory_size(template_object_caches));
size = saturating_add_external_memory_size(size, vector_external_memory_size(object_shape_caches));
for (auto const& cache : object_shape_caches)

View file

@ -274,6 +274,7 @@ public:
NonnullRefPtr<SourceCode const>,
size_t number_of_property_lookup_caches,
size_t number_of_global_variable_caches,
size_t number_of_environment_coordinate_caches,
size_t number_of_template_object_caches,
size_t number_of_object_shape_caches,
size_t number_of_object_property_iterator_caches,
@ -286,6 +287,7 @@ public:
InstructionStream bytecode;
Vector<PropertyLookupCache> property_lookup_caches;
Vector<GlobalVariableCache> global_variable_caches;
Vector<EnvironmentCoordinate> environment_coordinate_caches;
Vector<TemplateObjectCache> template_object_caches;
Vector<ObjectShapeCache> object_shape_caches;
Vector<ObjectPropertyIteratorCache> object_property_iterator_caches;

View file

@ -1304,9 +1304,59 @@ inline ThrowCompletionOr<CalleeAndThis> get_callee_and_this_from_environment(VM&
};
}
inline ThrowCompletionOr<CalleeAndThis> dynamically_get_callee_and_this_from_environment(VM& vm, Utf16FlyString const& name, Strict strict)
template<typename EnvironmentPointer>
static EnvironmentPointer get_cacheable_environment(EnvironmentPointer environment, EnvironmentCoordinate const& cache)
{
VERIFY(cache.is_valid());
for (size_t i = 0; i < cache.hops; ++i) {
if (!environment->is_declarative_environment() || environment->is_permanently_screwed_by_eval()) [[unlikely]]
return nullptr;
environment = environment->outer_environment();
if (!environment) [[unlikely]]
return nullptr;
}
if (environment->is_declarative_environment() && !environment->is_permanently_screwed_by_eval()) [[likely]]
return environment;
return nullptr;
}
template<typename EnvironmentPointer>
static EnvironmentPointer get_cached_environment(EnvironmentPointer environment, EnvironmentCoordinate& cache)
{
if (!cache.is_valid()) [[unlikely]]
return nullptr;
if (auto* cached_environment = get_cacheable_environment(environment, cache)) [[likely]]
return cached_environment;
cache = {};
return nullptr;
}
template<typename EnvironmentPointer>
static void update_environment_coordinate_cache(EnvironmentPointer environment, Reference const& reference, EnvironmentCoordinate& cache)
{
if (!reference.environment_coordinate().has_value())
return;
auto candidate = reference.environment_coordinate().value();
if (get_cacheable_environment(environment, candidate))
cache = candidate;
}
inline ThrowCompletionOr<CalleeAndThis> dynamically_get_callee_and_this_from_environment(VM& vm, Utf16FlyString const& name, Strict strict, EnvironmentCoordinate& cache)
{
auto const* current_environment = vm.running_execution_context().lexical_environment.ptr();
if (auto const* environment = get_cached_environment(current_environment, cache)) [[likely]] {
auto callee = TRY(static_cast<DeclarativeEnvironment const&>(*environment).get_binding_value_direct(vm, cache.index));
return CalleeAndThis {
.callee = callee,
.this_value = js_undefined(),
};
}
auto reference = TRY(vm.resolve_binding(name, strict));
update_environment_coordinate_cache(current_environment, reference, cache);
auto callee = TRY(reference.get_value(vm));
@ -2331,10 +2381,24 @@ static ThrowCompletionOr<void> get_binding(VM& vm, Operand dst, EnvironmentCoord
return {};
}
static ThrowCompletionOr<void> dynamically_get_binding(VM& vm, Operand dst, IdentifierTableIndex identifier, Strict strict)
template<BindingIsKnownToBeInitialized binding_is_known_to_be_initialized>
static ThrowCompletionOr<void> dynamically_get_binding(VM& vm, Operand dst, IdentifierTableIndex identifier, Strict strict, EnvironmentCoordinate& cache)
{
auto const* current_environment = vm.running_execution_context().lexical_environment.ptr();
if (auto const* environment = get_cached_environment(current_environment, cache)) [[likely]] {
Value value;
if constexpr (binding_is_known_to_be_initialized == BindingIsKnownToBeInitialized::No) {
value = TRY(static_cast<DeclarativeEnvironment const&>(*environment).get_binding_value_direct(vm, cache.index));
} else {
value = static_cast<DeclarativeEnvironment const&>(*environment).get_initialized_binding_value_direct(cache.index);
}
vm.set(dst, value);
return {};
}
auto& executable = vm.current_executable();
auto reference = TRY(vm.resolve_binding(executable.get_identifier(identifier), strict));
update_environment_coordinate_cache(current_environment, reference, cache);
vm.set(dst, TRY(reference.get_value(vm)));
return {};
@ -2352,12 +2416,12 @@ ThrowCompletionOr<void> GetInitializedBinding::execute_impl(VM& vm) const
ThrowCompletionOr<void> DynamicGetBinding::execute_impl(VM& vm) const
{
return dynamically_get_binding(vm, m_dst, m_identifier, strict());
return dynamically_get_binding<BindingIsKnownToBeInitialized::No>(vm, m_dst, m_identifier, strict(), vm.current_executable().environment_coordinate_caches[m_cache]);
}
ThrowCompletionOr<void> DynamicGetInitializedBinding::execute_impl(VM& vm) const
{
return dynamically_get_binding(vm, m_dst, m_identifier, strict());
return dynamically_get_binding<BindingIsKnownToBeInitialized::Yes>(vm, m_dst, m_identifier, strict(), vm.current_executable().environment_coordinate_caches[m_cache]);
}
ThrowCompletionOr<void> GetCalleeAndThisFromEnvironment::execute_impl(VM& vm) const
@ -2375,7 +2439,8 @@ ThrowCompletionOr<void> DynamicGetCalleeAndThisFromEnvironment::execute_impl(VM&
auto callee_and_this = TRY(dynamically_get_callee_and_this_from_environment(
vm,
vm.get_identifier(m_identifier),
strict()));
strict(),
vm.current_executable().environment_coordinate_caches[m_cache]));
vm.set(m_callee, callee_and_this.callee);
vm.set(m_this_value, callee_and_this.this_value);
return {};
@ -2601,13 +2666,23 @@ static ThrowCompletionOr<void> initialize_or_set_binding(VM& vm, Strict strict,
}
template<EnvironmentMode environment_mode, BindingInitializationMode initialization_mode>
static ThrowCompletionOr<void> dynamically_initialize_or_set_binding(VM& vm, IdentifierTableIndex identifier_index, Strict strict, Value value)
static ThrowCompletionOr<void> dynamically_initialize_or_set_binding(VM& vm, IdentifierTableIndex identifier_index, Strict strict, Value value, EnvironmentCoordinate& cache)
{
auto* environment = environment_mode == EnvironmentMode::Lexical
? vm.running_execution_context().lexical_environment.ptr()
: vm.running_execution_context().variable_environment.ptr();
if (auto* cached_environment = get_cached_environment(environment, cache)) [[likely]] {
if constexpr (initialization_mode == BindingInitializationMode::Initialize) {
TRY(static_cast<DeclarativeEnvironment&>(*cached_environment).initialize_binding_direct(vm, cache.index, value, Environment::InitializeBindingHint::Normal));
} else if (initialization_mode == BindingInitializationMode::Set) {
TRY(static_cast<DeclarativeEnvironment&>(*cached_environment).set_mutable_binding_direct(vm, cache.index, value, strict == Strict::Yes));
}
return {};
}
auto reference = TRY(vm.resolve_binding(vm.get_identifier(identifier_index), strict, environment));
update_environment_coordinate_cache(environment, reference, cache);
if constexpr (initialization_mode == BindingInitializationMode::Initialize) {
TRY(reference.initialize_referenced_binding(vm, value));
} else if (initialization_mode == BindingInitializationMode::Set) {
@ -2628,12 +2703,12 @@ ThrowCompletionOr<void> InitializeVariableBinding::execute_impl(VM& vm) const
ThrowCompletionOr<void> DynamicInitializeLexicalBinding::execute_impl(VM& vm) const
{
return dynamically_initialize_or_set_binding<EnvironmentMode::Lexical, BindingInitializationMode::Initialize>(vm, m_identifier, strict(), vm.get(m_src));
return dynamically_initialize_or_set_binding<EnvironmentMode::Lexical, BindingInitializationMode::Initialize>(vm, m_identifier, strict(), vm.get(m_src), vm.current_executable().environment_coordinate_caches[m_cache]);
}
ThrowCompletionOr<void> DynamicInitializeVariableBinding::execute_impl(VM& vm) const
{
return dynamically_initialize_or_set_binding<EnvironmentMode::Var, BindingInitializationMode::Initialize>(vm, m_identifier, strict(), vm.get(m_src));
return dynamically_initialize_or_set_binding<EnvironmentMode::Var, BindingInitializationMode::Initialize>(vm, m_identifier, strict(), vm.get(m_src), vm.current_executable().environment_coordinate_caches[m_cache]);
}
ThrowCompletionOr<void> SetLexicalBinding::execute_impl(VM& vm) const
@ -2648,12 +2723,12 @@ ThrowCompletionOr<void> SetVariableBinding::execute_impl(VM& vm) const
ThrowCompletionOr<void> DynamicSetLexicalBinding::execute_impl(VM& vm) const
{
return dynamically_initialize_or_set_binding<EnvironmentMode::Lexical, BindingInitializationMode::Set>(vm, m_identifier, strict(), vm.get(m_src));
return dynamically_initialize_or_set_binding<EnvironmentMode::Lexical, BindingInitializationMode::Set>(vm, m_identifier, strict(), vm.get(m_src), vm.current_executable().environment_coordinate_caches[m_cache]);
}
ThrowCompletionOr<void> DynamicSetVariableBinding::execute_impl(VM& vm) const
{
return dynamically_initialize_or_set_binding<EnvironmentMode::Var, BindingInitializationMode::Set>(vm, m_identifier, strict(), vm.get(m_src));
return dynamically_initialize_or_set_binding<EnvironmentMode::Var, BindingInitializationMode::Set>(vm, m_identifier, strict(), vm.get(m_src), vm.current_executable().environment_coordinate_caches[m_cache]);
}
ThrowCompletionOr<void> GetById::execute_impl(VM& vm) const
@ -3449,6 +3524,14 @@ ThrowCompletionOr<void> TypeofBinding::execute_impl(VM& vm) const
ThrowCompletionOr<void> DynamicTypeofBinding::execute_impl(VM& vm) const
{
auto& cache = vm.current_executable().environment_coordinate_caches[m_cache];
auto const* current_environment = vm.running_execution_context().lexical_environment.ptr();
if (auto const* environment = get_cached_environment(current_environment, cache)) [[likely]] {
auto value = TRY(static_cast<DeclarativeEnvironment const&>(*environment).get_binding_value_direct(vm, cache.index));
vm.set(dst(), value.typeof_(vm));
return {};
}
// 1. Let val be the result of evaluating UnaryExpression.
auto reference = TRY(vm.resolve_binding(vm.get_identifier(m_identifier), strict()));
@ -3460,6 +3543,8 @@ ThrowCompletionOr<void> DynamicTypeofBinding::execute_impl(VM& vm) const
}
// 3. Set val to ? GetValue(val).
update_environment_coordinate_cache(current_environment, reference, cache);
auto value = TRY(reference.get_value(vm));
// 4. NOTE: This step is replaced in section B.3.6.3.

View file

@ -51,6 +51,8 @@ static StringView validation_error_kind_to_string(JS::FFI::ValidationErrorKind k
return "PropertyLookupCacheIndexOutOfRange"sv;
case JS::FFI::ValidationErrorKind::GlobalVariableCacheIndexOutOfRange:
return "GlobalVariableCacheIndexOutOfRange"sv;
case JS::FFI::ValidationErrorKind::EnvironmentCoordinateCacheIndexOutOfRange:
return "EnvironmentCoordinateCacheIndexOutOfRange"sv;
case JS::FFI::ValidationErrorKind::TemplateObjectCacheIndexOutOfRange:
return "TemplateObjectCacheIndexOutOfRange"sv;
case JS::FFI::ValidationErrorKind::ObjectShapeCacheIndexOutOfRange:
@ -109,6 +111,7 @@ ErrorOr<void> validate_bytecode(Executable const& executable, ReadonlySpan<u32>
.regex_table_size = 0,
.property_lookup_cache_count = static_cast<u32>(executable.property_lookup_caches.size()),
.global_variable_cache_count = static_cast<u32>(executable.global_variable_caches.size()),
.environment_coordinate_cache_count = static_cast<u32>(executable.environment_coordinate_caches.size()),
.template_object_cache_count = static_cast<u32>(executable.template_object_caches.size()),
.object_shape_cache_count = static_cast<u32>(executable.object_shape_caches.size()),
.object_property_iterator_cache_count = static_cast<u32>(executable.object_property_iterator_caches.size()),

View file

@ -139,6 +139,7 @@ pub fn field_type_info(ty: &str) -> FieldType {
"Value" => ("u64", 8, 8, "u64"),
"PropertyLookupCacheIndex"
| "GlobalVariableCacheIndex"
| "EnvironmentCoordinateCacheIndex"
| "TemplateObjectCacheIndex"
| "ObjectShapeCacheIndex"
| "ObjectPropertyIteratorCacheIndex" => ("u32", 4, 4, "u32"),

View file

@ -201,6 +201,10 @@ fn emit_scalar_field_check(
w,
" validate_global_variable_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
)?,
"EnvironmentCoordinateCacheIndex" => writeln!(
w,
" validate_environment_coordinate_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
)?,
"TemplateObjectCacheIndex" => writeln!(
w,
" validate_template_object_cache_index(read_u32(bytes, at + {offset}), ctx)?;"

View file

@ -88,8 +88,14 @@ fn emit_get_binding(
) {
(Some(cache), true) => generator.emit(Instruction::GetInitializedBinding { dst, identifier, cache }),
(Some(cache), false) => generator.emit(Instruction::GetBinding { dst, identifier, cache }),
(None, true) => generator.emit(Instruction::DynamicGetInitializedBinding { dst, identifier }),
(None, false) => generator.emit(Instruction::DynamicGetBinding { dst, identifier }),
(None, true) => {
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicGetInitializedBinding { dst, identifier, cache });
}
(None, false) => {
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicGetBinding { dst, identifier, cache });
}
}
}
@ -107,10 +113,12 @@ fn emit_get_callee_and_this_from_environment(
cache,
});
} else {
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicGetCalleeAndThisFromEnvironment {
callee,
this_value,
identifier,
cache,
});
}
}
@ -119,7 +127,8 @@ fn emit_initialize_lexical_binding(generator: &mut Generator, identifier: Identi
if let Some(cache) = generator.environment_coordinate_for_identifier(identifier) {
generator.emit(Instruction::InitializeLexicalBinding { identifier, src, cache });
} else {
generator.emit(Instruction::DynamicInitializeLexicalBinding { identifier, src });
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicInitializeLexicalBinding { identifier, src, cache });
}
}
@ -130,7 +139,8 @@ fn emit_initialize_variable_binding(generator: &mut Generator, identifier: Ident
if let Some(cache) = generator.variable_environment_coordinate_for_identifier(identifier) {
generator.emit(Instruction::InitializeVariableBinding { identifier, src, cache });
} else {
generator.emit(Instruction::DynamicInitializeVariableBinding { identifier, src });
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicInitializeVariableBinding { identifier, src, cache });
}
}
@ -138,7 +148,8 @@ fn emit_set_lexical_binding(generator: &mut Generator, identifier: IdentifierTab
if let Some(cache) = generator.environment_coordinate_for_identifier(identifier) {
generator.emit(Instruction::SetLexicalBinding { identifier, src, cache });
} else {
generator.emit(Instruction::DynamicSetLexicalBinding { identifier, src });
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicSetLexicalBinding { identifier, src, cache });
}
}
@ -148,7 +159,8 @@ fn emit_set_variable_binding(generator: &mut Generator, identifier: IdentifierTa
if let Some(cache) = generator.variable_environment_coordinate_for_identifier(identifier) {
generator.emit(Instruction::SetVariableBinding { identifier, src, cache });
} else {
generator.emit(Instruction::DynamicSetVariableBinding { identifier, src });
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicSetVariableBinding { identifier, src, cache });
}
}
@ -156,7 +168,8 @@ fn emit_typeof_binding(generator: &mut Generator, dst: Operand, identifier: Iden
if let Some(cache) = generator.environment_coordinate_for_identifier(identifier) {
generator.emit(Instruction::TypeofBinding { dst, identifier, cache });
} else {
generator.emit(Instruction::DynamicTypeofBinding { dst, identifier });
let cache = generator.next_environment_coordinate_cache();
generator.emit(Instruction::DynamicTypeofBinding { dst, identifier, cache });
}
}

View file

@ -212,6 +212,7 @@ pub struct FFIExecutableData {
pub local_variable_count: usize,
pub property_lookup_cache_count: u32,
pub global_variable_cache_count: u32,
pub environment_coordinate_cache_count: u32,
pub template_object_cache_count: u32,
pub object_shape_cache_count: u32,
pub object_property_iterator_cache_count: u32,
@ -700,6 +701,7 @@ pub struct ExecutableParts<'a> {
pub struct ExecutableMetadata {
pub property_lookup_cache_count: u32,
pub global_variable_cache_count: u32,
pub environment_coordinate_cache_count: u32,
pub template_object_cache_count: u32,
pub object_shape_cache_count: u32,
pub object_property_iterator_cache_count: u32,
@ -781,6 +783,7 @@ pub unsafe fn create_executable_from_slices(
local_variable_count: slices.local_variable_names.len(),
property_lookup_cache_count: metadata.property_lookup_cache_count,
global_variable_cache_count: metadata.global_variable_cache_count,
environment_coordinate_cache_count: metadata.environment_coordinate_cache_count,
template_object_cache_count: metadata.template_object_cache_count,
object_shape_cache_count: metadata.object_shape_cache_count,
object_property_iterator_cache_count: metadata.object_property_iterator_cache_count,
@ -850,6 +853,7 @@ pub unsafe fn create_executable_with_dependencies_from_parts(
let metadata = ExecutableMetadata {
property_lookup_cache_count: generator.next_property_lookup_cache,
global_variable_cache_count: generator.next_global_variable_cache,
environment_coordinate_cache_count: generator.next_environment_coordinate_cache,
template_object_cache_count: generator.next_template_object_cache,
object_shape_cache_count: generator.next_object_shape_cache,
object_property_iterator_cache_count: generator.next_object_property_iterator_cache,

View file

@ -259,6 +259,7 @@ pub struct Generator {
// --- Various counters ---
pub next_property_lookup_cache: u32,
pub next_global_variable_cache: u32,
pub next_environment_coordinate_cache: u32,
pub next_template_object_cache: u32,
pub next_object_shape_cache: u32,
pub next_object_property_iterator_cache: u32,
@ -424,6 +425,7 @@ impl Generator {
current_finally_context: None,
next_property_lookup_cache: 0,
next_global_variable_cache: 0,
next_environment_coordinate_cache: 0,
next_template_object_cache: 0,
next_object_shape_cache: 0,
next_object_property_iterator_cache: 0,
@ -968,6 +970,7 @@ impl Generator {
next_cache_method!(next_property_lookup_cache, next_property_lookup_cache);
next_cache_method!(next_global_variable_cache, next_global_variable_cache);
next_cache_method!(next_environment_coordinate_cache, next_environment_coordinate_cache);
next_cache_method!(next_template_object_cache, next_template_object_cache);
next_cache_method!(next_object_shape_cache, next_object_shape_cache);
next_cache_method!(next_object_property_iterator_cache, next_object_property_iterator_cache);

View file

@ -41,6 +41,7 @@ pub struct FFIValidatorBounds {
pub regex_table_size: u32,
pub property_lookup_cache_count: u32,
pub global_variable_cache_count: u32,
pub environment_coordinate_cache_count: u32,
pub template_object_cache_count: u32,
pub object_shape_cache_count: u32,
pub object_property_iterator_cache_count: u32,
@ -88,6 +89,7 @@ pub enum ValidationErrorKind {
ExceptionHandlerHandlerInvalid = 24,
ExceptionHandlerRangeInvalid = 25,
SourceMapOffsetInvalid = 26,
EnvironmentCoordinateCacheIndexOutOfRange = 27,
}
/// Detail returned to the C++ caller on validation failure.
@ -257,6 +259,17 @@ pub fn validate_global_variable_cache_index(raw: u32, ctx: &ValidationContext) -
Ok(())
}
#[inline]
pub fn validate_environment_coordinate_cache_index(
raw: u32,
ctx: &ValidationContext,
) -> Result<(), ValidationErrorKind> {
if raw >= ctx.bounds.environment_coordinate_cache_count {
return Err(ValidationErrorKind::EnvironmentCoordinateCacheIndexOutOfRange);
}
Ok(())
}
#[inline]
pub fn validate_template_object_cache_index(raw: u32, ctx: &ValidationContext) -> Result<(), ValidationErrorKind> {
if raw == NO_CACHE_INDEX {
@ -495,6 +508,7 @@ mod tests {
regex_table_size: 0,
property_lookup_cache_count: 4,
global_variable_cache_count: 4,
environment_coordinate_cache_count: 4,
template_object_cache_count: 4,
object_shape_cache_count: 4,
object_property_iterator_cache_count: 4,
@ -693,6 +707,21 @@ mod tests {
assert_eq!(err.kind, ValidationErrorKind::ObjectShapeCacheIndexOutOfRange);
}
#[test]
fn rejects_environment_coordinate_cache_sentinel() {
// DynamicGetBinding layout: header(2) + pad(2) + m_dst(4)
// + m_identifier(4) + m_cache(4) = 16 bytes.
let mut bytes = [0u8; 16];
bytes[0] = OpCode::DynamicGetBinding as u8;
// m_dst and m_identifier stay at 0.
// m_cache at offset 12: dynamic environment opcodes always index this
// cache, so the no-cache sentinel must not validate.
put_u32(&mut bytes, 12, NO_CACHE_INDEX);
let err = validate(&bytes, &permissive_bounds()).unwrap_err();
assert_eq!(err.kind, ValidationErrorKind::EnvironmentCoordinateCacheIndexOutOfRange);
}
#[test]
fn rejects_enum_out_of_range() {
// SetCompletionType layout: header(2) + pad(2) + m_completion(4) + m_completion_type(4) = 12, padded to 16.

View file

@ -29,7 +29,7 @@ use crate::bytecode::validator::{
use crate::{CompiledProgram, CompiledProgramBytecode, ModuleCallbacks, ast, u32_from_usize};
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
const FORMAT_VERSION: u32 = 10;
const FORMAT_VERSION: u32 = 11;
const SOURCE_HASH_SIZE: usize = 32;
const BYTECODE_ALIGNMENT: usize = 8;
const COMPLETION_TYPE_VARIANT_COUNT: u32 = 6;
@ -1229,6 +1229,7 @@ unsafe fn materialize_executable(
crate::bytecode::ffi::ExecutableMetadata {
property_lookup_cache_count: cache_counters.property_lookup_cache_count,
global_variable_cache_count: cache_counters.global_variable_cache_count,
environment_coordinate_cache_count: cache_counters.environment_coordinate_cache_count,
template_object_cache_count: cache_counters.template_object_cache_count,
object_shape_cache_count: cache_counters.object_shape_cache_count,
object_property_iterator_cache_count: cache_counters.object_property_iterator_cache_count,
@ -2391,6 +2392,7 @@ impl DecodedExecutableRecord {
regex_table_size: 0,
property_lookup_cache_count: self.cache_counters.property_lookup_cache_count,
global_variable_cache_count: self.cache_counters.global_variable_cache_count,
environment_coordinate_cache_count: self.cache_counters.environment_coordinate_cache_count,
template_object_cache_count: self.cache_counters.template_object_cache_count,
object_shape_cache_count: self.cache_counters.object_shape_cache_count,
object_property_iterator_cache_count: self.cache_counters.object_property_iterator_cache_count,
@ -2477,6 +2479,7 @@ impl Encode for CacheCounters<'_> {
fn encode(&self, encoder: &mut Encoder) {
self.0.next_property_lookup_cache.encode(encoder);
self.0.next_global_variable_cache.encode(encoder);
self.0.next_environment_coordinate_cache.encode(encoder);
self.0.next_template_object_cache.encode(encoder);
self.0.next_object_shape_cache.encode(encoder);
self.0.next_object_property_iterator_cache.encode(encoder);
@ -2488,6 +2491,7 @@ impl CacheCounters<'_> {
Some(DecodedCacheCounters {
property_lookup_cache_count: u32::decode(decoder)?,
global_variable_cache_count: u32::decode(decoder)?,
environment_coordinate_cache_count: u32::decode(decoder)?,
template_object_cache_count: u32::decode(decoder)?,
object_shape_cache_count: u32::decode(decoder)?,
object_property_iterator_cache_count: u32::decode(decoder)?,
@ -2498,6 +2502,7 @@ impl CacheCounters<'_> {
struct DecodedCacheCounters {
property_lookup_cache_count: u32,
global_variable_cache_count: u32,
environment_coordinate_cache_count: u32,
template_object_cache_count: u32,
object_shape_cache_count: u32,
object_property_iterator_cache_count: u32,
@ -2507,6 +2512,7 @@ impl DecodedCacheCounters {
fn validate(&self) {
let _ = self.property_lookup_cache_count
+ self.global_variable_cache_count
+ self.environment_coordinate_cache_count
+ self.template_object_cache_count
+ self.object_shape_cache_count
+ self.object_property_iterator_cache_count;
@ -3568,6 +3574,9 @@ mod tests {
}
unsafe extern "C" fn ignore_foreign_owner(_: *mut c_void) {}
unsafe extern "C" fn ignore_clone_foreign_owner(_: *const c_void) -> *mut c_void {
std::ptr::null_mut()
}
#[test]
fn utf16_decode_borrows_from_foreign_blob() {
@ -3581,6 +3590,7 @@ mod tests {
&bytes,
Some(ForeignBytecodeCacheBlobOwner {
owner: std::ptr::null_mut(),
clone_owner: ignore_clone_foreign_owner,
free_owner: ignore_foreign_owner,
}),
);

View file

@ -1076,6 +1076,7 @@ extern "C" void* rust_create_executable(
source_code,
data->property_lookup_cache_count,
data->global_variable_cache_count,
data->environment_coordinate_cache_count,
data->template_object_cache_count,
data->object_shape_cache_count,
data->object_property_iterator_cache_count,

View file

@ -349,6 +349,7 @@ NUMERIC_TYPES = {
CACHE_INDEX_TYPES = {
"PropertyLookupCacheIndex",
"GlobalVariableCacheIndex",
"EnvironmentCoordinateCacheIndex",
"TemplateObjectCacheIndex",
"ObjectShapeCacheIndex",
"ObjectPropertyIteratorCacheIndex",

View file

@ -12,7 +12,7 @@ block0:
[ 60] End value:reg6
with_eval$361b5eed eval-prevents-locals.js:6:5
with_eval$b4b96665 eval-prevents-locals.js:6:5
Registers: 9
Blocks: 1
Constants:
@ -26,9 +26,9 @@ block0:
[ 30] CreateVariable `x`, is_immutable:false, is_global:false, is_strict:false
[ 40] InitializeLexicalBinding `x`, src:Int32(1)
[ 58] DynamicGetCalleeAndThisFromEnvironment callee:reg7, this_value:reg8, `eval`
[ 68] CallDirectEval dst:reg6, callee:reg7, this_value:reg8, eval, arguments:[String("")]
[ 90] GetBinding dst:reg6, `x`
[ a8] Return value:reg6
[ 70] CallDirectEval dst:reg6, callee:reg7, this_value:reg8, eval, arguments:[String("")]
[ 98] GetBinding dst:reg6, `x`
[ b0] Return value:reg6
eval$b72141f3
@ -41,7 +41,7 @@ block0:
[ 0] End value:Undefined
outer_eval$e6231df2 eval-prevents-locals.js:14:9
outer_eval$6785167a eval-prevents-locals.js:14:9
Registers: 8
Blocks: 1
Constants:
@ -55,10 +55,10 @@ block0:
[ 38] NewFunction dst:reg5, shared_function_data_index:0
[ 50] SetVariableBinding `inner`, src:reg5
[ 68] DynamicGetCalleeAndThisFromEnvironment callee:reg6, this_value:reg7, `eval`
[ 78] CallDirectEval dst:reg5, callee:reg6, this_value:reg7, eval, arguments:[String("")]
[ a0] GetCalleeAndThisFromEnvironment callee:reg6, this_value:reg7, `inner`
[ b8] Call dst:reg5, callee:reg6, this_value:reg7, inner
[ d8] Return value:reg5
[ 80] CallDirectEval dst:reg5, callee:reg6, this_value:reg7, eval, arguments:[String("")]
[ a8] GetCalleeAndThisFromEnvironment callee:reg6, this_value:reg7, `inner`
[ c0] Call dst:reg5, callee:reg6, this_value:reg7, inner
[ e0] Return value:reg5
eval$b72141f3

View file

@ -10,7 +10,7 @@ block0:
[ 30] End value:reg5
foo$bf97ec0c eval-same-function.js:6:9
foo$3e35f384 eval-same-function.js:6:9
Registers: 8
Blocks: 1
Constants:
@ -21,10 +21,10 @@ foo$bf97ec0c eval-same-function.js:6:9
block0:
[ 0] CreateArguments is_immutable:false
[ 10] DynamicGetCalleeAndThisFromEnvironment callee:reg6, this_value:reg7, `eval`
[ 20] CallDirectEval dst:reg5, callee:reg6, this_value:reg7, eval, arguments:[String("var x = 1")]
[ 48] DynamicGetBinding dst:reg6, `Number`
[ 58] CallConstruct dst:reg5, callee:reg6, Number, arguments:[Int32(42)]
[ 78] Return value:reg5
[ 28] CallDirectEval dst:reg5, callee:reg6, this_value:reg7, eval, arguments:[String("var x = 1")]
[ 50] DynamicGetBinding dst:reg6, `Number`
[ 60] CallConstruct dst:reg5, callee:reg6, Number, arguments:[Int32(42)]
[ 80] Return value:reg5
eval$d8e83433 line 1, column 1

View file

@ -10,7 +10,7 @@ block0:
[ 30] End value:reg5
f$20dcebd6 var-env-capacity-with-param-expressions.js:3:9
f$a23ee45e var-env-capacity-with-param-expressions.js:3:9
Registers: 10
Blocks: 3
Constants:
@ -42,8 +42,8 @@ block2:
[ 108] CreateVariable `c`, is_immutable:false, is_global:false, is_strict:false
[ 118] InitializeVariableBinding `c`, src:reg7
[ 130] DynamicGetCalleeAndThisFromEnvironment callee:reg8, this_value:reg9, `eval`
[ 140] CallDirectEval dst:reg7, callee:reg8, this_value:reg9, eval, arguments:[String("")]
[ 168] End value:Undefined
[ 148] CallDirectEval dst:reg7, callee:reg8, this_value:reg9, eval, arguments:[String("")]
[ 170] End value:Undefined
eval$b72141f3

View file

@ -43,3 +43,18 @@ test("restores lexical environment even when exception is thrown", () => {
}
expect(() => foo).toThrowWithMessage(ReferenceError, "'foo' is not defined");
});
test("with object changes can shadow an outer binding", () => {
let outer = "outer";
let object = {};
let seen = [];
with (object) {
for (let i = 0; i < 2; ++i) {
seen.push(outer);
object.outer = "object";
}
}
expect(seen).toEqual(["outer", "object"]);
});