LibJS: Share declarative binding flags in shapes

Let shaped declarative environments read static binding flags from their
EnvironmentShape instead of retaining a per-environment copy. The local
flag vector now mirrors the local name vector and only stores dynamic
bindings created after the shared shape.

Split EnvironmentShape binding descriptors into separate name and flag
vectors so the shared metadata is compact as well.
This commit is contained in:
Andreas Kling 2026-05-15 00:47:38 +02:00 committed by Andreas Kling
parent b0372ca84c
commit da65105cd6
6 changed files with 47 additions and 21 deletions

View file

@ -994,9 +994,27 @@ macro store_binding_value(env, idx, binding_values, value)
end
macro load_binding_flags(env, idx, flags, flag)
temp shape, shape_size, local_index
load64 shape, [env, DECLARATIVE_ENVIRONMENT_SHAPE]
branch_zero shape, .load_environment_flags
load64 shape_size, [shape, ENVIRONMENT_SHAPE_BINDING_FLAGS]
branch_ge_unsigned idx, shape_size, .load_environment_local_flags
load64 flags, [shape, ENVIRONMENT_SHAPE_BINDING_FLAGS_DATA_PTR]
assert_nonzero flags
load8 flag, [flags, idx]
jmp .done
.load_environment_local_flags:
load64 flags, [env, BINDING_FLAGS_DATA_PTR]
assert_nonzero flags
mov local_index, idx
sub local_index, shape_size
load8 flag, [flags, local_index]
jmp .done
.load_environment_flags:
load64 flags, [env, BINDING_FLAGS_DATA_PTR]
assert_nonzero flags
load8 flag, [flags, idx]
.done:
end
# Inline environment chain walk + binding value load with TDZ check.

View file

@ -326,13 +326,16 @@ int main()
// DeclarativeEnvironment binding storage layout
outln("\n# DeclarativeEnvironment binding storage layout");
EMIT_OFFSET(DECLARATIVE_ENVIRONMENT_SHAPE, DeclarativeEnvironment, m_shape);
EMIT_OFFSET(DECLARATIVE_ENVIRONMENT_BINDING_VALUES, DeclarativeEnvironment, m_binding_values);
EMIT_OFFSET(DECLARATIVE_ENVIRONMENT_BINDING_FLAGS, DeclarativeEnvironment, m_binding_flags);
EMIT_OFFSET(ENVIRONMENT_SHAPE_BINDING_FLAGS, EnvironmentShape, m_binding_flags);
outln("const BINDING_FLAG_MUTABLE = {}", 1 << 1);
// Vector<T> layout: m_size(0), m_capacity(8), m_metadata.outline_buffer(16)
outln("const BINDING_VALUES_DATA_PTR = {}", offsetof(DeclarativeEnvironment, m_binding_values) + sizeof(size_t) * 2);
outln("const BINDING_FLAGS_DATA_PTR = {}", offsetof(DeclarativeEnvironment, m_binding_flags) + sizeof(size_t) * 2);
outln("const ENVIRONMENT_SHAPE_BINDING_FLAGS_DATA_PTR = {}", offsetof(EnvironmentShape, m_binding_flags) + sizeof(size_t) * 2);
// EnvironmentCoordinate layout
outln("\n# EnvironmentCoordinate layout");

View file

@ -75,10 +75,10 @@ void DeclarativeEnvironment::append_binding(Binding binding)
} else {
m_bindings_assoc.set(binding.name, index);
m_binding_names.append(move(binding.name));
m_binding_flags.append(flags);
}
m_binding_values.append(binding.initialized ? binding.value : js_special_empty_value());
m_binding_flags.append(flags);
}
void DeclarativeEnvironment::clear_binding(Utf16FlyString const& name, size_t index)
@ -94,7 +94,7 @@ void DeclarativeEnvironment::clear_binding(Utf16FlyString const& name, size_t in
auto local_index = local_binding_index(index);
m_binding_names[local_index] = Utf16FlyString {};
m_binding_values[index] = js_special_empty_value();
m_binding_flags[index] = 0;
m_binding_flags[local_index] = 0;
}
DeclarativeEnvironment::Binding DeclarativeEnvironment::binding_at(size_t index) const
@ -127,6 +127,7 @@ void DeclarativeEnvironment::set_environment_shape(GC::Ref<EnvironmentShape> sha
m_shape = shape;
m_binding_names.clear();
m_binding_flags.clear();
m_bindings_assoc.clear();
}

View file

