Serenity Operating System
at master 273 lines 12 kB view raw
1/* 2 * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/MemoryStream.h> 8#include <LibTest/JavaScriptTestRunner.h> 9#include <LibWasm/AbstractMachine/BytecodeInterpreter.h> 10#include <LibWasm/Types.h> 11#include <string.h> 12 13TEST_ROOT("Userland/Libraries/LibWasm/Tests"); 14 15TESTJS_GLOBAL_FUNCTION(read_binary_wasm_file, readBinaryWasmFile) 16{ 17 auto& realm = *vm.current_realm(); 18 19 auto error_code_to_string = [](int code) { 20 auto const* error_string = strerror(code); 21 return StringView { error_string, strlen(error_string) }; 22 }; 23 24 auto filename = TRY(vm.argument(0).to_deprecated_string(vm)); 25 auto file = Core::File::open(filename, Core::File::OpenMode::Read); 26 if (file.is_error()) 27 return vm.throw_completion<JS::TypeError>(error_code_to_string(file.error().code())); 28 29 auto file_size = file.value()->size(); 30 if (file_size.is_error()) 31 return vm.throw_completion<JS::TypeError>(error_code_to_string(file_size.error().code())); 32 33 auto array = TRY(JS::Uint8Array::create(realm, file_size.value())); 34 35 auto read = file.value()->read_until_filled(array->data()); 36 if (read.is_error()) 37 return vm.throw_completion<JS::TypeError>(error_code_to_string(read.error().code())); 38 39 return JS::Value(array); 40} 41 42class WebAssemblyModule final : public JS::Object { 43 JS_OBJECT(WebAssemblyModule, JS::Object); 44 45public: 46 explicit WebAssemblyModule(JS::Object& prototype) 47 : JS::Object(ConstructWithPrototypeTag::Tag, prototype) 48 { 49 m_machine.enable_instruction_count_limit(); 50 } 51 52 static Wasm::AbstractMachine& machine() { return m_machine; } 53 Wasm::Module& module() { return *m_module; } 54 Wasm::ModuleInstance& module_instance() { return *m_module_instance; } 55 56 static JS::ThrowCompletionOr<WebAssemblyModule*> create(JS::Realm& realm, Wasm::Module module, HashMap<Wasm::Linker::Name, Wasm::ExternValue> const& imports) 57 { 58 auto& vm = realm.vm(); 59 auto instance = MUST_OR_THROW_OOM(realm.heap().allocate<WebAssemblyModule>(realm, *realm.intrinsics().object_prototype())); 60 instance->m_module = move(module); 61 Wasm::Linker linker(*instance->m_module); 62 linker.link(imports); 63 linker.link(spec_test_namespace()); 64 auto link_result = linker.finish(); 65 if (link_result.is_error()) 66 return vm.throw_completion<JS::TypeError>("Link failed"sv); 67 auto result = machine().instantiate(*instance->m_module, link_result.release_value()); 68 if (result.is_error()) 69 return vm.throw_completion<JS::TypeError>(result.release_error().error); 70 instance->m_module_instance = result.release_value(); 71 return instance.ptr(); 72 } 73 JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; 74 75 ~WebAssemblyModule() override = default; 76 77private: 78 JS_DECLARE_NATIVE_FUNCTION(get_export); 79 JS_DECLARE_NATIVE_FUNCTION(wasm_invoke); 80 81 static HashMap<Wasm::Linker::Name, Wasm::ExternValue> const& spec_test_namespace() 82 { 83 if (!s_spec_test_namespace.is_empty()) 84 return s_spec_test_namespace; 85 Wasm::FunctionType print_i32_type { { Wasm::ValueType(Wasm::ValueType::I32) }, {} }; 86 87 auto address = m_machine.store().allocate(Wasm::HostFunction { 88 [](auto&, auto&) -> Wasm::Result { 89 // Noop, this just needs to exist. 90 return Wasm::Result { Vector<Wasm::Value> {} }; 91 }, 92 print_i32_type }); 93 s_spec_test_namespace.set({ "spectest", "print_i32", print_i32_type }, Wasm::ExternValue { *address }); 94 95 return s_spec_test_namespace; 96 } 97 98 static HashMap<Wasm::Linker::Name, Wasm::ExternValue> s_spec_test_namespace; 99 static Wasm::AbstractMachine m_machine; 100 Optional<Wasm::Module> m_module; 101 OwnPtr<Wasm::ModuleInstance> m_module_instance; 102}; 103 104Wasm::AbstractMachine WebAssemblyModule::m_machine; 105HashMap<Wasm::Linker::Name, Wasm::ExternValue> WebAssemblyModule::s_spec_test_namespace; 106 107TESTJS_GLOBAL_FUNCTION(parse_webassembly_module, parseWebAssemblyModule) 108{ 109 auto& realm = *vm.current_realm(); 110 auto* object = TRY(vm.argument(0).to_object(vm)); 111 if (!is<JS::Uint8Array>(object)) 112 return vm.throw_completion<JS::TypeError>("Expected a Uint8Array argument to parse_webassembly_module"sv); 113 auto& array = static_cast<JS::Uint8Array&>(*object); 114 FixedMemoryStream stream { array.data() }; 115 auto result = Wasm::Module::parse(stream); 116 if (result.is_error()) 117 return vm.throw_completion<JS::SyntaxError>(Wasm::parse_error_to_deprecated_string(result.error())); 118 119 HashMap<Wasm::Linker::Name, Wasm::ExternValue> imports; 120 auto import_value = vm.argument(1); 121 if (import_value.is_object()) { 122 auto& import_object = import_value.as_object(); 123 for (auto& property : import_object.shape().property_table()) { 124 auto value = import_object.get_without_side_effects(property.key); 125 if (!value.is_object() || !is<WebAssemblyModule>(value.as_object())) 126 continue; 127 auto& module_object = static_cast<WebAssemblyModule&>(value.as_object()); 128 for (auto& entry : module_object.module_instance().exports()) { 129 // FIXME: Don't pretend that everything is a function 130 imports.set({ property.key.as_string(), entry.name(), Wasm::TypeIndex(0) }, entry.value()); 131 } 132 } 133 } 134 135 return JS::Value(TRY(WebAssemblyModule::create(realm, result.release_value(), imports))); 136} 137 138TESTJS_GLOBAL_FUNCTION(compare_typed_arrays, compareTypedArrays) 139{ 140 auto* lhs = TRY(vm.argument(0).to_object(vm)); 141 if (!is<JS::TypedArrayBase>(lhs)) 142 return vm.throw_completion<JS::TypeError>("Expected a TypedArray"sv); 143 auto& lhs_array = static_cast<JS::TypedArrayBase&>(*lhs); 144 auto* rhs = TRY(vm.argument(1).to_object(vm)); 145 if (!is<JS::TypedArrayBase>(rhs)) 146 return vm.throw_completion<JS::TypeError>("Expected a TypedArray"sv); 147 auto& rhs_array = static_cast<JS::TypedArrayBase&>(*rhs); 148 return JS::Value(lhs_array.viewed_array_buffer()->buffer() == rhs_array.viewed_array_buffer()->buffer()); 149} 150 151JS::ThrowCompletionOr<void> WebAssemblyModule::initialize(JS::Realm& realm) 152{ 153 MUST_OR_THROW_OOM(Base::initialize(realm)); 154 define_native_function(realm, "getExport", get_export, 1, JS::default_attributes); 155 define_native_function(realm, "invoke", wasm_invoke, 1, JS::default_attributes); 156 157 return {}; 158} 159 160JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export) 161{ 162 auto name = TRY(vm.argument(0).to_deprecated_string(vm)); 163 auto this_value = vm.this_value(); 164 auto* object = TRY(this_value.to_object(vm)); 165 if (!is<WebAssemblyModule>(object)) 166 return vm.throw_completion<JS::TypeError>("Not a WebAssemblyModule"sv); 167 auto instance = static_cast<WebAssemblyModule*>(object); 168 for (auto& entry : instance->module_instance().exports()) { 169 if (entry.name() == name) { 170 auto& value = entry.value(); 171 if (auto ptr = value.get_pointer<Wasm::FunctionAddress>()) 172 return JS::Value(static_cast<unsigned long>(ptr->value())); 173 if (auto v = value.get_pointer<Wasm::GlobalAddress>()) { 174 return m_machine.store().get(*v)->value().value().visit( 175 [&](auto const& value) -> JS::Value { return JS::Value(static_cast<double>(value)); }, 176 [&](i32 value) { return JS::Value(static_cast<double>(value)); }, 177 [&](i64 value) -> JS::Value { return JS::BigInt::create(vm, Crypto::SignedBigInteger { value }); }, 178 [&](Wasm::Reference const& reference) -> JS::Value { 179 return reference.ref().visit( 180 [&](const Wasm::Reference::Null&) -> JS::Value { return JS::js_null(); }, 181 [&](const auto& ref) -> JS::Value { return JS::Value(static_cast<double>(ref.address.value())); }); 182 }); 183 } 184 return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("'{}' does not refer to a function or a global", name))); 185 } 186 } 187 return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("'{}' could not be found", name))); 188} 189 190JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) 191{ 192 auto address = static_cast<unsigned long>(TRY(vm.argument(0).to_double(vm))); 193 Wasm::FunctionAddress function_address { address }; 194 auto function_instance = WebAssemblyModule::machine().store().get(function_address); 195 if (!function_instance) 196 return vm.throw_completion<JS::TypeError>("Invalid function address"sv); 197 198 Wasm::FunctionType const* type { nullptr }; 199 function_instance->visit([&](auto& value) { type = &value.type(); }); 200 if (!type) 201 return vm.throw_completion<JS::TypeError>("Invalid function found at given address"sv); 202 203 Vector<Wasm::Value> arguments; 204 if (type->parameters().size() + 1 > vm.argument_count()) 205 return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Expected {} arguments for call, but found {}", type->parameters().size() + 1, vm.argument_count()))); 206 size_t index = 1; 207 for (auto& param : type->parameters()) { 208 auto argument = vm.argument(index++); 209 double double_value = 0; 210 if (!argument.is_bigint()) 211 double_value = TRY(argument.to_double(vm)); 212 switch (param.kind()) { 213 case Wasm::ValueType::Kind::I32: 214 arguments.append(Wasm::Value(param, static_cast<i64>(double_value))); 215 break; 216 case Wasm::ValueType::Kind::I64: 217 if (argument.is_bigint()) { 218 auto value = TRY(argument.to_bigint_int64(vm)); 219 arguments.append(Wasm::Value(param, value)); 220 } else { 221 arguments.append(Wasm::Value(param, static_cast<i64>(double_value))); 222 } 223 break; 224 case Wasm::ValueType::Kind::F32: 225 arguments.append(Wasm::Value(static_cast<float>(double_value))); 226 break; 227 case Wasm::ValueType::Kind::F64: 228 arguments.append(Wasm::Value(static_cast<double>(double_value))); 229 break; 230 case Wasm::ValueType::Kind::FunctionReference: 231 arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } })); 232 break; 233 case Wasm::ValueType::Kind::ExternReference: 234 arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } })); 235 break; 236 case Wasm::ValueType::Kind::NullFunctionReference: 237 arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } })); 238 break; 239 case Wasm::ValueType::Kind::NullExternReference: 240 arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExternReference) } })); 241 break; 242 } 243 } 244 245 auto result = WebAssemblyModule::machine().invoke(function_address, arguments); 246 if (result.is_trap()) 247 return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Execution trapped: {}", result.trap().reason))); 248 249 if (result.is_completion()) 250 return result.completion(); 251 252 if (result.values().is_empty()) 253 return JS::js_null(); 254 255 auto to_js_value = [&](Wasm::Value const& value) { 256 return value.value().visit( 257 [](auto const& value) { return JS::Value(static_cast<double>(value)); }, 258 [](i32 value) { return JS::Value(static_cast<double>(value)); }, 259 [&](i64 value) { return JS::Value(JS::BigInt::create(vm, Crypto::SignedBigInteger { value })); }, 260 [](Wasm::Reference const& reference) { 261 return reference.ref().visit( 262 [](const Wasm::Reference::Null&) { return JS::js_null(); }, 263 [](const auto& ref) { return JS::Value(static_cast<double>(ref.address.value())); }); 264 }); 265 }; 266 267 if (result.values().size() == 1) 268 return to_js_value(result.values().first()); 269 270 return JS::Array::create_from<Wasm::Value>(*vm.current_realm(), result.values(), [&](Wasm::Value value) { 271 return to_js_value(value); 272 }); 273}