LibWasm: Implement the wasm-gc instruction set in the BC interpreter
This commit is contained in:
parent
87961e3c92
commit
16d1486cc9
6 changed files with 796 additions and 53 deletions
|
|
@ -157,6 +157,8 @@ void AbstractMachine::RootsProvider::for_each_conservative_range(AK::Function<vo
|
|||
|
||||
for (auto& table : m_store.tables())
|
||||
report_references(table.elements().span());
|
||||
for (auto& element : m_store.elements())
|
||||
report_references(element.references().span());
|
||||
for (auto& global : m_store.globals())
|
||||
report_values(&global.value(), 1);
|
||||
for (auto& exception : m_store.exceptions())
|
||||
|
|
@ -565,6 +567,8 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
auto& auxiliary_instance = *auxiliary_instance_ptr;
|
||||
|
||||
auxiliary_instance.cached_minimum_call_record_allocation_size = module.minimum_call_record_allocation_size();
|
||||
auxiliary_instance.types() = module.type_section().types();
|
||||
auxiliary_instance.canonical_types() = module.canonical_types();
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/modules.html#instantiation
|
||||
// https://webassembly.github.io/spec/core/valid/matching.html#external-types
|
||||
|
|
@ -705,11 +709,12 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
Configuration config { m_store };
|
||||
if (m_should_limit_instruction_count)
|
||||
config.enable_instruction_count_limit();
|
||||
// https://webassembly.github.io/spec/core/exec/modules.html#instantiation
|
||||
config.set_frame(IsTailcall::No,
|
||||
main_module_instance,
|
||||
Vector<Value, ArgumentsStaticSize> {},
|
||||
entry,
|
||||
entry.instructions().size() - 1);
|
||||
1uz);
|
||||
auto result = config.execute(interpreter);
|
||||
if (result.is_trap())
|
||||
return InstantiationError { "Element section initialisation trapped", move(result.trap()) };
|
||||
|
|
|
|||
|
|
@ -182,6 +182,8 @@ public:
|
|||
// 6: a gc object
|
||||
// 7: an i31 reference
|
||||
// 8: a null reference in the any hierarchy
|
||||
// 9 | (tag << 8): an "externalized" internal reference (extern.convert_any)
|
||||
// 10: a host externref viewed as a value in the any hierarchy (any.convert_extern)
|
||||
// anything else: funcref, where high is the defining Module* (null for host functions)
|
||||
ref.ref().visit(
|
||||
[&](Reference::Func const& func) { m_value = u128(bit_cast<u64>(func.address), bit_cast<u64>(func.source_module.ptr())); },
|
||||
|
|
@ -213,7 +215,9 @@ public:
|
|||
// The gc cell behind this value if it holds a gc object reference, otherwise null.
|
||||
GC::Cell* gc_cell() const
|
||||
{
|
||||
if (m_value.high() == 6)
|
||||
// A gc object, either plain (tag 6) or externalized by extern.convert_any (tag 9
|
||||
// wrapping tag 6).
|
||||
if (m_value.high() == 6 || m_value.high() == (9 | (6 << 8)))
|
||||
return bit_cast<GC::Cell*>(m_value.low());
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -268,6 +272,15 @@ public:
|
|||
return Reference { Reference::I31 { static_cast<u32>(m_value.low()) } };
|
||||
case 8:
|
||||
return Reference { Reference::Null { ValueType(ValueType::Kind::AnyReference) } };
|
||||
case 9 | (6 << 8):
|
||||
// An externalized gc object; boxing drops the extern.convert_any wrapper.
|
||||
return Reference { Reference::GcObject { bit_cast<GC::Cell*>(m_value.low()) } };
|
||||
case 9 | (7 << 8):
|
||||
// An externalized i31; boxing drops the extern.convert_any wrapper.
|
||||
return Reference { Reference::I31 { static_cast<u32>(m_value.low()) } };
|
||||
case 10:
|
||||
// A host externref internalized by any.convert_extern; boxing drops the wrapper.
|
||||
return Reference { Reference::Extern { bit_cast<ExternAddress>(m_value.low()) } };
|
||||
default:
|
||||
return Reference { Reference::Func { bit_cast<FunctionAddress>(m_value.low()), bit_cast<Wasm::Module*>(m_value.high()) } };
|
||||
}
|
||||
|
|
@ -818,6 +831,7 @@ public:
|
|||
|
||||
auto& tables() const { return m_tables; }
|
||||
auto& globals() const { return m_globals; }
|
||||
auto& elements() const { return m_elements; }
|
||||
auto& exceptions() const { return m_exceptions; }
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <AK/SaturatingMath.h>
|
||||
#include <AK/ScopedValueRollback.h>
|
||||
#include <AK/Time.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
||||
#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
|
||||
|
|
@ -5515,52 +5516,733 @@ HANDLE_INSTRUCTION(try_table)
|
|||
return Outcome::Return;
|
||||
}
|
||||
|
||||
// Proposal "GC"; FIXME: Actually implement these :)
|
||||
#define HANDLE_WASM_GC_STUB(name) \
|
||||
HANDLE_INSTRUCTION(name) \
|
||||
{ \
|
||||
LOG_INSN; \
|
||||
interpreter.set_trap("Not Implemented: wasm-gc"sv); \
|
||||
return Outcome::Return; \
|
||||
// Proposal "gc".
|
||||
|
||||
#define GC_TRAP_IF(condition, message) \
|
||||
do { \
|
||||
if (condition) [[unlikely]] { \
|
||||
interpreter.set_trap(message##sv); \
|
||||
return Outcome::Return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
static ALWAYS_INLINE bool is_null_gc_reference(Value const& value)
|
||||
{
|
||||
auto const tag = value.value().high();
|
||||
return tag == 2 || tag == 3 || tag == 4 || tag == 8;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/runtime.html#aggregate-instances
|
||||
static Value pack_into_field(ValueType const& storage_type, Value const& value)
|
||||
{
|
||||
switch (storage_type.kind()) {
|
||||
case ValueType::I8:
|
||||
return Value(static_cast<u32>(value.to<u32>() & 0xff));
|
||||
case ValueType::I16:
|
||||
return Value(static_cast<u32>(value.to<u32>() & 0xffff));
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE_WASM_GC_STUB(ref_eq)
|
||||
HANDLE_WASM_GC_STUB(ref_as_non_null)
|
||||
HANDLE_WASM_GC_STUB(br_on_null)
|
||||
HANDLE_WASM_GC_STUB(br_on_non_null)
|
||||
HANDLE_WASM_GC_STUB(struct_new)
|
||||
HANDLE_WASM_GC_STUB(struct_new_default)
|
||||
HANDLE_WASM_GC_STUB(struct_get)
|
||||
HANDLE_WASM_GC_STUB(struct_get_s)
|
||||
HANDLE_WASM_GC_STUB(struct_get_u)
|
||||
HANDLE_WASM_GC_STUB(struct_set)
|
||||
HANDLE_WASM_GC_STUB(array_new)
|
||||
HANDLE_WASM_GC_STUB(array_new_default)
|
||||
HANDLE_WASM_GC_STUB(array_new_fixed)
|
||||
HANDLE_WASM_GC_STUB(array_new_data)
|
||||
HANDLE_WASM_GC_STUB(array_new_elem)
|
||||
HANDLE_WASM_GC_STUB(array_get)
|
||||
HANDLE_WASM_GC_STUB(array_get_s)
|
||||
HANDLE_WASM_GC_STUB(array_get_u)
|
||||
HANDLE_WASM_GC_STUB(array_set)
|
||||
HANDLE_WASM_GC_STUB(array_len)
|
||||
HANDLE_WASM_GC_STUB(array_fill)
|
||||
HANDLE_WASM_GC_STUB(array_copy)
|
||||
HANDLE_WASM_GC_STUB(array_init_data)
|
||||
HANDLE_WASM_GC_STUB(array_init_elem)
|
||||
HANDLE_WASM_GC_STUB(ref_test)
|
||||
HANDLE_WASM_GC_STUB(ref_test_null)
|
||||
HANDLE_WASM_GC_STUB(ref_cast)
|
||||
HANDLE_WASM_GC_STUB(ref_cast_null)
|
||||
HANDLE_WASM_GC_STUB(br_on_cast)
|
||||
HANDLE_WASM_GC_STUB(br_on_cast_fail)
|
||||
HANDLE_WASM_GC_STUB(any_convert_extern)
|
||||
HANDLE_WASM_GC_STUB(extern_convert_any)
|
||||
HANDLE_WASM_GC_STUB(ref_i31)
|
||||
HANDLE_WASM_GC_STUB(i31_get_s)
|
||||
HANDLE_WASM_GC_STUB(i31_get_u)
|
||||
// https://webassembly.github.io/spec/core/exec/runtime.html#aggregate-instances
|
||||
// unpack^sx?_zt(val) = val if zt is a valtype
|
||||
// = extend^sx_|zt|,32(val) if zt is a packtype
|
||||
static Value unpack_from_field(ValueType const& storage_type, Value const& value, bool sign_extend)
|
||||
{
|
||||
switch (storage_type.kind()) {
|
||||
case ValueType::I8:
|
||||
return sign_extend
|
||||
? Value(static_cast<i32>(static_cast<i8>(value.to<u32>() & 0xff)))
|
||||
: Value(static_cast<u32>(value.to<u32>() & 0xff));
|
||||
case ValueType::I16:
|
||||
return sign_extend
|
||||
? Value(static_cast<i32>(static_cast<i16>(value.to<u32>() & 0xffff)))
|
||||
: Value(static_cast<u32>(value.to<u32>() & 0xffff));
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
#undef HANDLE_WASM_GC_STUB
|
||||
static size_t storage_type_byte_width(ValueType const& storage_type)
|
||||
{
|
||||
switch (storage_type.kind()) {
|
||||
case ValueType::I8:
|
||||
return 1;
|
||||
case ValueType::I16:
|
||||
return 2;
|
||||
case ValueType::I32:
|
||||
case ValueType::F32:
|
||||
return 4;
|
||||
case ValueType::I64:
|
||||
case ValueType::F64:
|
||||
return 8;
|
||||
case ValueType::V128:
|
||||
return 16;
|
||||
default:
|
||||
// References cannot be read out of a data segment (checked by the validator).
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
static Value value_from_segment_bytes(ValueType const& storage_type, ReadonlyBytes bytes)
|
||||
{
|
||||
auto read_le = []<typename T>(ReadonlyBytes data) {
|
||||
T value {};
|
||||
__builtin_memcpy(&value, data.data(), sizeof(T));
|
||||
return AK::convert_between_host_and_little_endian(value);
|
||||
};
|
||||
switch (storage_type.kind()) {
|
||||
case ValueType::I8:
|
||||
// Packed fields are stored pre-truncated; reads sign- or zero-extend, see unpack_from_field() above.
|
||||
return Value(static_cast<u32>(bytes[0]));
|
||||
case ValueType::I16:
|
||||
return Value(static_cast<u32>(read_le.operator()<u16>(bytes)));
|
||||
case ValueType::I32:
|
||||
return Value(static_cast<i32>(read_le.operator()<u32>(bytes)));
|
||||
case ValueType::I64:
|
||||
return Value(static_cast<i64>(read_le.operator()<u64>(bytes)));
|
||||
case ValueType::F32:
|
||||
return Value(bit_cast<f32>(read_le.operator()<u32>(bytes)));
|
||||
case ValueType::F64:
|
||||
return Value(bit_cast<f64>(read_le.operator()<u64>(bytes)));
|
||||
case ValueType::V128: {
|
||||
u128 value {};
|
||||
__builtin_memcpy(&value, bytes.data(), sizeof(u128));
|
||||
return Value(value);
|
||||
}
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
static bool reference_matches_type(Value const& value, ValueType const& target, Configuration& configuration)
|
||||
{
|
||||
// See the encoding table in Value(Reference const&).
|
||||
switch (value.value().high()) {
|
||||
case 2: // null funcref / nofuncref
|
||||
case 3: // null externref / noexternref
|
||||
case 4: // null exnref / noexnref
|
||||
case 8: // null in the any hierarchy
|
||||
// "ref.null matches (ref null ht)": null only matches nullable targets. Validation
|
||||
// keeps the hierarchies apart, so the target heap type always agrees with the null;
|
||||
// in particular this is the only way to match the bottom (none/nofunc/...) types.
|
||||
return target.is_nullable();
|
||||
case 1: // a host externref
|
||||
// Matches extern; also any, for an internalized host reference (tag 10) that lost its
|
||||
// wrapper crossing a Reference boundary (e.g. an anyref table), see Value::to<Reference>().
|
||||
return target.kind() == ValueType::ExternReference || target.kind() == ValueType::AnyReference;
|
||||
case 5: // an exception
|
||||
return target.kind() == ValueType::ExceptionReference;
|
||||
case 6: { // a struct or array instance
|
||||
auto& cell = *bit_cast<GC::Cell*>(value.value().low());
|
||||
switch (target.kind()) {
|
||||
case ValueType::AnyReference:
|
||||
case ValueType::EqReference:
|
||||
return true;
|
||||
case ValueType::ExternReference:
|
||||
// An externalized aggregate (tag 9) that lost its wrapper crossing a Reference boundary (e.g. an externref table); it is still in the extern hierarchy.
|
||||
return true;
|
||||
case ValueType::StructReference:
|
||||
return is<StructInstance>(cell);
|
||||
case ValueType::ArrayReference:
|
||||
return is<ArrayInstance>(cell);
|
||||
case ValueType::TypeUseReference: {
|
||||
DefinedType const* actual = nullptr;
|
||||
if (auto* struct_instance = as_if<StructInstance>(cell))
|
||||
actual = &struct_instance->type();
|
||||
else if (auto* array_instance = as_if<ArrayInstance>(cell))
|
||||
actual = &array_instance->type();
|
||||
return actual && matches_defined_type(*actual, *configuration.frame().module().canonical_types()[target.unsafe_typeindex().value()]);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case 7: // an i31
|
||||
// ExternReference covers an externalized i31 (tag 9) that lost its wrapper crossing a Reference boundary, as for tag 6 above.
|
||||
return first_is_one_of(target.kind(), ValueType::I31Reference, ValueType::EqReference, ValueType::AnyReference, ValueType::ExternReference);
|
||||
case 9 | (6 << 8): // an externalized struct or array instance
|
||||
case 9 | (7 << 8): // an externalized i31
|
||||
return target.kind() == ValueType::ExternReference;
|
||||
case 10: // a host externref internalized into the any hierarchy
|
||||
return target.kind() == ValueType::AnyReference;
|
||||
default: { // a funcref; high is the defining Module* (null for host functions)
|
||||
if (target.kind() == ValueType::FunctionReference)
|
||||
return true;
|
||||
if (target.kind() != ValueType::TypeUseReference)
|
||||
return false;
|
||||
auto* function = configuration.store().get(FunctionAddress { value.value().low() });
|
||||
auto const* actual = function ? function->visit([](auto& f) { return f.defined_type(); }) : nullptr;
|
||||
return actual && matches_defined_type(*actual, *configuration.frame().module().canonical_types()[target.unsafe_typeindex().value()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(ref_eq)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
// bounds checked by verifier.
|
||||
auto rhs = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto& lhs_slot = configuration.source_value<source_address_mix>(1, addresses.sources);
|
||||
// Both operands are in the eq hierarchy: nulls are uniformly tag 8, i31s carry their payload and aggregates their cell pointer, so reference equality is bit equality.
|
||||
auto const equal = lhs_slot.value() == rhs.value();
|
||||
lhs_slot = Value(static_cast<i32>(equal ? 1 : 0));
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(ref_as_non_null)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto const& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
GC_TRAP_IF(is_null_gc_reference(slot), "null reference");
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions
|
||||
HANDLE_INSTRUCTION(br_on_null)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
// bounds checked by verifier.
|
||||
auto value = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto const is_null = is_null_gc_reference(value);
|
||||
// The branched-to label's arity does not include the reference; it is only put back when falling through.
|
||||
if (!is_null)
|
||||
configuration.value_stack().append(value);
|
||||
short_ip.current_ip_value = interpreter.branch_to_label<true>(configuration, instruction->arguments().unsafe_get<Instruction::BranchArgs>().label, short_ip.current_ip_value, is_null).value();
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions
|
||||
HANDLE_INSTRUCTION(br_on_non_null)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
// bounds checked by verifier.
|
||||
auto value = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto const is_null = is_null_gc_reference(value);
|
||||
if (!is_null)
|
||||
configuration.value_stack().append(value);
|
||||
short_ip.current_ip_value = interpreter.branch_to_label<true>(configuration, instruction->arguments().unsafe_get<Instruction::BranchArgs>().label, short_ip.current_ip_value, !is_null).value();
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(struct_new)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto const& type = *configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()];
|
||||
auto const& field_types = type.expansion().get<StructType>().fields();
|
||||
auto const field_count = field_types.size();
|
||||
auto operands = configuration.value_stack().span().slice_from_end(field_count);
|
||||
Vector<Value> fields;
|
||||
fields.ensure_capacity(field_count);
|
||||
for (size_t i = 0; i < field_count; ++i)
|
||||
fields.unchecked_append(pack_into_field(field_types[i].type(), operands[i]));
|
||||
auto instance = configuration.store().heap().allocate<StructInstance>(type, move(fields));
|
||||
configuration.value_stack().shrink(configuration.value_stack().size() - field_count);
|
||||
configuration.push_to_destination<source_address_mix>(Value(Reference { Reference::GcObject { instance.ptr() } }), addresses.destination);
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(struct_new_default)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto const& type = *configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()];
|
||||
auto const& field_types = type.expansion().get<StructType>().fields();
|
||||
Vector<Value> fields;
|
||||
fields.ensure_capacity(field_types.size());
|
||||
for (auto const& field_type : field_types)
|
||||
fields.unchecked_append(Value(field_type.type().unpacked()));
|
||||
auto instance = configuration.store().heap().allocate<StructInstance>(type, move(fields));
|
||||
configuration.push_to_destination<source_address_mix>(Value(Reference { Reference::GcObject { instance.ptr() } }), addresses.destination);
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
template<SourceAddressMix mix>
|
||||
static bool struct_get_impl(BytecodeInterpreter& interpreter, Configuration& configuration, Instruction const* instruction, SourcesAndDestination const& addresses, bool sign_extend)
|
||||
{
|
||||
auto& args = instruction->arguments().get<Instruction::StructFieldArgs>();
|
||||
auto& slot = configuration.source_value<mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
if (is_null_gc_reference(slot)) [[unlikely]]
|
||||
return interpreter.set_trap("null structure reference"sv);
|
||||
auto& instance = *static_cast<StructInstance*>(bit_cast<GC::Cell*>(slot.value().low()));
|
||||
auto const& field_types = configuration.frame().module().canonical_types()[args.type_index.value()]->expansion().get<StructType>().fields();
|
||||
slot = unpack_from_field(field_types[args.field_index].type(), instance.fields()[args.field_index], sign_extend);
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(struct_get)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
if (struct_get_impl<source_address_mix>(interpreter, configuration, instruction, addresses, false))
|
||||
return Outcome::Return;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(struct_get_s)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
if (struct_get_impl<source_address_mix>(interpreter, configuration, instruction, addresses, true))
|
||||
return Outcome::Return;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(struct_get_u)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
if (struct_get_impl<source_address_mix>(interpreter, configuration, instruction, addresses, false))
|
||||
return Outcome::Return;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(struct_set)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& args = instruction->arguments().get<Instruction::StructFieldArgs>();
|
||||
// bounds checked by verifier.
|
||||
auto value = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto reference = configuration.take_source<source_address_mix>(1, addresses.sources);
|
||||
GC_TRAP_IF(is_null_gc_reference(reference), "null structure reference");
|
||||
auto& instance = *static_cast<StructInstance*>(bit_cast<GC::Cell*>(reference.value().low()));
|
||||
auto const& field_types = configuration.frame().module().canonical_types()[args.type_index.value()]->expansion().get<StructType>().fields();
|
||||
instance.fields()[args.field_index] = pack_into_field(field_types[args.field_index].type(), value);
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_new)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto const& type = *configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()];
|
||||
auto const& field_type = type.expansion().get<ArrayType>().type();
|
||||
// bounds checked by verifier.
|
||||
auto count = configuration.take_source<source_address_mix>(0, addresses.sources).template to<u32>();
|
||||
auto& slot = configuration.source_value<source_address_mix>(1, addresses.sources);
|
||||
Vector<Value> elements;
|
||||
GC_TRAP_IF(elements.try_ensure_capacity(count).is_error(), "Out of memory");
|
||||
// The initializer stays live in `slot` (a scanned location) if the allocation below triggers a collection.
|
||||
auto const element = pack_into_field(field_type.type(), slot);
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
elements.unchecked_append(element);
|
||||
auto instance = configuration.store().heap().allocate<ArrayInstance>(type, move(elements));
|
||||
slot = Value(Reference { Reference::GcObject { instance.ptr() } });
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_new_default)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto const& type = *configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()];
|
||||
auto const& field_type = type.expansion().get<ArrayType>().type();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
auto count = slot.template to<u32>();
|
||||
Vector<Value> elements;
|
||||
GC_TRAP_IF(elements.try_ensure_capacity(count).is_error(), "Out of memory");
|
||||
auto const element = Value(field_type.type().unpacked());
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
elements.unchecked_append(element);
|
||||
auto instance = configuration.store().heap().allocate<ArrayInstance>(type, move(elements));
|
||||
slot = Value(Reference { Reference::GcObject { instance.ptr() } });
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_new_fixed)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto& args = instruction->arguments().get<Instruction::ArrayNewFixedArgs>();
|
||||
auto const& type = *configuration.frame().module().canonical_types()[args.type_index.value()];
|
||||
auto const& field_type = type.expansion().get<ArrayType>().type();
|
||||
auto operands = configuration.value_stack().span().slice_from_end(args.count);
|
||||
Vector<Value> elements;
|
||||
elements.ensure_capacity(args.count);
|
||||
for (auto const& operand : operands)
|
||||
elements.unchecked_append(pack_into_field(field_type.type(), operand));
|
||||
auto instance = configuration.store().heap().allocate<ArrayInstance>(type, move(elements));
|
||||
configuration.value_stack().shrink(configuration.value_stack().size() - args.count);
|
||||
configuration.push_to_destination<source_address_mix>(Value(Reference { Reference::GcObject { instance.ptr() } }), addresses.destination);
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_new_data)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto& args = instruction->arguments().get<Instruction::ArrayDataArgs>();
|
||||
auto const& type = *configuration.frame().module().canonical_types()[args.type_index.value()];
|
||||
auto const& field_type = type.expansion().get<ArrayType>().type();
|
||||
auto const element_size = storage_type_byte_width(field_type.type());
|
||||
// bounds checked by verifier.
|
||||
auto count = configuration.take_source<source_address_mix>(0, addresses.sources).template to<u32>();
|
||||
auto& slot = configuration.source_value<source_address_mix>(1, addresses.sources);
|
||||
auto offset = slot.template to<u32>();
|
||||
auto const& data = *configuration.store().get(configuration.frame().module().datas()[args.data_index.value()]);
|
||||
GC_TRAP_IF(static_cast<u64>(offset) + static_cast<u64>(count) * element_size > data.size(), "out of bounds memory access");
|
||||
Vector<Value> elements;
|
||||
GC_TRAP_IF(elements.try_ensure_capacity(count).is_error(), "Out of memory");
|
||||
auto bytes = data.data().span();
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
elements.unchecked_append(value_from_segment_bytes(field_type.type(), bytes.slice(offset + static_cast<size_t>(i) * element_size, element_size)));
|
||||
auto instance = configuration.store().heap().allocate<ArrayInstance>(type, move(elements));
|
||||
slot = Value(Reference { Reference::GcObject { instance.ptr() } });
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_new_elem)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
{
|
||||
auto& args = instruction->arguments().get<Instruction::ArrayElemArgs>();
|
||||
auto const& type = *configuration.frame().module().canonical_types()[args.type_index.value()];
|
||||
// bounds checked by verifier.
|
||||
auto count = configuration.take_source<source_address_mix>(0, addresses.sources).template to<u32>();
|
||||
auto& slot = configuration.source_value<source_address_mix>(1, addresses.sources);
|
||||
auto offset = slot.template to<u32>();
|
||||
auto const& references = configuration.store().get(configuration.frame().module().elements()[args.element_index.value()])->references();
|
||||
GC_TRAP_IF(static_cast<u64>(offset) + count > references.size(), "out of bounds table access");
|
||||
Vector<Value> elements;
|
||||
GC_TRAP_IF(elements.try_ensure_capacity(count).is_error(), "Out of memory");
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
elements.unchecked_append(Value(references[offset + i]));
|
||||
auto instance = configuration.store().heap().allocate<ArrayInstance>(type, move(elements));
|
||||
slot = Value(Reference { Reference::GcObject { instance.ptr() } });
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
template<SourceAddressMix mix>
|
||||
static bool array_get_impl(BytecodeInterpreter& interpreter, Configuration& configuration, Instruction const* instruction, SourcesAndDestination const& addresses, bool sign_extend)
|
||||
{
|
||||
// bounds checked by verifier.
|
||||
auto index = configuration.take_source<mix>(0, addresses.sources).template to<u32>();
|
||||
auto& slot = configuration.source_value<mix>(1, addresses.sources);
|
||||
if (is_null_gc_reference(slot)) [[unlikely]]
|
||||
return interpreter.set_trap("null array reference"sv);
|
||||
auto& instance = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(slot.value().low()));
|
||||
if (index >= instance.elements().size()) [[unlikely]]
|
||||
return interpreter.set_trap("out of bounds array access"sv);
|
||||
auto const& field_type = configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()]->expansion().get<ArrayType>().type();
|
||||
slot = unpack_from_field(field_type.type(), instance.elements()[index], sign_extend);
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(array_get)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
if (array_get_impl<source_address_mix>(interpreter, configuration, instruction, addresses, false))
|
||||
return Outcome::Return;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(array_get_s)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
if (array_get_impl<source_address_mix>(interpreter, configuration, instruction, addresses, true))
|
||||
return Outcome::Return;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(array_get_u)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
if (array_get_impl<source_address_mix>(interpreter, configuration, instruction, addresses, false))
|
||||
return Outcome::Return;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_set)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
// bounds checked by verifier.
|
||||
auto value = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto index = configuration.take_source<source_address_mix>(1, addresses.sources).template to<u32>();
|
||||
auto reference = configuration.take_source<source_address_mix>(2, addresses.sources);
|
||||
GC_TRAP_IF(is_null_gc_reference(reference), "null array reference");
|
||||
auto& instance = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(reference.value().low()));
|
||||
GC_TRAP_IF(index >= instance.elements().size(), "out of bounds array access");
|
||||
auto const& field_type = configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()]->expansion().get<ArrayType>().type();
|
||||
instance.elements()[index] = pack_into_field(field_type.type(), value);
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_len)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
GC_TRAP_IF(is_null_gc_reference(slot), "null array reference");
|
||||
auto& instance = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(slot.value().low()));
|
||||
slot = Value(static_cast<i32>(instance.elements().size()));
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_fill)
|
||||
{
|
||||
LOG_INSN;
|
||||
auto& value_stack = configuration.value_stack();
|
||||
auto count = value_stack.take_last().to<u32>();
|
||||
auto value = value_stack.take_last();
|
||||
auto offset = value_stack.take_last().to<u32>();
|
||||
auto reference = value_stack.take_last();
|
||||
GC_TRAP_IF(is_null_gc_reference(reference), "null array reference");
|
||||
auto& instance = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(reference.value().low()));
|
||||
GC_TRAP_IF(static_cast<u64>(offset) + count > instance.elements().size(), "out of bounds array access");
|
||||
auto const& field_type = configuration.frame().module().canonical_types()[instruction->arguments().get<TypeIndex>().value()]->expansion().get<ArrayType>().type();
|
||||
auto const element = pack_into_field(field_type.type(), value);
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
instance.elements()[offset + i] = element;
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_copy)
|
||||
{
|
||||
LOG_INSN;
|
||||
auto& value_stack = configuration.value_stack();
|
||||
auto count = value_stack.take_last().to<u32>();
|
||||
auto source_offset = value_stack.take_last().to<u32>();
|
||||
auto source_reference = value_stack.take_last();
|
||||
auto destination_offset = value_stack.take_last().to<u32>();
|
||||
auto destination_reference = value_stack.take_last();
|
||||
GC_TRAP_IF(is_null_gc_reference(destination_reference), "null array reference");
|
||||
GC_TRAP_IF(is_null_gc_reference(source_reference), "null array reference");
|
||||
auto& destination = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(destination_reference.value().low()));
|
||||
auto& source = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(source_reference.value().low()));
|
||||
GC_TRAP_IF(static_cast<u64>(destination_offset) + count > destination.elements().size(), "out of bounds array access");
|
||||
GC_TRAP_IF(static_cast<u64>(source_offset) + count > source.elements().size(), "out of bounds array access");
|
||||
if (destination_offset <= source_offset) {
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
destination.elements()[destination_offset + i] = source.elements()[source_offset + i];
|
||||
} else {
|
||||
for (u32 i = count; i > 0; --i)
|
||||
destination.elements()[destination_offset + i - 1] = source.elements()[source_offset + i - 1];
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_init_data)
|
||||
{
|
||||
LOG_INSN;
|
||||
auto& args = instruction->arguments().get<Instruction::ArrayDataArgs>();
|
||||
auto& value_stack = configuration.value_stack();
|
||||
auto count = value_stack.take_last().to<u32>();
|
||||
auto source_offset = value_stack.take_last().to<u32>();
|
||||
auto destination_offset = value_stack.take_last().to<u32>();
|
||||
auto reference = value_stack.take_last();
|
||||
GC_TRAP_IF(is_null_gc_reference(reference), "null array reference");
|
||||
auto& instance = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(reference.value().low()));
|
||||
GC_TRAP_IF(static_cast<u64>(destination_offset) + count > instance.elements().size(), "out of bounds array access");
|
||||
auto const& field_type = configuration.frame().module().canonical_types()[args.type_index.value()]->expansion().get<ArrayType>().type();
|
||||
auto const element_size = storage_type_byte_width(field_type.type());
|
||||
auto const& data = *configuration.store().get(configuration.frame().module().datas()[args.data_index.value()]);
|
||||
GC_TRAP_IF(static_cast<u64>(source_offset) + static_cast<u64>(count) * element_size > data.size(), "out of bounds memory access");
|
||||
auto bytes = data.data().span();
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
instance.elements()[destination_offset + i] = value_from_segment_bytes(field_type.type(), bytes.slice(source_offset + static_cast<size_t>(i) * element_size, element_size));
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(array_init_elem)
|
||||
{
|
||||
LOG_INSN;
|
||||
auto& args = instruction->arguments().get<Instruction::ArrayElemArgs>();
|
||||
auto& value_stack = configuration.value_stack();
|
||||
auto count = value_stack.take_last().to<u32>();
|
||||
auto source_offset = value_stack.take_last().to<u32>();
|
||||
auto destination_offset = value_stack.take_last().to<u32>();
|
||||
auto reference = value_stack.take_last();
|
||||
GC_TRAP_IF(is_null_gc_reference(reference), "null array reference");
|
||||
auto& instance = *static_cast<ArrayInstance*>(bit_cast<GC::Cell*>(reference.value().low()));
|
||||
GC_TRAP_IF(static_cast<u64>(destination_offset) + count > instance.elements().size(), "out of bounds array access");
|
||||
auto const& references = configuration.store().get(configuration.frame().module().elements()[args.element_index.value()])->references();
|
||||
GC_TRAP_IF(static_cast<u64>(source_offset) + count > references.size(), "out of bounds table access");
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
instance.elements()[destination_offset + i] = Value(references[source_offset + i]);
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(ref_test)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
slot = Value(static_cast<i32>(reference_matches_type(slot, instruction->arguments().get<ValueType>(), configuration) ? 1 : 0));
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
ALIAS_INSTRUCTION(ref_test_null, ref_test)
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(ref_cast)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto const& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
GC_TRAP_IF(!reference_matches_type(slot, instruction->arguments().get<ValueType>(), configuration), "cast failure");
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
ALIAS_INSTRUCTION(ref_cast_null, ref_cast)
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions
|
||||
HANDLE_INSTRUCTION(br_on_cast)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& args = instruction->arguments().get<Instruction::BranchOnCastArgs>();
|
||||
// bounds checked by verifier.
|
||||
auto value = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto const matches = reference_matches_type(value, args.target_type, configuration);
|
||||
configuration.value_stack().append(value);
|
||||
short_ip.current_ip_value = interpreter.branch_to_label<true>(configuration, args.branch.label, short_ip.current_ip_value, matches).value();
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions
|
||||
HANDLE_INSTRUCTION(br_on_cast_fail)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& args = instruction->arguments().get<Instruction::BranchOnCastArgs>();
|
||||
// bounds checked by verifier.
|
||||
auto value = configuration.take_source<source_address_mix>(0, addresses.sources);
|
||||
auto const matches = reference_matches_type(value, args.target_type, configuration);
|
||||
configuration.value_stack().append(value);
|
||||
short_ip.current_ip_value = interpreter.branch_to_label<true>(configuration, args.branch.label, short_ip.current_ip_value, !matches).value();
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(any_convert_extern)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
auto const low = slot.value().low();
|
||||
switch (slot.value().high()) {
|
||||
case 3:
|
||||
slot = Value(u128(0, 8));
|
||||
break;
|
||||
case 1:
|
||||
slot = Value(u128(low, 10));
|
||||
break;
|
||||
case 9 | (6 << 8):
|
||||
slot = Value(u128(low, 6));
|
||||
break;
|
||||
case 9 | (7 << 8):
|
||||
slot = Value(u128(low, 7));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(extern_convert_any)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
auto const low = slot.value().low();
|
||||
switch (slot.value().high()) {
|
||||
case 8:
|
||||
slot = Value(u128(0, 3));
|
||||
break;
|
||||
case 6:
|
||||
slot = Value(u128(low, 9 | (6 << 8)));
|
||||
break;
|
||||
case 7:
|
||||
slot = Value(u128(low, 9 | (7 << 8)));
|
||||
break;
|
||||
case 10:
|
||||
slot = Value(u128(low, 1));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(ref_i31)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
slot = Value(u128(static_cast<u64>(slot.template to<u32>() & 0x7fffffff), 7));
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#reference-instructions
|
||||
HANDLE_INSTRUCTION(i31_get_s)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
GC_TRAP_IF(is_null_gc_reference(slot), "null i31 reference");
|
||||
auto const payload = static_cast<u32>(slot.value().low() & 0x7fffffff);
|
||||
slot = Value(static_cast<i32>(payload << 1) >> 1);
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
HANDLE_INSTRUCTION(i31_get_u)
|
||||
{
|
||||
LOG_INSN;
|
||||
LOAD_ADDRESSES();
|
||||
auto& slot = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
|
||||
GC_TRAP_IF(is_null_gc_reference(slot), "null i31 reference");
|
||||
slot = Value(static_cast<u32>(slot.value().low() & 0x7fffffff));
|
||||
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
|
||||
}
|
||||
|
||||
#undef GC_TRAP_IF
|
||||
|
||||
bool BytecodeInterpreter::trap_if_insufficient_native_stack_space(size_t minimum_native_stack_space_to_keep_free)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -349,9 +349,12 @@ ErrorOr<void, ValidationError> Validator::validate(ElementSection const& section
|
|||
for (auto& expression : segment.init) {
|
||||
if (expression.instructions().is_empty())
|
||||
continue;
|
||||
// https://webassembly.github.io/spec/core/valid/modules.html#element-segments
|
||||
auto result = TRY(validate(expression, { segment.type }));
|
||||
if (!result.is_constant)
|
||||
return Errors::invalid("element initializer"sv);
|
||||
if (result.result_types.size() != 1)
|
||||
return Errors::invalid("element initializer type"sv, segment.type, result.result_types);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -171,6 +171,13 @@ class GeneratedAnyExternRef:
|
|||
pass
|
||||
|
||||
|
||||
# A gc reference expectation with no concrete index, e.g. `(ref.struct)`: any non-null
|
||||
# reference. The harness cannot inspect the heap type, so only non-nullness is checked.
|
||||
@dataclass
|
||||
class GeneratedAnyGCRef:
|
||||
kind: str
|
||||
|
||||
|
||||
GeneratedValue = Union[
|
||||
str,
|
||||
ArithmeticNan,
|
||||
|
|
@ -179,6 +186,7 @@ GeneratedValue = Union[
|
|||
GeneratedEitherOf,
|
||||
GeneratedAnyFuncRef,
|
||||
GeneratedAnyExternRef,
|
||||
GeneratedAnyGCRef,
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -327,7 +335,11 @@ def gen_vector(vec: WasmVector, *, array=False) -> str:
|
|||
|
||||
def gen_value_arg(value: WasmValue) -> str:
|
||||
if isinstance(value, WasmGCValue):
|
||||
return "null"
|
||||
if value.value is None or value.value == "null":
|
||||
return "null"
|
||||
# A host reference (e.g. `(ref.host 2)` passed as anyref); the harness passes host
|
||||
# references by their address.
|
||||
return value.value
|
||||
|
||||
if isinstance(value, WasmVector):
|
||||
return gen_vector(value)
|
||||
|
|
@ -389,6 +401,17 @@ def gen_value_result(value: WasmValue) -> GeneratedValue:
|
|||
if isinstance(value, EitherOf):
|
||||
return GeneratedEitherOf([gen_value_result(option) for option in value.options])
|
||||
|
||||
if isinstance(value, WasmGCValue):
|
||||
if value.value == "null":
|
||||
return "null"
|
||||
# A bottom-type reference (e.g. `(ref.null none)`) can only ever be null.
|
||||
if value.kind in ("nullref", "nullexnref", "nullexternref"):
|
||||
return "null"
|
||||
if value.value is None:
|
||||
return GeneratedAnyGCRef(value.kind)
|
||||
# A host reference result, surfaced by its address (e.g. `(ref.host 1)` as anyref).
|
||||
return value.value
|
||||
|
||||
if value.kind == "funcref" and value.value is None:
|
||||
return GeneratedAnyFuncRef()
|
||||
|
||||
|
|
@ -486,7 +509,7 @@ expect(() => parseWebAssemblyModule(content, globalImportObject)).toThrow(Error,
|
|||
|
||||
|
||||
def gen_pretty_expect(expr: str, got: str, expect: str):
|
||||
print(f"if (!{expr}) {{ expect().fail(`Failed with ${{{got}}}, expected {expect}`); }}")
|
||||
print(f"if (!({expr})) {{ expect().fail(`Failed with ${{{got}}}, expected {expect}`); }}")
|
||||
|
||||
|
||||
def gen_expectation(gen_result: GeneratedValue, module: str):
|
||||
|
|
@ -510,6 +533,16 @@ def gen_expectation(gen_result: GeneratedValue, module: str):
|
|||
"(ref.extern)",
|
||||
)
|
||||
return
|
||||
if isinstance(gen_result, GeneratedAnyGCRef):
|
||||
# Null gc references surface as JS null; the harness cannot check the heap type of a
|
||||
# live one, only that it is not null.
|
||||
print(f"/* {gen_result} */ ", end="")
|
||||
gen_pretty_expect(
|
||||
"_result !== null",
|
||||
"_result",
|
||||
f"(ref.{gen_result.kind})",
|
||||
)
|
||||
return
|
||||
if isinstance(gen_result, ArithmeticNan):
|
||||
print(f"/* {gen_result} */ ", end="")
|
||||
gen_pretty_expect(
|
||||
|
|
|
|||
|
|
@ -356,7 +356,8 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export)
|
|||
case Wasm::ValueType::I31Reference:
|
||||
case Wasm::ValueType::StructReference:
|
||||
case Wasm::ValueType::ArrayReference:
|
||||
case Wasm::ValueType::NoneReference: {
|
||||
case Wasm::ValueType::NoneReference:
|
||||
case Wasm::ValueType::TypeUseReference: {
|
||||
auto ref = global->value().to<Wasm::Reference>();
|
||||
return ref.ref().visit(
|
||||
[&](Wasm::Reference::Null const&) -> JS::Value { return JS::js_null(); },
|
||||
|
|
@ -367,7 +368,6 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export)
|
|||
}
|
||||
case Wasm::ValueType::I8:
|
||||
case Wasm::ValueType::I16:
|
||||
case Wasm::ValueType::TypeUseReference:
|
||||
return vm.throw_completion<JS::TypeError>("Unsupported heap reference"sv);
|
||||
}
|
||||
}
|
||||
|
|
@ -461,9 +461,16 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
|
|||
else
|
||||
return vm.throw_completion<JS::TypeError>("GC Heap references are not supported"sv);
|
||||
break;
|
||||
case Wasm::ValueType::Kind::AnyReference:
|
||||
if (argument.is_null()) {
|
||||
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(param.kind()) } }));
|
||||
break;
|
||||
}
|
||||
// A host reference passed by address (e.g. a `(ref.host n)` spec-test argument); it enters the any hierarchy as an internalized extern reference.
|
||||
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Extern { static_cast<u64>(double_value) } }));
|
||||
break;
|
||||
case Wasm::ValueType::Kind::NoFunctionReference:
|
||||
case Wasm::ValueType::Kind::NoExternReference:
|
||||
case Wasm::ValueType::Kind::AnyReference:
|
||||
case Wasm::ValueType::Kind::EqReference:
|
||||
case Wasm::ValueType::Kind::I31Reference:
|
||||
case Wasm::ValueType::Kind::StructReference:
|
||||
|
|
@ -519,12 +526,11 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
|
|||
case Wasm::ValueType::StructReference:
|
||||
case Wasm::ValueType::ArrayReference:
|
||||
case Wasm::ValueType::NoneReference:
|
||||
return (value.to<Wasm::Reference>()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](Wasm::Reference::I31 const& ref) { return JS::Value(static_cast<double>(ref.value)); }, [&](Wasm::Reference::GcObject const&) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast<double>(ref.address.value())); });
|
||||
case Wasm::ValueType::TypeUseReference:
|
||||
return (value.to<Wasm::Reference>()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::js_undefined(); }, [&](Wasm::Reference::I31 const& ref) { return JS::Value(static_cast<double>(ref.value)); }, [&](Wasm::Reference::GcObject const&) { return JS::js_undefined(); }, [&](auto const& ref) { return JS::Value(static_cast<double>(ref.address.value())); });
|
||||
case Wasm::ValueType::ExceptionReference:
|
||||
case Wasm::ValueType::NoExceptionReference:
|
||||
return JS::js_null();
|
||||
case Wasm::ValueType::TypeUseReference:
|
||||
return JS::js_null();
|
||||
case Wasm::ValueType::I8:
|
||||
case Wasm::ValueType::I16:
|
||||
return vm.throw_completion<JS::TypeError>("Unsupported packed type"sv);
|
||||
|
|
|
|||
Loading…
Reference in a new issue