LibWeb+LibWasm: Keep WebAssembly cache storage stable

These need to stay in place across rehashes so we can avoid querying
their address on every collection cycle.
This commit is contained in:
Ali Mohammad Pur 2026-06-13 13:21:40 +02:00 committed by Ali Mohammad Pur
parent 530c95fde5
commit b3f3d68291
5 changed files with 17 additions and 18 deletions

View file

@ -962,6 +962,11 @@ public:
return *m_heap; return *m_heap;
} }
// For embedders that decide on a (shared) heap after constructing the machine. Must
// happen before any code runs.
bool has_heap() const { return m_heap != nullptr; }
void adopt_heap(GC::Heap&);
// Validate a module; permanently sets the module's validity status. // Validate a module; permanently sets the module's validity status.
ErrorOr<void, ValidationError> validate(Module&, Optional<CompileCacheConfig> cache_config = {}, CompileToNative = CompileToNative::Yes); ErrorOr<void, ValidationError> validate(Module&, Optional<CompileCacheConfig> cache_config = {}, CompileToNative = CompileToNative::Yes);
// Load and instantiate a module, and link it into this interpreter. // Load and instantiate a module, and link it into this interpreter.
@ -1004,7 +1009,6 @@ private:
Optional<InstantiationError> allocate_all_initial_phase(Module const&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values, Vector<Value>& table_initial_values, Vector<FunctionAddress>& own_functions); Optional<InstantiationError> allocate_all_initial_phase(Module const&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values, Vector<Value>& table_initial_values, Vector<FunctionAddress>& own_functions);
Optional<InstantiationError> allocate_all_final_phase(Module const&, ModuleInstance&, Vector<Vector<Reference>>& elements); Optional<InstantiationError> allocate_all_final_phase(Module const&, ModuleInstance&, Vector<Vector<Reference>>& elements);
void adopt_heap(GC::Heap&);
void create_own_heap(); void create_own_heap();
class RootsProvider final : public GC::ConservativeRangeProvider { class RootsProvider final : public GC::ConservativeRangeProvider {

View file

@ -1638,8 +1638,7 @@ VALIDATE_INSTRUCTION(select_typed)
return Errors::invalid("select types"sv, "exactly one type"sv, required_types); return Errors::invalid("select types"sv, "exactly one type"sv, required_types);
// https://webassembly.github.io/spec/core/valid/instructions.html#parametric-instructions // https://webassembly.github.io/spec/core/valid/instructions.html#parametric-instructions
// select t: valid with [t t i32] -> [t] if the value type t is valid; both operands must // select t: valid with [t t i32] -> [t] if the value type t is valid; both operands must match the annotated type.
// match the annotated type.
TRY(validate(required_types.first())); TRY(validate(required_types.first()));
TRY(stack.take<ValueType::I32>()); TRY(stack.take<ValueType::I32>());
TRY(stack.take(required_types.first())); TRY(stack.take(required_types.first()));
@ -1781,8 +1780,6 @@ VALIDATE_INSTRUCTION(table_copy)
auto lhs_table = TRY(validate(args.lhs)); auto lhs_table = TRY(validate(args.lhs));
auto rhs_table = TRY(validate(args.rhs)); auto rhs_table = TRY(validate(args.rhs));
// https://webassembly.github.io/spec/core/valid/instructions.html#table-instructions
// table.copy x y: the source table's reference type rt2 must match the destination's rt1.
if (!matches_reference_type(rhs_table.element_type(), lhs_table.element_type(), m_context.type_context())) if (!matches_reference_type(rhs_table.element_type(), lhs_table.element_type(), m_context.type_context()))
return Errors::non_conforming_types("table.copy"sv, lhs_table.element_type(), rhs_table.element_type()); return Errors::non_conforming_types("table.copy"sv, lhs_table.element_type(), rhs_table.element_type());
@ -1806,9 +1803,6 @@ VALIDATE_INSTRUCTION(table_init)
auto& element_type = m_context.elements[args.element_index.value()]; auto& element_type = m_context.elements[args.element_index.value()];
// https://webassembly.github.io/spec/core/valid/instructions.html#table-instructions
// table.init x y: "The element segment C.elems[y] must match the reference type rt" of the
// table C.tables[x].
if (!matches_reference_type(element_type, table.element_type(), m_context.type_context())) if (!matches_reference_type(element_type, table.element_type(), m_context.type_context()))
return Errors::non_conforming_types("table.init"sv, table.element_type(), element_type); return Errors::non_conforming_types("table.init"sv, table.element_type(), element_type);
@ -2673,9 +2667,6 @@ VALIDATE_INSTRUCTION(call_indirect)
auto table = TRY(validate(args.table)); auto table = TRY(validate(args.table));
TRY(validate(args.type)); TRY(validate(args.type));
// https://webassembly.github.io/spec/core/valid/instructions.html#control-instructions
// call_indirect x y: "The table C.tables[x] must be of the form (at lim rt), and rt must
// match (ref null func)."
if (!matches_reference_type(table.element_type(), ValueType(ValueType::FunctionReference), m_context.type_context())) if (!matches_reference_type(table.element_type(), ValueType(ValueType::FunctionReference), m_context.type_context()))
return Errors::invalid("table element type for call.indirect"sv, "a function reference"sv, table.element_type()); return Errors::invalid("table element type for call.indirect"sv, "a function reference"sv, table.element_type());
@ -2722,7 +2713,6 @@ VALIDATE_INSTRUCTION(return_call_indirect)
TRY(validate(args.type)); TRY(validate(args.type));
auto& table = m_context.tables[args.table.value()]; auto& table = m_context.tables[args.table.value()];
// See call_indirect: rt must match (ref null func).
if (!matches_reference_type(table.element_type(), ValueType(ValueType::FunctionReference), m_context.type_context())) if (!matches_reference_type(table.element_type(), ValueType(ValueType::FunctionReference), m_context.type_context()))
return Errors::invalid("table element type for call.indirect"sv, "a function reference"sv, table.element_type()); return Errors::invalid("table element type for call.indirect"sv, "a function reference"sv, table.element_type());

View file

@ -1573,8 +1573,6 @@ ParseResult<ElementSection::Element> ElementSection::Element::parse(ConstrainedS
// https://webassembly.github.io/spec/core/binary/modules.html#element-section // https://webassembly.github.io/spec/core/binary/modules.html#element-section
// elemkind ::= 0x00 => (ref func) // elemkind ::= 0x00 => (ref func)
// Segments listing function indices have the non-nullable type (ref func) (flags 0-3);
// the flag-4 expression shorthand has type funcref, i.e. (ref null func).
auto type = has_exprs ? ValueType(ValueType::FunctionReference) : ValueType(ValueType::FunctionReference, false); auto type = has_exprs ? ValueType(ValueType::FunctionReference) : ValueType(ValueType::FunctionReference, false);
if (has_passive || has_explicit_index) { if (has_passive || has_explicit_index) {
if (has_exprs) { if (has_exprs) {

View file

@ -55,15 +55,17 @@ static GC::Ref<WebIDL::Promise> compile_potential_webassembly_response(JS::VM&,
namespace Detail { namespace Detail {
static GC::WeakHashMap<JS::Object, WebAssemblyCache>& caches() // The caches hold AbstractMachines whose gc-roots providers keep pointers into them, so they
// live behind OwnPtrs and never move on rehash.
static GC::WeakHashMap<JS::Object, NonnullOwnPtr<WebAssemblyCache>>& caches()
{ {
static NeverDestroyed<GC::WeakHashMap<JS::Object, WebAssemblyCache>> caches; static NeverDestroyed<GC::WeakHashMap<JS::Object, NonnullOwnPtr<WebAssemblyCache>>> caches;
return *caches; return *caches;
} }
WebAssemblyCache& get_cache(JS::Realm& realm) WebAssemblyCache& get_cache(JS::Realm& realm)
{ {
return caches().ensure(realm.global_object()); return *caches().ensure(realm.global_object(), [] { return make<WebAssemblyCache>(); });
} }
} }
@ -72,7 +74,7 @@ void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
{ {
auto& global_object = HTML::relevant_global_object(object); auto& global_object = HTML::relevant_global_object(object);
if (auto maybe_cache = Detail::caches().get(global_object); maybe_cache.has_value()) { if (auto maybe_cache = Detail::caches().get(global_object); maybe_cache.has_value()) {
auto& cache = maybe_cache.value(); auto& cache = *maybe_cache.value();
visitor.visit(cache.function_instances()); visitor.visit(cache.function_instances());
visitor.visit(cache.imported_objects()); visitor.visit(cache.imported_objects());
visitor.visit(cache.extern_values()); visitor.visit(cache.extern_values());

View file

@ -49,7 +49,12 @@ struct CompiledWebAssemblyModule : public RefCounted<CompiledWebAssemblyModule>
}; };
class WebAssemblyCache { class WebAssemblyCache {
AK_MAKE_NONCOPYABLE(WebAssemblyCache);
AK_MAKE_NONMOVABLE(WebAssemblyCache);
public: public:
WebAssemblyCache() = default;
void add_compiled_module(NonnullRefPtr<CompiledWebAssemblyModule> module) { m_compiled_modules.append(module); } void add_compiled_module(NonnullRefPtr<CompiledWebAssemblyModule> module) { m_compiled_modules.append(module); }
void add_function_instance(Wasm::FunctionAddress address, GC::Ptr<JS::NativeFunction> function) { m_function_instances.set(address, function); } void add_function_instance(Wasm::FunctionAddress address, GC::Ptr<JS::NativeFunction> function) { m_function_instances.set(address, function); }
void add_imported_object(GC::Ptr<JS::Object> object) { m_imported_objects.set(object); } void add_imported_object(GC::Ptr<JS::Object> object) { m_imported_objects.set(object); }