mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-18 18:00:31 +00:00
Replace the 24-byte Vector<Value> m_storage with an 8-byte raw Value* m_named_properties pointer, backed by a malloc'd allocation with an inline capacity header. Memory layout of the allocation: [u32 capacity] [u32 padding] [Value 0] [Value 1] ... m_named_properties points to Value 0. This shrinks JS::Object from 64 to 48 bytes (on non-Windows platforms) and removes one level of indirection for property access in the asm interpreter, since the data pointer is now stored directly on the object rather than inside a Vector's internal metadata. Growth policy: max(4, max(needed, old_capacity * 2)).
291 lines
16 KiB
C++
291 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2026, the Ladybird developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
// Small build-time tool that prints struct field offsets as DSL constants.
|
|
// Compiled with the same flags as LibJS so layouts match exactly.
|
|
|
|
#include <AK/Format.h>
|
|
#include <LibJS/Bytecode/Builtins.h>
|
|
#include <LibJS/Bytecode/Executable.h>
|
|
#include <LibJS/Bytecode/Interpreter.h>
|
|
#include <LibJS/Runtime/ArrayBuffer.h>
|
|
#include <LibJS/Runtime/DeclarativeEnvironment.h>
|
|
#include <LibJS/Runtime/ExecutionContext.h>
|
|
#include <LibJS/Runtime/FunctionObject.h>
|
|
#include <LibJS/Runtime/IndexedProperties.h>
|
|
#include <LibJS/Runtime/Object.h>
|
|
#include <LibJS/Runtime/Realm.h>
|
|
#include <LibJS/Runtime/Shape.h>
|
|
#include <LibJS/Runtime/TypedArray.h>
|
|
|
|
#define EMIT_OFFSET(name, type, member) \
|
|
outln("const " #name " = {}", offsetof(type, member))
|
|
|
|
#define EMIT_SIZEOF(name, type) \
|
|
outln("const " #name " = {}", sizeof(type))
|
|
|
|
int main()
|
|
{
|
|
using namespace JS;
|
|
using namespace JS::Bytecode;
|
|
|
|
outln("# Generated by gen_asm_offsets -- DO NOT EDIT\n");
|
|
|
|
// Object layout
|
|
outln("# Object layout");
|
|
EMIT_OFFSET(OBJECT_SHAPE, Object, m_shape);
|
|
EMIT_OFFSET(OBJECT_NAMED_PROPERTIES, Object, m_named_properties);
|
|
EMIT_OFFSET(OBJECT_INDEXED_PROPERTIES, Object, m_indexed_properties);
|
|
EMIT_SIZEOF(OBJECT_SIZE, Object);
|
|
|
|
// Object flags byte
|
|
outln("\n# Object flags");
|
|
EMIT_OFFSET(OBJECT_FLAGS, Object, m_flags);
|
|
outln("const OBJECT_FLAG_HAS_MAGICAL_LENGTH = {}", Object::Flag::HasMagicalLengthProperty);
|
|
outln("const OBJECT_FLAG_MAY_INTERFERE = {}", Object::Flag::MayInterfereWithIndexedPropertyAccess);
|
|
outln("const OBJECT_FLAG_IS_TYPED_ARRAY = {}", Object::Flag::IsTypedArray);
|
|
outln("const OBJECT_FLAG_IS_FUNCTION = {}", Object::Flag::IsFunction);
|
|
|
|
// Shape layout
|
|
outln("\n# Shape layout");
|
|
EMIT_OFFSET(SHAPE_PROTOTYPE, Shape, m_prototype);
|
|
EMIT_OFFSET(SHAPE_DICTIONARY_GENERATION, Shape, m_dictionary_generation);
|
|
EMIT_SIZEOF(SHAPE_SIZE, Shape);
|
|
|
|
// PropertyLookupCache layout
|
|
outln("\n# PropertyLookupCache layout");
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRIES, PropertyLookupCache, entries);
|
|
EMIT_SIZEOF(PROPERTY_LOOKUP_CACHE_SIZE, PropertyLookupCache);
|
|
|
|
// PropertyLookupCache::Entry layout
|
|
outln("\n# PropertyLookupCache::Entry layout");
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_PROPERTY_OFFSET, PropertyLookupCache::Entry, property_offset);
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_DICTIONARY_GENERATION, PropertyLookupCache::Entry, shape_dictionary_generation);
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_FROM_SHAPE, PropertyLookupCache::Entry, from_shape);
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_SHAPE, PropertyLookupCache::Entry, shape);
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE, PropertyLookupCache::Entry, prototype);
|
|
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE_CHAIN_VALIDITY, PropertyLookupCache::Entry, prototype_chain_validity);
|
|
EMIT_SIZEOF(PROPERTY_LOOKUP_CACHE_ENTRY_SIZE, PropertyLookupCache::Entry);
|
|
|
|
// Composite offsets for entry[0] within a PropertyLookupCache
|
|
outln("\n# Entry[0] offsets within PropertyLookupCache");
|
|
auto plc_entries = offsetof(PropertyLookupCache, entries);
|
|
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET = {}", plc_entries + offsetof(PropertyLookupCache::Entry, property_offset));
|
|
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION = {}", plc_entries + offsetof(PropertyLookupCache::Entry, shape_dictionary_generation));
|
|
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE = {}", plc_entries + offsetof(PropertyLookupCache::Entry, shape));
|
|
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE = {}", plc_entries + offsetof(PropertyLookupCache::Entry, prototype));
|
|
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE_CHAIN_VALIDITY = {}", plc_entries + offsetof(PropertyLookupCache::Entry, prototype_chain_validity));
|
|
|
|
// Executable layout
|
|
outln("\n# Executable layout");
|
|
EMIT_OFFSET(EXECUTABLE_PROPERTY_LOOKUP_CACHES, Executable, property_lookup_caches);
|
|
|
|
// ExecutionContext layout
|
|
outln("\n# ExecutionContext layout");
|
|
EMIT_OFFSET(EXECUTION_CONTEXT_EXECUTABLE, ExecutionContext, executable);
|
|
EMIT_OFFSET(EXECUTION_CONTEXT_REALM, ExecutionContext, realm);
|
|
EMIT_OFFSET(EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT, ExecutionContext, lexical_environment);
|
|
EMIT_OFFSET(EXECUTION_CONTEXT_CALLER_FRAME, ExecutionContext, caller_frame);
|
|
EMIT_OFFSET(EXECUTION_CONTEXT_PROGRAM_COUNTER, ExecutionContext, program_counter);
|
|
EMIT_SIZEOF(SIZEOF_EXECUTION_CONTEXT, ExecutionContext);
|
|
|
|
// Realm layout
|
|
outln("\n# Realm layout");
|
|
EMIT_OFFSET(REALM_GLOBAL_OBJECT, Realm, m_global_object);
|
|
EMIT_OFFSET(REALM_GLOBAL_DECLARATIVE_ENVIRONMENT, Realm, m_global_declarative_environment);
|
|
|
|
// Interpreter layout
|
|
outln("\n# Interpreter layout");
|
|
EMIT_OFFSET(INTERPRETER_RUNNING_EXECUTION_CONTEXT, Interpreter, m_running_execution_context);
|
|
|
|
// IndexedPropertyStorage layout
|
|
outln("\n# IndexedPropertyStorage layout");
|
|
EMIT_OFFSET(INDEXED_PROPERTY_STORAGE_ARRAY_SIZE, IndexedPropertyStorage, m_array_size);
|
|
EMIT_OFFSET(INDEXED_PROPERTY_STORAGE_IS_SIMPLE, IndexedPropertyStorage, m_is_simple_storage);
|
|
|
|
// SimpleIndexedPropertyStorage layout
|
|
outln("\n# SimpleIndexedPropertyStorage layout");
|
|
EMIT_OFFSET(SIMPLE_INDEXED_PROPERTY_STORAGE_PACKED_ELEMENTS, SimpleIndexedPropertyStorage, m_packed_elements);
|
|
|
|
// Vector<Value> layout (used for m_packed_elements and bytecode)
|
|
outln("\n# Vector<Value> layout");
|
|
{
|
|
Vector<Value> v;
|
|
auto base = reinterpret_cast<uintptr_t>(&v);
|
|
auto vec_data = reinterpret_cast<uintptr_t>(&v.m_metadata.outline_buffer) - base;
|
|
auto vec_size = reinterpret_cast<uintptr_t>(&v.m_size) - base;
|
|
outln("const VECTOR_DATA = {}", vec_data);
|
|
outln("const VECTOR_SIZE = {}", vec_size);
|
|
|
|
// Composite offsets for SimpleIndexedPropertyStorage.m_packed_elements data pointer
|
|
outln("const SIMPLE_INDEXED_PROPERTY_STORAGE_PACKED_DATA = {}", offsetof(SimpleIndexedPropertyStorage, m_packed_elements) + vec_data);
|
|
// Composite offset for Executable.bytecode data pointer
|
|
outln("const EXECUTABLE_BYTECODE_DATA = {}", offsetof(Executable, bytecode) + vec_data);
|
|
}
|
|
|
|
// PrototypeChainValidity layout
|
|
outln("\n# PrototypeChainValidity layout");
|
|
EMIT_OFFSET(PROTOTYPE_CHAIN_VALIDITY_VALID, PrototypeChainValidity, m_valid);
|
|
|
|
// DeclarativeEnvironment layout
|
|
outln("\n# DeclarativeEnvironment layout");
|
|
EMIT_OFFSET(DECLARATIVE_ENVIRONMENT_SERIAL, DeclarativeEnvironment, m_environment_serial_number);
|
|
|
|
// GlobalVariableCache layout
|
|
outln("\n# GlobalVariableCache layout");
|
|
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_ENVIRONMENT_SERIAL, GlobalVariableCache, environment_serial_number);
|
|
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_ENVIRONMENT_BINDING_INDEX, GlobalVariableCache, environment_binding_index);
|
|
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_HAS_ENVIRONMENT_BINDING, GlobalVariableCache, has_environment_binding_index);
|
|
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_IN_MODULE_ENVIRONMENT, GlobalVariableCache, in_module_environment);
|
|
EMIT_SIZEOF(GLOBAL_VARIABLE_CACHE_SIZE, GlobalVariableCache);
|
|
|
|
// Builtin enum values
|
|
outln("\n# Builtin enum values");
|
|
outln("const BUILTIN_MATH_ABS = {}", static_cast<u8>(Bytecode::Builtin::MathAbs));
|
|
outln("const BUILTIN_MATH_FLOOR = {}", static_cast<u8>(Bytecode::Builtin::MathFloor));
|
|
outln("const BUILTIN_MATH_CEIL = {}", static_cast<u8>(Bytecode::Builtin::MathCeil));
|
|
outln("const BUILTIN_MATH_ROUND = {}", static_cast<u8>(Bytecode::Builtin::MathRound));
|
|
outln("const BUILTIN_MATH_SQRT = {}", static_cast<u8>(Bytecode::Builtin::MathSqrt));
|
|
outln("const BUILTIN_MATH_EXP = {}", static_cast<u8>(Bytecode::Builtin::MathExp));
|
|
|
|
// FunctionObject layout
|
|
outln("\n# FunctionObject layout");
|
|
EMIT_OFFSET(FUNCTION_OBJECT_BUILTIN, FunctionObject, m_builtin);
|
|
{
|
|
// Optional<Builtin> has a value byte and a has_value byte.
|
|
auto base = offsetof(FunctionObject, m_builtin);
|
|
// Optional<Builtin> stores value at offset 0, has_value bool at offset 1.
|
|
// Verify this assumption at compile time:
|
|
Optional<Bytecode::Builtin> opt_test { Bytecode::Builtin::MathFloor };
|
|
auto const* raw = reinterpret_cast<u8 const*>(&opt_test);
|
|
VERIFY(raw[0] == static_cast<u8>(Bytecode::Builtin::MathFloor));
|
|
VERIFY(raw[1] == 1); // has_value
|
|
outln("const FUNCTION_OBJECT_BUILTIN_VALUE = {}", base);
|
|
outln("const FUNCTION_OBJECT_BUILTIN_HAS_VALUE = {}", base + 1);
|
|
}
|
|
|
|
// Environment layout
|
|
outln("\n# Environment layout");
|
|
EMIT_OFFSET(ENVIRONMENT_SCREWED_BY_EVAL, Environment, m_permanently_screwed_by_eval);
|
|
EMIT_OFFSET(ENVIRONMENT_OUTER, Environment, m_outer_environment);
|
|
|
|
// DeclarativeEnvironment / Binding layout
|
|
outln("\n# DeclarativeEnvironment / Binding layout");
|
|
EMIT_OFFSET(DECLARATIVE_ENVIRONMENT_BINDINGS, DeclarativeEnvironment, m_bindings);
|
|
outln("const BINDING_VALUE = {}", offsetof(DeclarativeEnvironment::Binding, value));
|
|
outln("const BINDING_STRICT = {}", offsetof(DeclarativeEnvironment::Binding, strict));
|
|
outln("const BINDING_MUTABLE = {}", offsetof(DeclarativeEnvironment::Binding, mutable_));
|
|
outln("const BINDING_INITIALIZED = {}", offsetof(DeclarativeEnvironment::Binding, initialized));
|
|
outln("const SIZEOF_BINDING = {}", sizeof(DeclarativeEnvironment::Binding));
|
|
|
|
// Vector<Binding> layout: m_size(0), m_capacity(8), m_metadata.outline_buffer(16)
|
|
outln("const BINDINGS_DATA_PTR = {}", offsetof(DeclarativeEnvironment, m_bindings) + sizeof(size_t) * 2);
|
|
|
|
// EnvironmentCoordinate layout
|
|
outln("\n# EnvironmentCoordinate layout");
|
|
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);
|
|
|
|
// TypedArrayBase layout
|
|
outln("\n# TypedArrayBase layout");
|
|
EMIT_OFFSET(TYPED_ARRAY_ELEMENT_SIZE, TypedArrayBase, m_element_size);
|
|
EMIT_OFFSET(TYPED_ARRAY_ARRAY_LENGTH, TypedArrayBase, m_array_length);
|
|
EMIT_OFFSET(TYPED_ARRAY_BYTE_OFFSET, TypedArrayBase, m_byte_offset);
|
|
EMIT_OFFSET(TYPED_ARRAY_KIND, TypedArrayBase, m_kind);
|
|
EMIT_OFFSET(TYPED_ARRAY_VIEWED_BUFFER, TypedArrayBase, m_viewed_array_buffer);
|
|
|
|
// ByteLength (Variant<Auto, Detached, u32>) layout
|
|
outln("\n# ByteLength layout");
|
|
{
|
|
// For Variant<Auto, Detached, u32>: u32 index 2 means it holds a u32
|
|
outln("const BYTE_LENGTH_U32_INDEX = 2");
|
|
EMIT_SIZEOF(BYTE_LENGTH_SIZE, ByteLength);
|
|
// Composite: array_length value (u32) and index byte within TypedArrayBase
|
|
auto ta_al = offsetof(TypedArrayBase, m_array_length);
|
|
// Variant<Auto, Detached, u32> stores m_data[4] then m_index (u8)
|
|
outln("const TYPED_ARRAY_ARRAY_LENGTH_VALUE = {}", ta_al); // u32 data at start
|
|
outln("const TYPED_ARRAY_ARRAY_LENGTH_INDEX = {}", ta_al + 4); // index byte after 4 bytes of data
|
|
}
|
|
|
|
// ArrayBuffer layout
|
|
outln("\n# ArrayBuffer layout");
|
|
EMIT_OFFSET(ARRAY_BUFFER_DATA_BLOCK, ArrayBuffer, m_data_block);
|
|
{
|
|
auto ab_data_block = offsetof(ArrayBuffer, m_data_block);
|
|
auto db_byte_buffer = offsetof(DataBlock, byte_buffer);
|
|
outln("const ARRAY_BUFFER_BYTE_BUFFER = {}", ab_data_block + db_byte_buffer);
|
|
outln("const ARRAY_BUFFER_BYTE_BUFFER_OFFSET = {}", ab_data_block + db_byte_buffer);
|
|
// Find the actual offset of the Variant index byte by creating a known variant
|
|
// and scanning for the index value.
|
|
{
|
|
decltype(DataBlock::byte_buffer) v_empty; // index = 0 (Empty)
|
|
decltype(DataBlock::byte_buffer) v_bb = ByteBuffer {}; // index = 1 (ByteBuffer)
|
|
auto const* p0 = reinterpret_cast<u8 const*>(&v_empty);
|
|
auto const* p1 = reinterpret_cast<u8 const*>(&v_bb);
|
|
size_t index_offset = 0;
|
|
for (size_t i = sizeof(v_empty); i-- > 0;) {
|
|
if (p0[i] == 0 && p1[i] == 1) {
|
|
index_offset = i;
|
|
break;
|
|
}
|
|
}
|
|
outln("const ARRAY_BUFFER_BYTE_BUFFER_VARIANT_INDEX = {}", ab_data_block + db_byte_buffer + index_offset);
|
|
}
|
|
outln("const ARRAY_BUFFER_BYTE_BUFFER_BYTEBUFFER_INDEX = 1");
|
|
auto bb_start = ab_data_block + db_byte_buffer;
|
|
outln("const ARRAY_BUFFER_BYTE_BUFFER_INLINE = {}", bb_start + offsetof(ByteBuffer, m_inline));
|
|
outln("const ARRAY_BUFFER_BYTE_BUFFER_OUTLINE_POINTER = {}", bb_start); // m_outline_buffer at offset 0 in union
|
|
}
|
|
|
|
// ArrayBuffer: check if buffer is fixed-length (not resizable)
|
|
{
|
|
outln("const ARRAY_BUFFER_IS_FIXED_LENGTH_OFFSET = {}",
|
|
offsetof(ArrayBuffer, m_data_block) + sizeof(DataBlock) + sizeof(size_t));
|
|
}
|
|
|
|
// TypedArrayBase::Kind enum values
|
|
outln("\n# TypedArrayBase::Kind values");
|
|
outln("const TYPED_ARRAY_KIND_UINT8 = {}", static_cast<u8>(TypedArrayBase::Kind::Uint8Array));
|
|
outln("const TYPED_ARRAY_KIND_UINT8_CLAMPED = {}", static_cast<u8>(TypedArrayBase::Kind::Uint8ClampedArray));
|
|
outln("const TYPED_ARRAY_KIND_UINT16 = {}", static_cast<u8>(TypedArrayBase::Kind::Uint16Array));
|
|
outln("const TYPED_ARRAY_KIND_UINT32 = {}", static_cast<u8>(TypedArrayBase::Kind::Uint32Array));
|
|
outln("const TYPED_ARRAY_KIND_INT8 = {}", static_cast<u8>(TypedArrayBase::Kind::Int8Array));
|
|
outln("const TYPED_ARRAY_KIND_INT16 = {}", static_cast<u8>(TypedArrayBase::Kind::Int16Array));
|
|
outln("const TYPED_ARRAY_KIND_INT32 = {}", static_cast<u8>(TypedArrayBase::Kind::Int32Array));
|
|
outln("const TYPED_ARRAY_KIND_FLOAT32 = {}", static_cast<u8>(TypedArrayBase::Kind::Float32Array));
|
|
outln("const TYPED_ARRAY_KIND_FLOAT64 = {}", static_cast<u8>(TypedArrayBase::Kind::Float64Array));
|
|
|
|
// Value tags
|
|
outln("\n# Value tags");
|
|
outln("const OBJECT_TAG = 0x{:X}", static_cast<u64>(OBJECT_TAG));
|
|
outln("const STRING_TAG = 0x{:X}", static_cast<u64>(STRING_TAG));
|
|
outln("const SYMBOL_TAG = 0x{:X}", static_cast<u64>(SYMBOL_TAG));
|
|
outln("const BIGINT_TAG = 0x{:X}", static_cast<u64>(BIGINT_TAG));
|
|
outln("const ACCESSOR_TAG = 0x{:X}", static_cast<u64>(ACCESSOR_TAG));
|
|
outln("const INT32_TAG = 0x{:X}", static_cast<u64>(INT32_TAG));
|
|
outln("const BOOLEAN_TAG = 0x{:X}", static_cast<u64>(BOOLEAN_TAG));
|
|
outln("const UNDEFINED_TAG = 0x{:X}", static_cast<u64>(UNDEFINED_TAG));
|
|
outln("const NULL_TAG = 0x{:X}", static_cast<u64>(NULL_TAG));
|
|
|
|
// Shifted value constants
|
|
outln("\n# Shifted value constants");
|
|
outln("const EMPTY_VALUE = 0x{:X}", static_cast<u64>(EMPTY_TAG << GC::TAG_SHIFT));
|
|
outln("const INT32_TAG_SHIFTED = 0x{:X}", static_cast<u64>(INT32_TAG << GC::TAG_SHIFT));
|
|
outln("const BOOLEAN_TRUE = 0x{:X}", static_cast<u64>((BOOLEAN_TAG << GC::TAG_SHIFT) | 1));
|
|
outln("const BOOLEAN_FALSE = 0x{:X}", static_cast<u64>(BOOLEAN_TAG << GC::TAG_SHIFT));
|
|
outln("const UNDEFINED_SHIFTED = 0x{:X}", static_cast<u64>(UNDEFINED_TAG << GC::TAG_SHIFT));
|
|
outln("const EMPTY_TAG_SHIFTED = 0x{:X}", static_cast<u64>(EMPTY_TAG << GC::TAG_SHIFT));
|
|
outln("const NAN_BASE_TAG = 0x{:X}", static_cast<u64>(GC::BASE_TAG));
|
|
outln("const CANON_NAN_BITS = 0x{:X}", static_cast<u64>(GC::CANON_NAN_BITS));
|
|
outln("const DOUBLE_ONE = 0x{:X}", bit_cast<u64>(1.0));
|
|
outln("const NEGATIVE_ZERO = 0x{:X}", static_cast<u64>(NEGATIVE_ZERO_BITS));
|
|
outln("const CELL_TAG_SHIFTED = 0x{:X}", static_cast<u64>(GC::SHIFTED_IS_CELL_PATTERN));
|
|
|
|
outln("const THIS_VALUE_REG_OFFSET = {}", static_cast<size_t>(Register::this_value().index()) * sizeof(Value));
|
|
|
|
return 0;
|
|
}
|