LibWasm: Implement the spec's type matching relations
This commit is contained in:
parent
3ca552b37b
commit
42c0a430f5
10 changed files with 773 additions and 90 deletions
|
|
@ -288,13 +288,16 @@ Optional<FunctionAddress> Store::allocate(ModuleInstance& instance, Module const
|
|||
return {};
|
||||
|
||||
auto& type = instance.types()[type_index.value()].function();
|
||||
m_functions.empend(WasmFunction { type, instance, module, code });
|
||||
auto const* defined_type = instance.canonical_types()[type_index.value()];
|
||||
m_functions.empend(WasmFunction { type, defined_type, instance, module, code });
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<FunctionAddress> Store::allocate(HostFunction&& function)
|
||||
{
|
||||
FunctionAddress address { m_functions.size() };
|
||||
if (!function.defined_type())
|
||||
function.set_defined_type(canonicalize_type(TypeSection::Type { FunctionType { function.type() } }, TypeContext {}));
|
||||
m_functions.empend(HostFunction { move(function) });
|
||||
return address;
|
||||
}
|
||||
|
|
@ -346,10 +349,10 @@ Optional<ElementAddress> Store::allocate(ValueType const& type, Vector<Reference
|
|||
return address;
|
||||
}
|
||||
|
||||
Optional<TagAddress> Store::allocate(FunctionType const& type, TagType::Flags flags)
|
||||
Optional<TagAddress> Store::allocate(FunctionType const& type, DefinedType const* defined_type, TagType::Flags flags)
|
||||
{
|
||||
TagAddress address { m_tags.size() };
|
||||
m_tags.append({ type, flags });
|
||||
m_tags.append({ type, defined_type, flags });
|
||||
return address;
|
||||
}
|
||||
|
||||
|
|
@ -480,6 +483,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
auto& main_module_instance = *main_module_instance_pointer;
|
||||
|
||||
main_module_instance.types() = module.type_section().types();
|
||||
main_module_instance.canonical_types() = module.canonical_types();
|
||||
|
||||
Vector<Value> global_values;
|
||||
Vector<Vector<Reference>> elements;
|
||||
|
|
@ -488,6 +492,9 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
|
||||
auxiliary_instance.cached_minimum_call_record_allocation_size = module.minimum_call_record_allocation_size();
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/modules.html#instantiation
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#external-types
|
||||
TypeContext const import_type_context { module.canonical_types().span() };
|
||||
for (auto [i, import_] : enumerate(module.import_section().imports())) {
|
||||
auto extern_ = externs.at(i);
|
||||
auto invalid = import_.description().visit(
|
||||
|
|
@ -495,7 +502,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
if (!extern_.has<MemoryAddress>())
|
||||
return "Expected memory import"sv;
|
||||
auto other_mem_type = m_store.get(extern_.get<MemoryAddress>())->type();
|
||||
if (other_mem_type.limits().is_subset_of(mem_type.limits()))
|
||||
if (matches_memory_type(other_mem_type, mem_type))
|
||||
return {};
|
||||
return ByteString::formatted("Memory import and extern do not match: {}-{} vs {}-{}", mem_type.limits().min(), mem_type.limits().max(), other_mem_type.limits().min(), other_mem_type.limits().max());
|
||||
},
|
||||
|
|
@ -503,8 +510,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
if (!extern_.has<TableAddress>())
|
||||
return "Expected table import"sv;
|
||||
auto other_table_type = m_store.get(extern_.get<TableAddress>())->type();
|
||||
if (table_type.element_type() == other_table_type.element_type()
|
||||
&& other_table_type.limits().is_subset_of(table_type.limits()))
|
||||
if (matches_table_type(other_table_type, table_type, TypeContext {}, import_type_context))
|
||||
return {};
|
||||
|
||||
return ByteString::formatted("Table import and extern do not match: {}-{} vs {}-{}", table_type.limits().min(), table_type.limits().max(), other_table_type.limits().min(), other_table_type.limits().max());
|
||||
|
|
@ -513,8 +519,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
if (!extern_.has<GlobalAddress>())
|
||||
return "Expected global import"sv;
|
||||
auto other_global_type = m_store.get(extern_.get<GlobalAddress>())->type();
|
||||
if (global_type.type() == other_global_type.type()
|
||||
&& global_type.is_mutable() == other_global_type.is_mutable())
|
||||
if (matches_global_type(other_global_type, global_type, TypeContext {}, import_type_context))
|
||||
return {};
|
||||
return "Global import and extern do not match"sv;
|
||||
},
|
||||
|
|
@ -535,8 +540,14 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
if (other_tag_instance->flags() != type.flags())
|
||||
return "Tag import and extern do not match"sv;
|
||||
|
||||
auto& this_type = module.type_section().types()[type.type().value()];
|
||||
auto const* defined_type = module.canonical_types()[type.type().value()];
|
||||
if (other_tag_instance->defined_type() && defined_type) {
|
||||
if (!matches_tag_type(*other_tag_instance->defined_type(), *defined_type))
|
||||
return "Tag import and extern do not match"sv;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& this_type = module.type_section().types()[type.type().value()];
|
||||
if (other_tag_instance->type().parameters() != this_type.function().parameters())
|
||||
return "Tag import and extern do not match"sv;
|
||||
return {};
|
||||
|
|
@ -544,12 +555,11 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
[&](TypeIndex type_index) -> Optional<ByteString> {
|
||||
if (!extern_.has<FunctionAddress>())
|
||||
return "Expected function import"sv;
|
||||
auto other_type = m_store.get(extern_.get<FunctionAddress>())->visit([&](WasmFunction const& wasm_func) { return wasm_func.type(); }, [&](HostFunction const& host_func) { return host_func.type(); });
|
||||
auto& type = module.type_section().types()[type_index.value()].function();
|
||||
if (type.results() != other_type.results())
|
||||
return ByteString::formatted("Function import and extern do not match, results: {} vs {}", type.results(), other_type.results());
|
||||
if (type.parameters() != other_type.parameters())
|
||||
return ByteString::formatted("Function import and extern do not match, parameters: {} vs {}", type.parameters(), other_type.parameters());
|
||||
auto const* other_defined_type = m_store.get(extern_.get<FunctionAddress>())->visit([&](WasmFunction const& wasm_func) { return wasm_func.defined_type(); }, [&](HostFunction const& host_func) { return host_func.defined_type(); });
|
||||
auto const* defined_type = module.canonical_types()[type_index.value()];
|
||||
VERIFY(other_defined_type && defined_type);
|
||||
if (!matches_defined_type(*other_defined_type, *defined_type))
|
||||
return ByteString::formatted("Function import and extern do not match: {} vs {}", module.type_section().types()[type_index.value()].name(), other_defined_type->sub_type().name());
|
||||
return {};
|
||||
});
|
||||
if (invalid.has_value())
|
||||
|
|
@ -758,8 +768,11 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
|
|||
|
||||
module_instance.functions().extend(own_functions);
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-clostype
|
||||
TypeContext const module_type_context { module.canonical_types().span() };
|
||||
|
||||
for (auto& table : module.table_section().tables()) {
|
||||
auto table_address = m_store.allocate(table.type());
|
||||
auto table_address = m_store.allocate(TableType { canonicalized(table.type().element_type(), module_type_context), table.type().limits() });
|
||||
if (!table_address.has_value())
|
||||
return InstantiationError { "Failed to allocate a table instance" };
|
||||
module_instance.tables().append(*table_address);
|
||||
|
|
@ -774,7 +787,7 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
|
|||
|
||||
size_t index = 0;
|
||||
for (auto& entry : module.global_section().entries()) {
|
||||
auto address = m_store.allocate(entry.type(), move(global_values[index]));
|
||||
auto address = m_store.allocate(GlobalType { canonicalized(entry.type().type(), module_type_context), entry.type().is_mutable() }, move(global_values[index]));
|
||||
VERIFY(address.has_value());
|
||||
module_instance.globals().append(*address);
|
||||
index++;
|
||||
|
|
@ -782,7 +795,8 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
|
|||
|
||||
for (auto& entry : module.tag_section().tags()) {
|
||||
auto& type = module.type_section().types()[entry.type().value()];
|
||||
auto address = m_store.allocate(type.function(), entry.flags());
|
||||
auto const* defined_type = module.canonical_types()[entry.type().value()];
|
||||
auto address = m_store.allocate(type.function(), defined_type, entry.flags());
|
||||
VERIFY(address.has_value());
|
||||
module_instance.tags().append(*address);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <AK/UFixedBigInt.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibWasm/Export.h>
|
||||
#include <LibWasm/TypeSystem.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
|
@ -364,6 +365,8 @@ public:
|
|||
ModuleInstance() = default;
|
||||
|
||||
auto& types() const { return m_types; }
|
||||
auto& canonical_types() const { return m_canonical_types; }
|
||||
auto& canonical_types() { return m_canonical_types; }
|
||||
auto& functions() const { return m_functions; }
|
||||
auto& tables() const { return m_tables; }
|
||||
auto& memories() const { return m_memories; }
|
||||
|
|
@ -391,6 +394,7 @@ public:
|
|||
|
||||
private:
|
||||
Vector<TypeSection::Type> m_types;
|
||||
Vector<DefinedType const*> m_canonical_types;
|
||||
Vector<TagType> m_tag_types;
|
||||
Vector<FunctionAddress> m_functions;
|
||||
Vector<TableAddress> m_tables;
|
||||
|
|
@ -407,8 +411,9 @@ private:
|
|||
|
||||
class WasmFunction {
|
||||
public:
|
||||
explicit WasmFunction(FunctionType const& type, ModuleInstance const& instance, Module const& module, CodeSection::Code const& code)
|
||||
explicit WasmFunction(FunctionType const& type, DefinedType const* defined_type, ModuleInstance const& instance, Module const& module, CodeSection::Code const& code)
|
||||
: m_type(type)
|
||||
, m_defined_type(defined_type)
|
||||
, m_module(module.make_weak_ptr())
|
||||
, m_module_instance(instance.make_weak_ptr<ModuleInstance const>())
|
||||
, m_code(&code)
|
||||
|
|
@ -416,7 +421,8 @@ public:
|
|||
}
|
||||
|
||||
auto& type() const { return m_type; }
|
||||
// Callers must have already verified the module is alive (e.g., via Store::get() returning non-null).
|
||||
// https://webassembly.github.io/spec/core/exec/runtime.html#function-instances
|
||||
DefinedType const* defined_type() const { return m_defined_type; }
|
||||
ModuleInstance const& module() const { return *m_module_instance.strong_ref(); }
|
||||
RefPtr<ModuleInstance const> try_module() const { return m_module_instance.strong_ref(); }
|
||||
auto& code() const { return *m_code; }
|
||||
|
|
@ -424,6 +430,7 @@ public:
|
|||
|
||||
private:
|
||||
FunctionType m_type;
|
||||
DefinedType const* m_defined_type { nullptr };
|
||||
WeakPtr<Module const> m_module;
|
||||
WeakPtr<ModuleInstance const> m_module_instance;
|
||||
CodeSection::Code const* m_code;
|
||||
|
|
@ -442,9 +449,14 @@ public:
|
|||
auto& type() const { return m_type; }
|
||||
auto& name() const { return m_name; }
|
||||
|
||||
// Interned on the store.
|
||||
DefinedType const* defined_type() const { return m_defined_type; }
|
||||
void set_defined_type(DefinedType const* defined_type) { m_defined_type = defined_type; }
|
||||
|
||||
private:
|
||||
AK::Function<Result(Configuration&, Span<Value>)> m_function;
|
||||
FunctionType m_type;
|
||||
DefinedType const* m_defined_type { nullptr };
|
||||
ByteString m_name;
|
||||
};
|
||||
|
||||
|
|
@ -641,17 +653,21 @@ private:
|
|||
|
||||
class TagInstance {
|
||||
public:
|
||||
TagInstance(FunctionType const& type, TagType::Flags flags)
|
||||
TagInstance(FunctionType const& type, DefinedType const* defined_type, TagType::Flags flags)
|
||||
: m_type(type)
|
||||
, m_defined_type(defined_type)
|
||||
, m_flags(flags)
|
||||
{
|
||||
}
|
||||
|
||||
auto& type() const { return m_type; }
|
||||
// https://webassembly.github.io/spec/core/exec/runtime.html#tag-instances
|
||||
DefinedType const* defined_type() const { return m_defined_type; }
|
||||
auto flags() const { return m_flags; }
|
||||
|
||||
private:
|
||||
FunctionType m_type;
|
||||
DefinedType const* m_defined_type { nullptr };
|
||||
TagType::Flags m_flags;
|
||||
};
|
||||
|
||||
|
|
@ -682,7 +698,7 @@ public:
|
|||
Optional<DataAddress> allocate_data(Vector<u8>);
|
||||
Optional<GlobalAddress> allocate(GlobalType const&, Value);
|
||||
Optional<ElementAddress> allocate(ValueType const&, Vector<Reference>);
|
||||
Optional<TagAddress> allocate(FunctionType const&, TagType::Flags);
|
||||
Optional<TagAddress> allocate(FunctionType const&, DefinedType const*, TagType::Flags);
|
||||
Optional<ExceptionAddress> allocate(TagInstance const&, Vector<Value>);
|
||||
|
||||
Module const* get_module_for(FunctionAddress);
|
||||
|
|
|
|||
|
|
@ -2403,10 +2403,10 @@ HANDLE_INSTRUCTION(call_indirect)
|
|||
auto& element = table_instance->elements()[index];
|
||||
TRAP_IN_LOOP_IF_NOT(element.ref().template has<Reference::Func>());
|
||||
auto address = element.ref().template get<Reference::Func>().address;
|
||||
auto const& type_actual = configuration.store().get(address)->visit([](auto& f) -> decltype(auto) { return f.type(); });
|
||||
auto const& type_expected = configuration.frame().module().types()[args.type.value()].unsafe_function();
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.parameters() == type_expected.parameters());
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results());
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-call-indirect-x-y
|
||||
auto const* type_actual = configuration.store().get(address)->visit([](auto& f) { return f.defined_type(); });
|
||||
auto const* type_expected = configuration.frame().module().canonical_types()[args.type.value()];
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual && matches_defined_type(*type_actual, *type_expected));
|
||||
|
||||
dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index, address.value());
|
||||
if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return)
|
||||
|
|
@ -2428,10 +2428,10 @@ HANDLE_INSTRUCTION(return_call_indirect)
|
|||
auto& element = table_instance->elements()[index];
|
||||
TRAP_IN_LOOP_IF_NOT(element.ref().template has<Reference::Func>());
|
||||
auto address = element.ref().template get<Reference::Func>().address;
|
||||
auto const& type_actual = configuration.store().get(address)->visit([](auto& f) -> decltype(auto) { return f.type(); });
|
||||
auto const& type_expected = configuration.frame().module().types()[args.type.value()].unsafe_function();
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.parameters() == type_expected.parameters());
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results());
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-call-indirect-x-y
|
||||
auto const* type_actual = configuration.store().get(address)->visit([](auto& f) { return f.defined_type(); });
|
||||
auto const* type_expected = configuration.frame().module().canonical_types()[args.type.value()];
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual && matches_defined_type(*type_actual, *type_expected));
|
||||
|
||||
configuration.label_stack().shrink(configuration.frame().label_index(), true);
|
||||
dbgln_if(WASM_TRACE_DEBUG, "tail call_indirect({} -> {})", index, address.value());
|
||||
|
|
@ -2462,10 +2462,10 @@ HANDLE_INSTRUCTION(call_ref)
|
|||
TRAP_IN_LOOP_IF_NOT(!reference.ref().template has<Reference::Null>());
|
||||
address = reference.ref().template get<Reference::Func>().address;
|
||||
}
|
||||
auto const& type_actual = configuration.store().get(address)->visit([](auto& f) -> decltype(auto) { return f.type(); });
|
||||
auto const& type_expected = configuration.frame().module().types()[type_index.value()].unsafe_function();
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.parameters() == type_expected.parameters());
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results());
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-call-ref-x
|
||||
auto const* type_actual = configuration.store().get(address)->visit([](auto& f) { return f.defined_type(); });
|
||||
auto const* type_expected = configuration.frame().module().canonical_types()[type_index.value()];
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual && matches_defined_type(*type_actual, *type_expected));
|
||||
|
||||
dbgln_if(WASM_TRACE_DEBUG, "call_ref({})", address.value());
|
||||
if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return)
|
||||
|
|
@ -2485,10 +2485,9 @@ HANDLE_INSTRUCTION(return_call_ref)
|
|||
TRAP_IN_LOOP_IF_NOT(!reference.ref().template has<Reference::Null>());
|
||||
address = reference.ref().template get<Reference::Func>().address;
|
||||
}
|
||||
auto const& type_actual = configuration.store().get(address)->visit([](auto& f) -> decltype(auto) { return f.type(); });
|
||||
auto const& type_expected = configuration.frame().module().types()[type_index.value()].unsafe_function();
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.parameters() == type_expected.parameters());
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results());
|
||||
auto const* type_actual = configuration.store().get(address)->visit([](auto& f) { return f.defined_type(); });
|
||||
auto const* type_expected = configuration.frame().module().canonical_types()[type_index.value()];
|
||||
TRAP_IN_LOOP_IF_NOT(type_actual && matches_defined_type(*type_actual, *type_expected));
|
||||
|
||||
configuration.label_stack().shrink(configuration.frame().label_index(), true);
|
||||
dbgln_if(WASM_TRACE_DEBUG, "tail call_ref({})", address.value());
|
||||
|
|
|
|||
|
|
@ -32,6 +32,17 @@ ErrorOr<void, ValidationError> Validator::validate(Module& module)
|
|||
m_context.types.extend(module.type_section().types());
|
||||
m_context.data_count = module.data_count_section().count();
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/modules.html#types
|
||||
// Intern the type section's recursive groups as defined types ("the defined type sequence
|
||||
// dt* is of the form roll*_x(rectype)") before anything else can refer to them; the rest of
|
||||
// the sub type validation rule runs in validate(TypeSection const&) below.
|
||||
{
|
||||
auto canonical_types = TRY(canonicalize_module_types(module.type_section()));
|
||||
m_context.canonical_types.extend(canonical_types);
|
||||
module.set_canonical_types(move(canonical_types));
|
||||
}
|
||||
TRY(validate(module.type_section()));
|
||||
|
||||
for (auto& import_ : module.import_section().imports()) {
|
||||
TRY(import_.description().visit(
|
||||
[&](TypeIndex const& index) -> ErrorOr<void, ValidationError> {
|
||||
|
|
@ -147,7 +158,6 @@ ErrorOr<void, ValidationError> Validator::validate(Module& module)
|
|||
TRY(validate(module.table_section()));
|
||||
TRY(validate(module.code_section()));
|
||||
TRY(validate(module.tag_section()));
|
||||
TRY(validate(module.type_section()));
|
||||
|
||||
for (auto& entry : module.code_section().functions())
|
||||
module.set_minimum_call_record_allocation_size(max(entry.func().body().compiled_instructions.max_call_rec_size, module.minimum_call_record_allocation_size()));
|
||||
|
|
@ -493,7 +503,11 @@ ErrorOr<void, ValidationError> Validator::validate(TypeSection::Type const& type
|
|||
if (supertype.is_final())
|
||||
return Errors::invalid("supertype, must not be final"sv);
|
||||
|
||||
// FIXME: - The composite type comptype matches the composite type comptype'.
|
||||
// - The composite type comptype matches the composite type comptype'.
|
||||
auto const* defined_type = m_context.canonical_types[this_type_index];
|
||||
auto const* defined_supertype = m_context.canonical_types[supertype_index.value()];
|
||||
if (!matches_composite_type(defined_type->expansion(), defined_supertype->expansion(), TypeContext {}))
|
||||
return Errors::invalid("subtype, composite type must match its declared supertype"sv);
|
||||
}
|
||||
|
||||
return type.description().visit(
|
||||
|
|
@ -2432,6 +2446,46 @@ VALIDATE_INSTRUCTION(try_table)
|
|||
auto& args = instruction.arguments().get<Instruction::TryTableArgs>();
|
||||
auto block_type = TRY(validate(args.block_type));
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-try-table-xref-syntax-types-syntax-blocktype-mathit-blocktype-xref-syntax-instructions-syntax-catch-mathit-catch-ast-xref-syntax-instructions-syntax-instr-mathit-instr-ast
|
||||
// try_table bt catch* instr* is valid with [t1*] -> [t2*] if:
|
||||
// - The block type bt is valid as some instruction type [t1*] -> [t2*].
|
||||
// - For all catch in catch*: the catch clause catch is valid.
|
||||
// Note: the catch clauses are validated under C, without the try_table's own label.
|
||||
for (auto& catch_ : args.catches()) {
|
||||
// - The label C.labels[l] exists.
|
||||
auto label = catch_.target_label();
|
||||
TRY(validate(label));
|
||||
auto& target_label_type = m_frames[(m_frames.size() - 1) - label.value()].labels();
|
||||
|
||||
Vector<ValueType> expected_label_types;
|
||||
if (auto tag = catch_.matching_tag_index(); tag.has_value()) {
|
||||
// - The tag C.tags[x] exists.
|
||||
TRY(validate(tag.value()));
|
||||
auto tag_type = m_context.tags[tag->value()];
|
||||
TRY(validate(tag_type.type()));
|
||||
auto& type = m_context.types[tag_type.type().value()];
|
||||
|
||||
// - The expansion of C.tags[x] is (func t* -> ε).
|
||||
if (!type.is_function())
|
||||
return Errors::invalid("catch tag type"sv, "a function type"sv, type);
|
||||
auto& func = type.function();
|
||||
if (!func.results().is_empty())
|
||||
return Errors::invalid("catch tag type"sv, "no results"sv, func.results());
|
||||
|
||||
expected_label_types.extend(func.parameters());
|
||||
}
|
||||
|
||||
// catch x l: the result type t* matches the label C.labels[l].
|
||||
// catch_ref x l: the result type t* (ref exn) matches the label C.labels[l].
|
||||
// catch_all l: the result type ε matches the label C.labels[l].
|
||||
// catch_all_ref l: the result type (ref exn) matches the label C.labels[l].
|
||||
if (catch_.is_ref())
|
||||
expected_label_types.append(ValueType(ValueType::ExceptionReference, false));
|
||||
|
||||
if (!matches_result_types(expected_label_types.span(), target_label_type.span(), m_context.type_context()))
|
||||
return Errors::non_conforming_types("catch"sv, expected_label_types.span(), target_label_type.span());
|
||||
}
|
||||
|
||||
auto& parameters = block_type.parameters();
|
||||
for (size_t i = 1; i <= parameters.size(); ++i)
|
||||
TRY(stack.take(parameters[parameters.size() - i]));
|
||||
|
|
@ -2447,51 +2501,6 @@ VALIDATE_INSTRUCTION(try_table)
|
|||
for (auto& parameter : parameters)
|
||||
stack.append(parameter);
|
||||
|
||||
for (auto& catch_ : args.catches()) {
|
||||
auto label = catch_.target_label();
|
||||
TRY(validate(label));
|
||||
auto& target_label_type = m_frames[(m_frames.size() - 1) - label.value()].labels();
|
||||
|
||||
if (auto tag = catch_.matching_tag_index(); tag.has_value()) {
|
||||
TRY(validate(tag.value()));
|
||||
auto tag_type = m_context.tags[tag->value()];
|
||||
TRY(validate(tag_type.type()));
|
||||
auto& type = m_context.types[tag_type.type().value()];
|
||||
|
||||
if (!type.is_function())
|
||||
return Errors::invalid("catch type"sv, "a function type"sv, type);
|
||||
|
||||
auto& func = type.function();
|
||||
|
||||
if (!func.results().is_empty())
|
||||
return Errors::invalid("catch type"sv, "empty"sv, func.results());
|
||||
|
||||
Span<ValueType const> parameters_to_check = func.parameters().span();
|
||||
if (catch_.is_ref()) {
|
||||
// catch_ref x l
|
||||
auto& parameters = func.parameters();
|
||||
if (parameters.is_empty() || parameters.last().kind() != ValueType::ExceptionReference)
|
||||
return Errors::invalid("catch_ref type"sv, "[..., exnref]"sv, parameters);
|
||||
parameters_to_check = parameters_to_check.slice(0, parameters.size() - 1);
|
||||
} else {
|
||||
// catch x l
|
||||
// (noop here)
|
||||
}
|
||||
|
||||
if (parameters_to_check != target_label_type.span())
|
||||
return Errors::non_conforming_types("catch"sv, parameters_to_check, target_label_type.span());
|
||||
} else {
|
||||
if (catch_.is_ref()) {
|
||||
// catch_all_ref l
|
||||
if (target_label_type.size() != 1 || target_label_type[0].kind() != ValueType::ExceptionReference)
|
||||
return Errors::invalid("catch_all_ref type"sv, "[exnref]"sv, target_label_type);
|
||||
} else {
|
||||
// catch_all l
|
||||
if (!target_label_type.is_empty())
|
||||
return Errors::invalid("catch_all type"sv, "empty"sv, target_label_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <AK/Tuple.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWasm/Forward.h>
|
||||
#include <LibWasm/TypeSystem.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
|
@ -23,6 +24,7 @@ struct Context {
|
|||
};
|
||||
|
||||
COWVector<TypeSection::Type> types;
|
||||
COWVector<DefinedType const*> canonical_types;
|
||||
COWVector<FunctionType> functions;
|
||||
COWVector<Optional<TypeIndex>> function_type_indices;
|
||||
COWVector<StructType> structs;
|
||||
|
|
@ -38,6 +40,8 @@ struct Context {
|
|||
RefPtr<RefRBTree> references { make_ref_counted<RefRBTree>() };
|
||||
size_t imported_function_count { 0 };
|
||||
size_t current_function_parameter_count { 0 };
|
||||
|
||||
TypeContext type_context() const { return TypeContext { canonical_types.span() }; }
|
||||
};
|
||||
|
||||
struct ValidationError : public Error {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ set(SOURCES
|
|||
AbstractMachine/Validator.cpp
|
||||
Parser/Parser.cpp
|
||||
Printer/Printer.cpp
|
||||
TypeSystem.cpp
|
||||
)
|
||||
|
||||
# FIXME: Add Windows support
|
||||
|
|
|
|||
|
|
@ -646,9 +646,11 @@ i32 wasm_cl_call_indirect(void* interp_ptr, void* config_ptr, i32 table_idx, i32
|
|||
auto* function = config.store().get(address);
|
||||
if (!function)
|
||||
return interpreter.set_trap(Trap::from_string("Indirect call to freed function"));
|
||||
auto const& type_actual = function->visit([](auto& f) -> decltype(auto) { return f.type(); });
|
||||
auto const& type_expected = module.types()[type_idx].unsafe_function();
|
||||
if (type_actual.parameters() != type_expected.parameters() || type_actual.results() != type_expected.results())
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-call-indirect-x-y
|
||||
// call_indirect's runtime check is a defined-type match (a downcast), not structural equality.
|
||||
auto const* type_actual = function->visit([](auto& f) { return f.defined_type(); });
|
||||
auto const* type_expected = module.canonical_types()[type_idx];
|
||||
if (!type_actual || !matches_defined_type(*type_actual, *type_expected))
|
||||
return interpreter.set_trap(Trap::from_string("Indirect call type mismatch"));
|
||||
|
||||
SourcesAndDestination addrs {};
|
||||
|
|
|
|||
548
Libraries/LibWasm/TypeSystem.cpp
Normal file
548
Libraries/LibWasm/TypeSystem.cpp
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ali Mohammad Pur <ali@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibSync/Mutex.h>
|
||||
#include <LibWasm/AbstractMachine/Validator.h>
|
||||
#include <LibWasm/TypeSystem.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#rolling-and-unrolling
|
||||
class TypeRegistry {
|
||||
AK_MAKE_NONCOPYABLE(TypeRegistry);
|
||||
AK_MAKE_NONMOVABLE(TypeRegistry);
|
||||
|
||||
public:
|
||||
static TypeRegistry& the()
|
||||
{
|
||||
// Defined types are deliberately immortal: they are the process-wide identities that
|
||||
// runtime values and compiled code point at.
|
||||
static TypeRegistry* registry = new TypeRegistry;
|
||||
return *registry;
|
||||
}
|
||||
|
||||
DefinedType const* type_at(u32 registry_index)
|
||||
{
|
||||
Sync::MutexLocker locker(m_mutex);
|
||||
if (registry_index >= m_types.size())
|
||||
return nullptr;
|
||||
return m_types[registry_index].ptr();
|
||||
}
|
||||
|
||||
ErrorOr<Vector<DefinedType const*>, ValidationError> intern_group(ReadonlySpan<TypeSection::Type> types, TypeSection::Type::RecGroupSpan group, ReadonlySpan<DefinedType const*> resolved_so_far);
|
||||
|
||||
private:
|
||||
TypeRegistry() = default;
|
||||
|
||||
Sync::Mutex m_mutex;
|
||||
Vector<NonnullOwnPtr<DefinedType>> m_types;
|
||||
HashMap<ByteString, u32> m_interned_groups; // group key -> index of first member
|
||||
};
|
||||
|
||||
DefinedType const* TypeContext::resolve(TypeIndex index) const
|
||||
{
|
||||
if (types.is_empty())
|
||||
return TypeRegistry::the().type_at(index.value());
|
||||
if (index.value() >= types.size())
|
||||
return nullptr;
|
||||
return types[index.value()];
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct RolledTypeUse {
|
||||
enum class Form : u8 {
|
||||
Closed,
|
||||
Recursive,
|
||||
};
|
||||
Form form;
|
||||
u32 index; // registry index (Closed) or group-internal index (Recursive)
|
||||
};
|
||||
|
||||
class GroupRoller {
|
||||
public:
|
||||
GroupRoller(TypeSection::Type::RecGroupSpan group, ReadonlySpan<DefinedType const*> resolved_so_far)
|
||||
: m_group(group)
|
||||
, m_resolved_so_far(resolved_so_far)
|
||||
{
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-roll-rectype
|
||||
// roll_x(rectype) substitutes the type indices [x, x+n) with rec.i; anything below x must already be defined.
|
||||
ErrorOr<RolledTypeUse, ValidationError> roll(TypeIndex index) const
|
||||
{
|
||||
if (index.value() < m_group.first_type_index)
|
||||
return RolledTypeUse { RolledTypeUse::Form::Closed, m_resolved_so_far[index.value()]->registry_index() };
|
||||
if (index.value() < m_group.first_type_index + m_group.size)
|
||||
return RolledTypeUse { RolledTypeUse::Form::Recursive, index.value() - m_group.first_type_index };
|
||||
return ValidationError { ByteString::formatted("unknown type {} referenced from recursive type group", index.value()) };
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> serialize(StringBuilder& builder, ValueType type) const
|
||||
{
|
||||
builder.append(static_cast<char>(type.kind()));
|
||||
builder.append(type.is_nullable() ? '\1' : '\0');
|
||||
if (type.is_typeuse()) {
|
||||
auto use = TRY(roll(type.unsafe_typeindex()));
|
||||
builder.append(use.form == RolledTypeUse::Form::Closed ? 'C' : 'R');
|
||||
builder.append(ReadonlyBytes { &use.index, sizeof(use.index) });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> serialize(StringBuilder& builder, FieldType const& field) const
|
||||
{
|
||||
TRY(serialize(builder, field.type()));
|
||||
builder.append(field.is_mutable() ? '\1' : '\0');
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> serialize(StringBuilder& builder, TypeSection::Type const& type) const
|
||||
{
|
||||
builder.append(type.is_final() ? '\1' : '\0');
|
||||
u32 supertype_count = type.supertypes().size();
|
||||
builder.append(ReadonlyBytes { &supertype_count, sizeof(supertype_count) });
|
||||
for (auto supertype : type.supertypes()) {
|
||||
auto use = TRY(roll(supertype));
|
||||
builder.append(use.form == RolledTypeUse::Form::Closed ? 'C' : 'R');
|
||||
builder.append(ReadonlyBytes { &use.index, sizeof(use.index) });
|
||||
}
|
||||
|
||||
TRY(type.description().visit(
|
||||
[&](FunctionType const& function) -> ErrorOr<void, ValidationError> {
|
||||
builder.append('F');
|
||||
u32 count = function.parameters().size();
|
||||
builder.append(ReadonlyBytes { &count, sizeof(count) });
|
||||
for (auto& parameter : function.parameters())
|
||||
TRY(serialize(builder, parameter));
|
||||
count = function.results().size();
|
||||
builder.append(ReadonlyBytes { &count, sizeof(count) });
|
||||
for (auto& result : function.results())
|
||||
TRY(serialize(builder, result));
|
||||
return {};
|
||||
},
|
||||
[&](StructType const& struct_) -> ErrorOr<void, ValidationError> {
|
||||
builder.append('S');
|
||||
u32 count = struct_.fields().size();
|
||||
builder.append(ReadonlyBytes { &count, sizeof(count) });
|
||||
for (auto& field : struct_.fields())
|
||||
TRY(serialize(builder, field));
|
||||
return {};
|
||||
},
|
||||
[&](ArrayType const& array) -> ErrorOr<void, ValidationError> {
|
||||
builder.append('A');
|
||||
return serialize(builder, array.type());
|
||||
}));
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-unroll-rectype
|
||||
ErrorOr<ValueType, ValidationError> substituted(ValueType type, u32 group_base_registry_index) const
|
||||
{
|
||||
if (!type.is_typeuse())
|
||||
return type;
|
||||
auto use = TRY(roll(type.unsafe_typeindex()));
|
||||
auto registry_index = use.form == RolledTypeUse::Form::Closed ? use.index : group_base_registry_index + use.index;
|
||||
return ValueType(ValueType::TypeUseReference, TypeIndex(registry_index), type.is_nullable());
|
||||
}
|
||||
|
||||
ErrorOr<TypeSection::Type, ValidationError> substituted(TypeSection::Type const& type, u32 group_base_registry_index) const
|
||||
{
|
||||
Vector<TypeIndex> supertypes;
|
||||
supertypes.ensure_capacity(type.supertypes().size());
|
||||
for (auto supertype : type.supertypes()) {
|
||||
auto use = TRY(roll(supertype));
|
||||
supertypes.append(TypeIndex(use.form == RolledTypeUse::Form::Closed ? use.index : group_base_registry_index + use.index));
|
||||
}
|
||||
|
||||
auto composite = TRY(type.description().visit(
|
||||
[&](FunctionType const& function) -> ErrorOr<TypeSection::Type::CompositeType, ValidationError> {
|
||||
Vector<ValueType> parameters;
|
||||
parameters.ensure_capacity(function.parameters().size());
|
||||
for (auto& parameter : function.parameters())
|
||||
parameters.append(TRY(substituted(parameter, group_base_registry_index)));
|
||||
Vector<ValueType> results;
|
||||
results.ensure_capacity(function.results().size());
|
||||
for (auto& result : function.results())
|
||||
results.append(TRY(substituted(result, group_base_registry_index)));
|
||||
return TypeSection::Type::CompositeType { FunctionType { move(parameters), move(results) } };
|
||||
},
|
||||
[&](StructType const& struct_) -> ErrorOr<TypeSection::Type::CompositeType, ValidationError> {
|
||||
Vector<FieldType> fields;
|
||||
fields.ensure_capacity(struct_.fields().size());
|
||||
for (auto& field : struct_.fields())
|
||||
fields.append(FieldType { field.is_mutable(), TRY(substituted(field.type(), group_base_registry_index)) });
|
||||
return TypeSection::Type::CompositeType { StructType { move(fields) } };
|
||||
},
|
||||
[&](ArrayType const& array) -> ErrorOr<TypeSection::Type::CompositeType, ValidationError> {
|
||||
return TypeSection::Type::CompositeType { ArrayType { FieldType { array.type().is_mutable(), TRY(substituted(array.type().type(), group_base_registry_index)) } } };
|
||||
}));
|
||||
|
||||
return TypeSection::Type { move(composite), move(supertypes), type.is_final() };
|
||||
}
|
||||
|
||||
private:
|
||||
TypeSection::Type::RecGroupSpan m_group;
|
||||
ReadonlySpan<DefinedType const*> m_resolved_so_far;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
ErrorOr<Vector<DefinedType const*>, ValidationError> TypeRegistry::intern_group(ReadonlySpan<TypeSection::Type> types, TypeSection::Type::RecGroupSpan group, ReadonlySpan<DefinedType const*> resolved_so_far)
|
||||
{
|
||||
GroupRoller roller { group, resolved_so_far };
|
||||
|
||||
StringBuilder key_builder;
|
||||
u32 group_size = group.size;
|
||||
key_builder.append(ReadonlyBytes { &group_size, sizeof(group_size) });
|
||||
for (size_t i = 0; i < group.size; ++i)
|
||||
TRY(roller.serialize(key_builder, types[group.first_type_index + i]));
|
||||
auto key = key_builder.to_byte_string();
|
||||
|
||||
Sync::MutexLocker locker(m_mutex);
|
||||
|
||||
Optional<u32> base_registry_index = m_interned_groups.get(key);
|
||||
if (!base_registry_index.has_value()) {
|
||||
auto base = static_cast<u32>(m_types.size());
|
||||
for (size_t i = 0; i < group.size; ++i) {
|
||||
auto sub_type = TRY(roller.substituted(types[group.first_type_index + i], base));
|
||||
sub_type.set_rec_group({ base, group.size });
|
||||
m_types.append(adopt_own(*new DefinedType(move(sub_type), base + i)));
|
||||
}
|
||||
for (size_t i = 0; i < group.size; ++i) {
|
||||
auto& defined_type = *m_types[base + i];
|
||||
if (!defined_type.m_sub_type.supertypes().is_empty()) {
|
||||
auto supertype_registry_index = defined_type.m_sub_type.supertypes().first().value();
|
||||
if (supertype_registry_index >= base + i)
|
||||
return ValidationError { ByteString::formatted("Invalid supertype {}, supertypes must precede their subtypes", supertype_registry_index) };
|
||||
defined_type.m_supertype = m_types[supertype_registry_index].ptr();
|
||||
defined_type.m_subtyping_depth = defined_type.m_supertype->m_subtyping_depth + 1;
|
||||
defined_type.m_ancestors.ensure_capacity(defined_type.m_subtyping_depth + 1);
|
||||
defined_type.m_ancestors.extend(defined_type.m_supertype->m_ancestors);
|
||||
}
|
||||
defined_type.m_ancestors.append(&defined_type);
|
||||
}
|
||||
m_interned_groups.set(move(key), base);
|
||||
base_registry_index = base;
|
||||
}
|
||||
|
||||
Vector<DefinedType const*> result;
|
||||
result.ensure_capacity(group.size);
|
||||
for (size_t i = 0; i < group.size; ++i)
|
||||
result.append(m_types[*base_registry_index + i].ptr());
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/modules.html#types
|
||||
ErrorOr<Vector<DefinedType const*>, ValidationError> canonicalize_module_types(TypeSection const& section)
|
||||
{
|
||||
Vector<DefinedType const*> resolved;
|
||||
resolved.ensure_capacity(section.types().size());
|
||||
|
||||
for (size_t type_index = 0; type_index < section.types().size();) {
|
||||
auto group = section.types()[type_index].rec_group();
|
||||
if (group.first_type_index != type_index || group.first_type_index + group.size > section.types().size())
|
||||
return ValidationError { ByteString("malformed recursive type group spans"sv) };
|
||||
auto group_types = TRY(TypeRegistry::the().intern_group(section.types(), group, resolved));
|
||||
resolved.extend(move(group_types));
|
||||
type_index += group.size;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
DefinedType const* canonicalize_type(TypeSection::Type const& type, TypeContext const& context)
|
||||
{
|
||||
Vector<TypeSection::Type> closed_types;
|
||||
|
||||
Vector<TypeIndex> supertypes;
|
||||
for (auto supertype : type.supertypes()) {
|
||||
auto const* resolved = context.resolve(supertype);
|
||||
VERIFY(resolved);
|
||||
supertypes.append(TypeIndex(resolved->registry_index()));
|
||||
}
|
||||
|
||||
auto close = [&](ValueType value_type) {
|
||||
return canonicalized(value_type, context);
|
||||
};
|
||||
|
||||
auto composite = type.description().visit(
|
||||
[&](FunctionType const& function) -> TypeSection::Type::CompositeType {
|
||||
Vector<ValueType> parameters;
|
||||
for (auto& parameter : function.parameters())
|
||||
parameters.append(close(parameter));
|
||||
Vector<ValueType> results;
|
||||
for (auto& result : function.results())
|
||||
results.append(close(result));
|
||||
return FunctionType { move(parameters), move(results) };
|
||||
},
|
||||
[&](StructType const& struct_) -> TypeSection::Type::CompositeType {
|
||||
Vector<FieldType> fields;
|
||||
for (auto& field : struct_.fields())
|
||||
fields.append(FieldType { field.is_mutable(), close(field.type()) });
|
||||
return StructType { move(fields) };
|
||||
},
|
||||
[&](ArrayType const& array) -> TypeSection::Type::CompositeType {
|
||||
return ArrayType { FieldType { array.type().is_mutable(), close(array.type().type()) } };
|
||||
});
|
||||
|
||||
closed_types.append(TypeSection::Type { move(composite), move(supertypes), type.is_final() });
|
||||
closed_types.first().set_rec_group({ 0, 1 });
|
||||
|
||||
auto result = TypeRegistry::the().intern_group(closed_types.span(), { 0, 1 }, {});
|
||||
VERIFY(!result.is_error());
|
||||
return result.release_value().first();
|
||||
}
|
||||
|
||||
ValueType canonicalized(ValueType type, TypeContext const& context)
|
||||
{
|
||||
if (!type.is_typeuse() || context.types.is_empty())
|
||||
return type;
|
||||
auto const* resolved = context.resolve(type.unsafe_typeindex());
|
||||
VERIFY(resolved);
|
||||
return ValueType(ValueType::TypeUseReference, TypeIndex(resolved->registry_index()), type.is_nullable());
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#defined-types
|
||||
bool matches_defined_type(DefinedType const& defined_type1, DefinedType const& defined_type2)
|
||||
{
|
||||
if (&defined_type1 == &defined_type2)
|
||||
return true;
|
||||
if (defined_type2.subtyping_depth() >= defined_type1.subtyping_depth())
|
||||
return false;
|
||||
return defined_type1.ancestors()[defined_type2.subtyping_depth()] == &defined_type2;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/syntax/types.html#heap-types
|
||||
static ValueType::Kind top_of_heap_type(ValueType const& type, TypeContext const& context)
|
||||
{
|
||||
switch (type.kind()) {
|
||||
case ValueType::AnyReference:
|
||||
case ValueType::EqReference:
|
||||
case ValueType::I31Reference:
|
||||
case ValueType::StructReference:
|
||||
case ValueType::ArrayReference:
|
||||
case ValueType::NoneReference:
|
||||
return ValueType::AnyReference;
|
||||
case ValueType::FunctionReference:
|
||||
case ValueType::NoFunctionReference:
|
||||
return ValueType::FunctionReference;
|
||||
case ValueType::ExternReference:
|
||||
case ValueType::NoExternReference:
|
||||
return ValueType::ExternReference;
|
||||
case ValueType::ExceptionReference:
|
||||
case ValueType::NoExceptionReference:
|
||||
return ValueType::ExceptionReference;
|
||||
case ValueType::TypeUseReference: {
|
||||
auto const* defined_type = context.resolve(type.unsafe_typeindex());
|
||||
VERIFY(defined_type);
|
||||
return defined_type->expansion().visit(
|
||||
[](FunctionType const&) { return ValueType::FunctionReference; },
|
||||
[](StructType const&) { return ValueType::AnyReference; },
|
||||
[](ArrayType const&) { return ValueType::AnyReference; });
|
||||
}
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#heap-types
|
||||
bool matches_heap_type(ValueType const& heap_type1, ValueType const& heap_type2, TypeContext const& context)
|
||||
{
|
||||
VERIFY(heap_type1.is_reference() && heap_type2.is_reference());
|
||||
|
||||
// The heap type heaptype_1 matches the heap type heaptype_2 if:
|
||||
// Either: The heap type heaptype_2 is of the form heaptype_1.
|
||||
if (heap_type1.kind() == heap_type2.kind()) {
|
||||
if (!heap_type1.is_typeuse())
|
||||
return true;
|
||||
auto const* defined_type1 = context.resolve(heap_type1.unsafe_typeindex());
|
||||
auto const* defined_type2 = context.resolve(heap_type2.unsafe_typeindex());
|
||||
VERIFY(defined_type1 && defined_type2);
|
||||
// Or: deftype_1 matches deftype_2.
|
||||
return matches_defined_type(*defined_type1, *defined_type2);
|
||||
}
|
||||
|
||||
switch (heap_type1.kind()) {
|
||||
// Or: heaptype_1 is eq and heaptype_2 is any.
|
||||
case ValueType::EqReference:
|
||||
return heap_type2.kind() == ValueType::AnyReference;
|
||||
// Or: heaptype_1 is i31/struct/array and heaptype_2 is eq (or any, transitively).
|
||||
case ValueType::I31Reference:
|
||||
case ValueType::StructReference:
|
||||
case ValueType::ArrayReference:
|
||||
return heap_type2.kind() == ValueType::EqReference || heap_type2.kind() == ValueType::AnyReference;
|
||||
// Or: heaptype_1 is a defined type.
|
||||
case ValueType::TypeUseReference: {
|
||||
auto const* defined_type = context.resolve(heap_type1.unsafe_typeindex());
|
||||
VERIFY(defined_type);
|
||||
switch (heap_type2.kind()) {
|
||||
// ... and heaptype_2 is func and the expansion of deftype is (func t1* -> t2*).
|
||||
case ValueType::FunctionReference:
|
||||
return defined_type->expansion().has<FunctionType>();
|
||||
// ... and heaptype_2 is struct and the expansion of deftype is (struct fieldtype*).
|
||||
case ValueType::StructReference:
|
||||
return defined_type->expansion().has<StructType>();
|
||||
// ... and heaptype_2 is array and the expansion of deftype is (array fieldtype).
|
||||
case ValueType::ArrayReference:
|
||||
return defined_type->expansion().has<ArrayType>();
|
||||
// Transitively: deftype <= struct/array <= eq <= any.
|
||||
case ValueType::EqReference:
|
||||
case ValueType::AnyReference:
|
||||
return defined_type->expansion().has<StructType>() || defined_type->expansion().has<ArrayType>();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Or: heaptype_1 is none and heaptype_2 matches any.
|
||||
case ValueType::NoneReference:
|
||||
return top_of_heap_type(heap_type2, context) == ValueType::AnyReference;
|
||||
// Or: heaptype_1 is nofunc and heaptype_2 matches func.
|
||||
case ValueType::NoFunctionReference:
|
||||
return top_of_heap_type(heap_type2, context) == ValueType::FunctionReference;
|
||||
// Or: heaptype_1 is noexn and heaptype_2 matches exn.
|
||||
case ValueType::NoExceptionReference:
|
||||
return top_of_heap_type(heap_type2, context) == ValueType::ExceptionReference;
|
||||
// Or: heaptype_1 is noextern and heaptype_2 matches extern.
|
||||
case ValueType::NoExternReference:
|
||||
return top_of_heap_type(heap_type2, context) == ValueType::ExternReference;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#reference-types
|
||||
bool matches_reference_type(ValueType const& reference_type1, ValueType const& reference_type2, TypeContext const& context)
|
||||
{
|
||||
if (reference_type1.is_nullable() && !reference_type2.is_nullable())
|
||||
return false;
|
||||
return matches_heap_type(reference_type1, reference_type2, context);
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#value-types
|
||||
bool matches_value_type(ValueType const& value_type1, ValueType const& value_type2, TypeContext const& context)
|
||||
{
|
||||
if (value_type1.is_reference() && value_type2.is_reference())
|
||||
return matches_reference_type(value_type1, value_type2, context);
|
||||
return !value_type1.is_reference() && !value_type2.is_reference() && value_type1.kind() == value_type2.kind();
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#result-types
|
||||
bool matches_result_types(ReadonlySpan<ValueType> result_types1, ReadonlySpan<ValueType> result_types2, TypeContext const& context)
|
||||
{
|
||||
if (result_types1.size() != result_types2.size())
|
||||
return false;
|
||||
for (size_t i = 0; i < result_types1.size(); ++i) {
|
||||
if (!matches_value_type(result_types1[i], result_types2[i], context))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#field-types
|
||||
static bool matches_storage_type(ValueType const& storage_type1, ValueType const& storage_type2, TypeContext const& context)
|
||||
{
|
||||
if (storage_type1.is_packed() || storage_type2.is_packed())
|
||||
return storage_type1.kind() == storage_type2.kind();
|
||||
return matches_value_type(storage_type1, storage_type2, context);
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#field-types
|
||||
bool matches_field_type(FieldType const& field_type1, FieldType const& field_type2, TypeContext const& context)
|
||||
{
|
||||
if (field_type1.is_mutable() != field_type2.is_mutable())
|
||||
return false;
|
||||
if (!matches_storage_type(field_type1.type(), field_type2.type(), context))
|
||||
return false;
|
||||
if (field_type1.is_mutable() && !matches_storage_type(field_type2.type(), field_type1.type(), context))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#composite-types
|
||||
bool matches_composite_type(TypeSection::Type::CompositeType const& composite_type1, TypeSection::Type::CompositeType const& composite_type2, TypeContext const& context)
|
||||
{
|
||||
return composite_type2.visit(
|
||||
[&](FunctionType const& function2) {
|
||||
auto const* function1 = composite_type1.get_pointer<FunctionType>();
|
||||
if (!function1)
|
||||
return false;
|
||||
return matches_result_types(function2.parameters(), function1->parameters(), context)
|
||||
&& matches_result_types(function1->results(), function2.results(), context);
|
||||
},
|
||||
[&](StructType const& struct2) {
|
||||
auto const* struct1 = composite_type1.get_pointer<StructType>();
|
||||
if (!struct1 || struct1->fields().size() < struct2.fields().size())
|
||||
return false;
|
||||
for (size_t i = 0; i < struct2.fields().size(); ++i) {
|
||||
if (!matches_field_type(struct1->fields()[i], struct2.fields()[i], context))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[&](ArrayType const& array2) {
|
||||
auto const* array1 = composite_type1.get_pointer<ArrayType>();
|
||||
if (!array1)
|
||||
return false;
|
||||
return matches_field_type(array1->type(), array2.type(), context);
|
||||
});
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#limits
|
||||
bool matches_limits(Limits const& limits1, Limits const& limits2)
|
||||
{
|
||||
if (limits1.min() < limits2.min())
|
||||
return false;
|
||||
if (!limits2.max().has_value())
|
||||
return true;
|
||||
return limits1.max().has_value() && *limits1.max() <= *limits2.max();
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#memory-types
|
||||
bool matches_memory_type(MemoryType const& memory_type1, MemoryType const& memory_type2)
|
||||
{
|
||||
if (memory_type1.limits().address_type() != memory_type2.limits().address_type())
|
||||
return false;
|
||||
return matches_limits(memory_type1.limits(), memory_type2.limits());
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#table-types
|
||||
bool matches_table_type(TableType const& table_type1, TableType const& table_type2, TypeContext const& context1, TypeContext const& context2)
|
||||
{
|
||||
if (table_type1.limits().address_type() != table_type2.limits().address_type())
|
||||
return false;
|
||||
if (!matches_limits(table_type1.limits(), table_type2.limits()))
|
||||
return false;
|
||||
auto element_type1 = canonicalized(table_type1.element_type(), context1);
|
||||
auto element_type2 = canonicalized(table_type2.element_type(), context2);
|
||||
return matches_reference_type(element_type1, element_type2, TypeContext {})
|
||||
&& matches_reference_type(element_type2, element_type1, TypeContext {});
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#global-types
|
||||
bool matches_global_type(GlobalType const& global_type1, GlobalType const& global_type2, TypeContext const& context1, TypeContext const& context2)
|
||||
{
|
||||
if (global_type1.is_mutable() != global_type2.is_mutable())
|
||||
return false;
|
||||
auto value_type1 = canonicalized(global_type1.type(), context1);
|
||||
auto value_type2 = canonicalized(global_type2.type(), context2);
|
||||
if (!matches_value_type(value_type1, value_type2, TypeContext {}))
|
||||
return false;
|
||||
if (global_type1.is_mutable() && !matches_value_type(value_type2, value_type1, TypeContext {}))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#tag-types
|
||||
bool matches_tag_type(DefinedType const& defined_type1, DefinedType const& defined_type2)
|
||||
{
|
||||
return matches_defined_type(defined_type1, defined_type2) && matches_defined_type(defined_type2, defined_type1);
|
||||
}
|
||||
|
||||
}
|
||||
83
Libraries/LibWasm/TypeSystem.h
Normal file
83
Libraries/LibWasm/TypeSystem.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ali Mohammad Pur <ali@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWasm/Export.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
struct ValidationError;
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#defined-types
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#rolling-and-unrolling
|
||||
class WASM_API DefinedType {
|
||||
AK_MAKE_NONCOPYABLE(DefinedType);
|
||||
AK_MAKE_NONMOVABLE(DefinedType);
|
||||
|
||||
public:
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-unroll-deftype
|
||||
TypeSection::Type const& sub_type() const { return m_sub_type; }
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-expand-deftype
|
||||
auto& expansion() const { return m_sub_type.description(); }
|
||||
|
||||
// The (single) declared supertype.
|
||||
DefinedType const* supertype() const { return m_supertype; }
|
||||
bool is_final() const { return m_sub_type.is_final(); }
|
||||
|
||||
u32 subtyping_depth() const { return m_subtyping_depth; }
|
||||
ReadonlySpan<DefinedType const*> ancestors() const { return m_ancestors; }
|
||||
|
||||
u32 registry_index() const { return m_registry_index; }
|
||||
|
||||
private:
|
||||
friend class TypeRegistry;
|
||||
|
||||
DefinedType(TypeSection::Type sub_type, u32 registry_index)
|
||||
: m_sub_type(move(sub_type))
|
||||
, m_registry_index(registry_index)
|
||||
{
|
||||
}
|
||||
|
||||
TypeSection::Type m_sub_type;
|
||||
DefinedType const* m_supertype { nullptr };
|
||||
Vector<DefinedType const*> m_ancestors;
|
||||
u32 m_subtyping_depth { 0 };
|
||||
u32 m_registry_index { 0 };
|
||||
};
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#contexts
|
||||
struct WASM_API TypeContext {
|
||||
DefinedType const* resolve(TypeIndex index) const;
|
||||
|
||||
ReadonlySpan<DefinedType const*> types {};
|
||||
};
|
||||
|
||||
WASM_API ErrorOr<Vector<DefinedType const*>, ValidationError> canonicalize_module_types(TypeSection const&);
|
||||
WASM_API DefinedType const* canonicalize_type(TypeSection::Type const&, TypeContext const& context);
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-clostype
|
||||
WASM_API ValueType canonicalized(ValueType, TypeContext const&);
|
||||
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html
|
||||
WASM_API bool matches_heap_type(ValueType const& heap_type1, ValueType const& heap_type2, TypeContext const&);
|
||||
WASM_API bool matches_reference_type(ValueType const& reference_type1, ValueType const& reference_type2, TypeContext const&);
|
||||
WASM_API bool matches_value_type(ValueType const& value_type1, ValueType const& value_type2, TypeContext const&);
|
||||
WASM_API bool matches_result_types(ReadonlySpan<ValueType> result_types1, ReadonlySpan<ValueType> result_types2, TypeContext const&);
|
||||
WASM_API bool matches_field_type(FieldType const&, FieldType const&, TypeContext const&);
|
||||
WASM_API bool matches_composite_type(TypeSection::Type::CompositeType const&, TypeSection::Type::CompositeType const&, TypeContext const&);
|
||||
WASM_API bool matches_defined_type(DefinedType const&, DefinedType const&);
|
||||
WASM_API bool matches_limits(Limits const&, Limits const&);
|
||||
WASM_API bool matches_memory_type(MemoryType const&, MemoryType const&);
|
||||
WASM_API bool matches_table_type(TableType const&, TableType const&, TypeContext const& context1, TypeContext const& context2);
|
||||
WASM_API bool matches_global_type(GlobalType const&, GlobalType const&, TypeContext const& context1, TypeContext const& context2);
|
||||
WASM_API bool matches_tag_type(DefinedType const&, DefinedType const&);
|
||||
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
namespace Wasm {
|
||||
|
||||
class DefinedType;
|
||||
class Module;
|
||||
|
||||
template<size_t M>
|
||||
|
|
@ -1081,10 +1082,9 @@ public:
|
|||
// https://webassembly.github.io/spec/core/syntax/types.html#recursive-types
|
||||
// https://webassembly.github.io/spec/core/syntax/types.html#composite-types
|
||||
class Type {
|
||||
private:
|
||||
public:
|
||||
using CompositeType = Variant<FunctionType, StructType, ArrayType>;
|
||||
|
||||
public:
|
||||
struct RecGroupSpan {
|
||||
u32 first_type_index { 0 };
|
||||
u32 size { 1 };
|
||||
|
|
@ -1709,6 +1709,11 @@ public:
|
|||
size_t minimum_call_record_allocation_size() const { return m_minimum_call_record_allocation_size; }
|
||||
void set_minimum_call_record_allocation_size(size_t size) { m_minimum_call_record_allocation_size = size; }
|
||||
|
||||
// The defined type of each (flattened) type-section entry; filled in during validation.
|
||||
// https://webassembly.github.io/spec/core/valid/conventions.html#defined-types
|
||||
auto& canonical_types() const { return m_canonical_types; }
|
||||
void set_canonical_types(Vector<DefinedType const*> types) { m_canonical_types = move(types); }
|
||||
|
||||
private:
|
||||
void set_validation_status(ValidationStatus status) { m_validation_status = status; }
|
||||
void preprocess();
|
||||
|
|
@ -1736,6 +1741,8 @@ private:
|
|||
Optional<CompileCacheConfig> m_cranelift_cache_config;
|
||||
Optional<ModuleStats> m_compile_stats;
|
||||
|
||||
Vector<DefinedType const*> m_canonical_types;
|
||||
|
||||
size_t m_minimum_call_record_allocation_size { 0 };
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue