Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWasm+AK: Sync Wasm changes from ladybird #24682

Merged
merged 9 commits into from
Jul 9, 2024
1 change: 1 addition & 0 deletions AK/ConstrainedStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ConstrainedStream : public Stream {
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
u64 remaining() const { return m_limit; }

private:
MaybeOwned<Stream> m_stream;
Expand Down
22 changes: 15 additions & 7 deletions Meta/generate-libwasm-spec-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def make_description(input_path: Path, name: str, out_path: Path) -> WastDescrip
return parse(description)


def gen_value(value: WasmValue) -> str:
def gen_value(value: WasmValue, as_arg=False) -> str:
def unsigned_to_signed(uint: int, bits: int) -> int:
max_value = 2**bits
if uint >= 2 ** (bits - 1):
Expand All @@ -221,7 +221,15 @@ def int_to_float64_bitcast(uint: int) -> float:
f = struct.unpack("d", b)[0]
return f

def float_to_str(f: float) -> str:
def float_to_str(bits: int, *, double=False, preserve_nan_sign=False) -> str:
f = int_to_float64_bitcast(bits) if double else int_to_float_bitcast(bits)

if math.isnan(f) and preserve_nan_sign:
f_bytes = bits.to_bytes(8 if double else 4, byteorder="little")
# -NaN does not preserve the sign bit in JavaScript land, so if
# we want to preserve NaN "sign", we pass in raw bytes
return f"new Uint8Array({list(f_bytes)})"

if math.isnan(f) and math.copysign(1.0, f) < 0:
return "-NaN"
elif math.isnan(f):
Expand All @@ -234,8 +242,6 @@ def float_to_str(f: float) -> str:

if value.value.startswith("nan"):
return "NaN"
elif value.value.startswith("-nan"):
return "-NaN"
elif value.value == "inf":
return "Infinity"
elif value.value == "-inf":
Expand All @@ -247,17 +253,19 @@ def float_to_str(f: float) -> str:
case "i64":
return str(unsigned_to_signed(int(value.value), 64)) + "n"
case "f32":
return float_to_str(int_to_float_bitcast(int(value.value)))
return float_to_str(
int(value.value), double=False, preserve_nan_sign=as_arg
)
case "f64":
return float_to_str(int_to_float64_bitcast(int(value.value)))
return float_to_str(int(value.value), double=True, preserve_nan_sign=as_arg)
case "externref" | "funcref" | "v128":
return value.value
case _:
raise GenerateException(f"Not implemented: {value.kind}")


def gen_args(args: list[WasmValue]) -> str:
return ",".join(gen_value(arg) for arg in args)
return ",".join(gen_value(arg, True) for arg in args)


def gen_module_command(command: ModuleCommand, ctx: Context):
Expand Down
33 changes: 24 additions & 9 deletions Tests/LibWasm/test-wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ class WebAssemblyModule final : public JS::Object {
// Noop, this just needs to exist.
return Wasm::Result { Vector<Wasm::Value> {} };
},
type });
type,
"__TEST" });
}

static HashMap<Wasm::Linker::Name, Wasm::ExternValue> s_spec_test_namespace;
Expand Down Expand Up @@ -256,7 +257,17 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
for (auto& param : type->parameters()) {
auto argument = vm.argument(index++);
double double_value = 0;
if (!argument.is_bigint())
if (argument.is_object()) {
auto object = MUST(argument.to_object(vm));
// Uint8Array allows for raw bytes to be passed into Wasm. This is
// particularly useful for NaN bit patterns
if (!is<JS::Uint8Array>(*object))
return vm.throw_completion<JS::TypeError>("Expected a Uint8Array object"sv);
auto& array = static_cast<JS::Uint8Array&>(*object);
if (array.array_length().length() > 8)
return vm.throw_completion<JS::TypeError>("Expected a Uint8Array of size <= 8"sv);
memcpy(&double_value, array.data().data(), array.array_length().length());
} else if (!argument.is_bigint())
double_value = TRY(argument.to_double(vm));
switch (param.kind()) {
case Wasm::ValueType::Kind::I32:
Expand All @@ -271,7 +282,17 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
}
break;
case Wasm::ValueType::Kind::F32:
arguments.append(Wasm::Value(static_cast<float>(double_value)));
// double_value should contain up to 8 bytes of information,
// if we were passed a Uint8Array. If the expected arg is a
// float, we were probably passed a Uint8Array of size 4. So
// we copy those bytes into a float value.
if (argument.is_object()) {
float float_value = 0;
memcpy(&float_value, &double_value, sizeof(float));
arguments.append(Wasm::Value(float_value));
} else {
arguments.append(Wasm::Value(static_cast<float>(double_value)));
}
break;
case Wasm::ValueType::Kind::F64:
arguments.append(Wasm::Value(static_cast<double>(double_value)));
Expand Down Expand Up @@ -308,12 +329,6 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
}
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Extern { static_cast<u64>(double_value) } }));
break;
case Wasm::ValueType::Kind::NullFunctionReference:
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } }));
break;
case Wasm::ValueType::Kind::NullExternReference:
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExternReference) } }));
break;
}
}