@ -82,11 +82,11 @@ public:
void ensure_capacity(size_t needed_capacity)
{
m_binding_values.ensure_capacity(needed_capacity);
m_binding_flags.ensure_capacity(needed_capacity);
if (m_shape)
return;
m_binding_names.ensure_capacity(needed_capacity);
m_binding_flags.ensure_capacity(needed_capacity);
}
[[nodiscard]] u64 environment_serial_number() const { return m_environment_serial_number; }
@ -123,7 +123,7 @@ private:
{
if (index < shape_binding_count())
return m_shape->binding_flags(index);
return m_binding_flags[index];
return m_binding_flags[local_binding_index(index)];
}
bool binding_is_strict(size_t index) const { return (binding_flags(index) & BindingFlagStrict) != 0; }
bool binding_is_mutable(size_t index) const { return (binding_flags(index) & BindingFlagMutable) != 0; }
@ -191,6 +191,8 @@ private:
GC::Ptr<EnvironmentShape> m_shape;
Vector<Utf16FlyString> m_binding_names;
Vector<Value> m_binding_values;
// When m_shape is set, this only stores flags for local bindings after
// the shared shape bindings.
Vector<u8> m_binding_flags;
Bitmap m_deleted_bindings;
HashMap<Utf16FlyString, size_t> m_bindings_assoc;

View file

@ -12,8 +12,9 @@ namespace JS {
GC_DEFINE_ALLOCATOR(EnvironmentShape);
EnvironmentShape::EnvironmentShape(Vector<BindingDescriptor> bindings, HashMap<Utf16FlyString, size_t> binding_indices)
: m_bindings(move(bindings))
EnvironmentShape::EnvironmentShape(Vector<Utf16FlyString> binding_names, Vector<u8> binding_flags, HashMap<Utf16FlyString, size_t> binding_indices)
: m_binding_names(move(binding_names))
, m_binding_flags(move(binding_flags))
, m_binding_indices(move(binding_indices))
{
}
@ -22,20 +23,24 @@ GC::Ref<EnvironmentShape> EnvironmentShape::create(VM& vm, ReadonlySpan<Utf16Fly
{
VERIFY(names.size() == flags.size());
Vector<BindingDescriptor> bindings;
bindings.ensure_capacity(names.size());
Vector<Utf16FlyString> binding_names;
binding_names.ensure_capacity(names.size());
Vector<u8> binding_flags;
binding_flags.ensure_capacity(flags.size());
HashMap<Utf16FlyString, size_t> binding_indices;
binding_indices.ensure_capacity(names.size());
for (size_t i = 0; i < names.size(); ++i) {
bindings.unchecked_append({ names[i], flags[i] });
binding_names.unchecked_append(names[i]);
binding_flags.unchecked_append(flags[i]);
if (!names[i].is_empty())
binding_indices.set(names[i], i);
}
return vm.heap().allocate<EnvironmentShape>(move(bindings), move(binding_indices));
return vm.heap().allocate<EnvironmentShape>(move(binding_names), move(binding_flags), move(binding_indices));
}
Optional<size_t> EnvironmentShape::find_binding(Utf16FlyString const& name) const
@ -48,7 +53,8 @@ Optional<size_t> EnvironmentShape::find_binding(Utf16FlyString const& name) cons
size_t EnvironmentShape::external_memory_size() const
{
auto size = vector_external_memory_size(m_bindings);
auto size = vector_external_memory_size(m_binding_names);
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_binding_flags));
size = saturating_add_external_memory_size(size, hash_map_external_memory_size(m_binding_indices));
return size;
}

View file

@ -30,25 +30,21 @@ public:
BindingFlagCanBeDeleted = 1 << 2,
};
struct BindingDescriptor {
Utf16FlyString name;
u8 flags { 0 };
};
EnvironmentShape(Vector<BindingDescriptor>, HashMap<Utf16FlyString, size_t>);
EnvironmentShape(Vector<Utf16FlyString>, Vector<u8>, HashMap<Utf16FlyString, size_t>);
virtual ~EnvironmentShape() override = default;
[[nodiscard]] static GC::Ref<EnvironmentShape> create(VM&, ReadonlySpan<Utf16FlyString> names, ReadonlySpan<u8> flags);
[[nodiscard]] size_t size() const { return m_bindings.size(); }
[[nodiscard]] Utf16FlyString const& binding_name(size_t index) const { return m_bindings[index].name; }
[[nodiscard]] u8 binding_flags(size_t index) const { return m_bindings[index].flags; }
[[nodiscard]] size_t size() const { return m_binding_names.size(); }
[[nodiscard]] Utf16FlyString const& binding_name(size_t index) const { return m_binding_names[index]; }
[[nodiscard]] u8 binding_flags(size_t index) const { return m_binding_flags[index]; }
[[nodiscard]] Optional<size_t> find_binding(Utf16FlyString const&) const;
private:
virtual size_t external_memory_size() const override;
Vector<BindingDescriptor> m_bindings;
Vector<Utf16FlyString> m_binding_names;
Vector<u8> m_binding_flags;
HashMap<Utf16FlyString, size_t> m_binding_indices;
};