Expand Down
15 changes: 5 additions & 10 deletions Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,6 @@ class Value {
case ValueType::Kind::F64:
m_value = bit_cast<double>(raw_value);
break;
case ValueType::Kind::NullFunctionReference:
VERIFY(raw_value == 0);
m_value = Reference { Reference::Null { ValueType(ValueType::Kind::FunctionReference) } };
break;
case ValueType::Kind::NullExternReference:
VERIFY(raw_value == 0);
m_value = Reference { Reference::Null { ValueType(ValueType::Kind::ExternReference) } };
break;
case ValueType::Kind::V128:
m_value = u128(0ull, bit_cast<u64>(raw_value));
break;
Expand Down Expand Up @@ -184,7 +176,7 @@ class Value {
return type.ref().visit(
[](Reference::Func const&) { return ValueType::Kind::FunctionReference; },
[](Reference::Null const& null_type) {
return null_type.type.kind() == ValueType::ExternReference ? ValueType::Kind::NullExternReference : ValueType::Kind::NullFunctionReference;
return null_type.type.kind();
},
[](Reference::Extern const&) { return ValueType::Kind::ExternReference; });
}));
Expand Down Expand Up @@ -356,18 +348,21 @@ class WasmFunction {

class HostFunction {
public:
explicit HostFunction(AK::Function<Result(Configuration&, Vector<Value>&)> function, FunctionType const& type)
explicit HostFunction(AK::Function<Result(Configuration&, Vector<Value>&)> function, FunctionType const& type, ByteString name)
: m_function(move(function))
, m_type(type)
, m_name(move(name))
{
}

auto& function() { return m_function; }
auto& type() const { return m_type; }
auto& name() const { return m_name; }

private:
AK::Function<Result(Configuration&, Vector<Value>&)> m_function;
FunctionType m_type;
ByteString m_name;
};

using FunctionInstance = Variant<WasmFunction, HostFunction>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,10 +519,10 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
configuration.stack().push(Value(ValueType { ValueType::I64 }, instruction.arguments().get<i64>()));
return;
case Instructions::f32_const.value():
configuration.stack().push(Value(ValueType { ValueType::F32 }, static_cast<double>(instruction.arguments().get<float>())));
configuration.stack().push(Value(Value::AnyValueType(instruction.arguments().get<float>())));
return;
case Instructions::f64_const.value():
configuration.stack().push(Value(ValueType { ValueType::F64 }, instruction.arguments().get<double>()));
configuration.stack().push(Value(Value::AnyValueType(instruction.arguments().get<double>())));
return;
case Instructions::block.value(): {
size_t arity = 0;
Expand Down
34 changes: 15 additions & 19 deletions Userland/Libraries/LibWasm/AbstractMachine/Operators.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,12 @@ struct Minimum {
auto operator()(Lhs lhs, Rhs rhs) const
{
if constexpr (IsFloatingPoint<Lhs> || IsFloatingPoint<Rhs>) {
if (isnan(lhs))
return lhs;
if (isnan(rhs))
return rhs;
if (isinf(lhs))
return lhs > 0 ? rhs : lhs;
if (isinf(rhs))
return rhs > 0 ? lhs : rhs;
if (isnan(lhs) || isnan(rhs)) {
return isnan(lhs) ? lhs : rhs;
}
if (lhs == 0 && rhs == 0) {
return signbit(lhs) ? lhs : rhs;
}
}
return min(lhs, rhs);
}
Expand All @@ -351,14 +349,12 @@ struct Maximum {
auto operator()(Lhs lhs, Rhs rhs) const
{
if constexpr (IsFloatingPoint<Lhs> || IsFloatingPoint<Rhs>) {
if (isnan(lhs))
return lhs;
if (isnan(rhs))
return rhs;
if (isinf(lhs))
return lhs > 0 ? lhs : rhs;
if (isinf(rhs))
return rhs > 0 ? rhs : lhs;
if (isnan(lhs) || isnan(rhs)) {
return isnan(lhs) ? lhs : rhs;
}
if (lhs == 0 && rhs == 0) {
return signbit(lhs) ? rhs : lhs;
}
}
return max(lhs, rhs);
}
Expand Down Expand Up @@ -654,8 +650,8 @@ struct Convert {
template<typename Lhs>
ResultT operator()(Lhs lhs) const
{
auto signed_interpretation = bit_cast<MakeSigned<Lhs>>(lhs);
return static_cast<ResultT>(signed_interpretation);
auto interpretation = bit_cast<Lhs>(lhs);
return static_cast<ResultT>(interpretation);
}

static StringView name() { return "convert"sv; }
Expand Down Expand Up @@ -690,7 +686,7 @@ struct Demote {
return nanf(""); // FIXME: Ensure canonical NaN remains canonical

if (isinf(lhs))
return __builtin_huge_valf();
return copysignf(__builtin_huge_valf(), lhs);

return static_cast<float>(lhs);
}
Expand Down
7 changes: 7 additions & 0 deletions Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1939,6 +1939,13 @@ VALIDATE_INSTRUCTION(structured_end)

auto& last_frame = m_frames.last();

// If this is true, then the `if` had no else. In that case, validate that the
// empty else block produces the correct type.
if (last_frame.kind == FrameKind::If) {
bool is_constant = false;
TRY(validate(Instruction(Instructions::structured_else), stack, is_constant));
}

auto& results = last_frame.type.results();
for (size_t i = 1; i <= results.size(); ++i)
TRY(stack.take(results[results.size() - i]));
Expand Down
6 changes: 2 additions & 4 deletions Userland/Libraries/LibWasm/Parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1413,10 +1413,8 @@ ParseResult<Module> Module::parse(Stream& stream)
return with_eof_check(stream, ParseError::InvalidModuleVersion);

Vector<AnySection> sections;
for (;;) {
while (!stream.is_eof()) {
auto section_id_or_error = stream.read_value<u8>();
if (stream.is_eof())
break;
if (section_id_or_error.is_error())
return with_eof_check(stream, ParseError::ExpectedIndex);

Expand Down Expand Up @@ -1472,7 +1470,7 @@ ParseResult<Module> Module::parse(Stream& stream)
default:
return with_eof_check(stream, ParseError::InvalidIndex);
}
if (!section_stream.is_eof())
if (section_stream.remaining() != 0)
return ParseError::SectionSizeMismatch;
}

Expand Down
8 changes: 1 addition & 7 deletions Userland/Libraries/LibWasm/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ class ValueType {
V128,
FunctionReference,
ExternReference,
NullFunctionReference,
NullExternReference,
};

explicit ValueType(Kind kind)
Expand All @@ -176,7 +174,7 @@ class ValueType {

bool operator==(ValueType const&) const = default;

auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference || m_kind == NullExternReference || m_kind == NullFunctionReference; }
auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference; }
auto is_vector() const { return m_kind == V128; }
auto is_numeric() const { return !is_reference() && !is_vector(); }
auto kind() const { return m_kind; }
Expand All @@ -200,10 +198,6 @@ class ValueType {
return "funcref";
case ExternReference:
return "externref";
case NullFunctionReference:
return "ref.null externref";
case NullExternReference:
return "ref.null funcref";
}
VERIFY_NOT_REACHED();
}
Expand Down
3 changes: 2 additions & 1 deletion Userland/Libraries/LibWasm/WASI/Wasi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,8 @@ struct InvocationOf<impl> {
FunctionType {
move(arguments_types),
{ ValueType(ValueType::I32) },
});
},
function_name);
}
};

Expand Down
29 changes: 18 additions & 11 deletions Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS

return Wasm::Result { move(wasm_values) };
},
type
type,
ByteString::formatted("func{}", resolved_imports.size()),
};
auto address = cache.abstract_machine().store().allocate(move(host_function));
dbgln("Resolved to {}", address->value());
Expand Down Expand Up @@ -412,10 +413,9 @@ JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value va
auto number = TRY(value.to_double(vm));
return Wasm::Value { static_cast<float>(number) };
}
case Wasm::ValueType::FunctionReference:
case Wasm::ValueType::NullFunctionReference: {
case Wasm::ValueType::FunctionReference: {
if (value.is_null())
return Wasm::Value { Wasm::ValueType(Wasm::ValueType::NullExternReference), 0ull };
return Wasm::Value { Wasm::ValueType(Wasm::ValueType::FunctionReference), 0ull };

if (value.is_function()) {
auto& function = value.as_function();
Expand All @@ -429,7 +429,6 @@ JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value va
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Exported function");
}
case Wasm::ValueType::ExternReference:
case Wasm::ValueType::NullExternReference:
TODO();
case Wasm::ValueType::V128:
return vm.throw_completion<JS::TypeError>("Cannot convert a vector value to a javascript value"sv);
Expand All @@ -450,14 +449,22 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value)
return JS::Value(wasm_value.to<double>().value());
case Wasm::ValueType::F32:
return JS::Value(static_cast<double>(wasm_value.to<float>().value()));
case Wasm::ValueType::FunctionReference:
// FIXME: What's the name of a function reference that isn't exported?
return create_native_function(vm, wasm_value.to<Wasm::Reference::Func>().value().address, "FIXME_IHaveNoIdeaWhatThisShouldBeCalled");
case Wasm::ValueType::NullFunctionReference:
return JS::js_null();
case Wasm::ValueType::FunctionReference: {
auto address = wasm_value.to<Wasm::Reference::Func>().value().address;
auto& cache = get_cache(realm);
auto* function = cache.abstract_machine().store().get(address);
auto name = function->visit(
[&](Wasm::WasmFunction& wasm_function) {
auto index = *wasm_function.module().functions().find_first_index(address);
return ByteString::formatted("func{}", index);
},
[](Wasm::HostFunction& host_function) {
return host_function.name();
});
return create_native_function(vm, address, name);
}
case Wasm::ValueType::V128:
case Wasm::ValueType::ExternReference:
case Wasm::ValueType::NullExternReference:
TODO();
}
VERIFY_NOT_REACHED();
Expand Down
3 changes: 2 additions & 1 deletion Userland/Utilities/wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
result.append(Wasm::Value { result_type, 0ull });
return Wasm::Result { move(result) };
},
type));
type,
entry.name));
exports.set(entry, *address);
}

Expand Down