WebAssembly.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/MemoryStream.h>
  8. #include <AK/ScopeGuard.h>
  9. #include <AK/StringBuilder.h>
  10. #include <LibJS/Runtime/Array.h>
  11. #include <LibJS/Runtime/ArrayBuffer.h>
  12. #include <LibJS/Runtime/BigInt.h>
  13. #include <LibJS/Runtime/DataView.h>
  14. #include <LibJS/Runtime/Iterator.h>
  15. #include <LibJS/Runtime/NativeFunction.h>
  16. #include <LibJS/Runtime/Object.h>
  17. #include <LibJS/Runtime/Promise.h>
  18. #include <LibJS/Runtime/TypedArray.h>
  19. #include <LibJS/Runtime/VM.h>
  20. #include <LibWasm/AbstractMachine/Validator.h>
  21. #include <LibWeb/Bindings/ResponsePrototype.h>
  22. #include <LibWeb/Fetch/Response.h>
  23. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  24. #include <LibWeb/Platform/EventLoopPlugin.h>
  25. #include <LibWeb/WebAssembly/Instance.h>
  26. #include <LibWeb/WebAssembly/Memory.h>
  27. #include <LibWeb/WebAssembly/Module.h>
  28. #include <LibWeb/WebAssembly/Table.h>
  29. #include <LibWeb/WebAssembly/WebAssembly.h>
  30. #include <LibWeb/WebIDL/AbstractOperations.h>
  31. #include <LibWeb/WebIDL/Buffers.h>
  32. #include <LibWeb/WebIDL/Promise.h>
  33. namespace Web::WebAssembly {
  34. static JS::NonnullGCPtr<WebIDL::Promise> asynchronously_compile_webassembly_module(JS::VM&, ByteBuffer, HTML::Task::Source = HTML::Task::Source::Unspecified);
  35. static JS::NonnullGCPtr<WebIDL::Promise> instantiate_promise_of_module(JS::VM&, JS::NonnullGCPtr<WebIDL::Promise>, JS::GCPtr<JS::Object> import_object);
  36. static JS::NonnullGCPtr<WebIDL::Promise> asynchronously_instantiate_webassembly_module(JS::VM&, JS::NonnullGCPtr<Module>, JS::GCPtr<JS::Object> import_object);
  37. static JS::NonnullGCPtr<WebIDL::Promise> compile_potential_webassembly_response(JS::VM&, JS::NonnullGCPtr<WebIDL::Promise>);
  38. namespace Detail {
  39. HashMap<JS::GCPtr<JS::Object>, WebAssemblyCache> s_caches;
  40. WebAssemblyCache& get_cache(JS::Realm& realm)
  41. {
  42. return s_caches.ensure(realm.global_object());
  43. }
  44. }
  45. void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
  46. {
  47. auto& global_object = HTML::relevant_global_object(object);
  48. if (auto maybe_cache = Detail::s_caches.get(global_object); maybe_cache.has_value()) {
  49. auto& cache = maybe_cache.release_value();
  50. visitor.visit(cache.function_instances());
  51. visitor.visit(cache.imported_objects());
  52. visitor.visit(cache.extern_values());
  53. }
  54. }
  55. void finalize(JS::Object& object)
  56. {
  57. auto& global_object = HTML::relevant_global_object(object);
  58. Detail::s_caches.remove(global_object);
  59. }
  60. // https://webassembly.github.io/spec/js-api/#dom-webassembly-validate
  61. bool validate(JS::VM& vm, JS::Handle<WebIDL::BufferSource>& bytes)
  62. {
  63. // 1. Let stableBytes be a copy of the bytes held by the buffer bytes.
  64. auto stable_bytes = WebIDL::get_buffer_source_copy(*bytes->raw_object());
  65. if (stable_bytes.is_error()) {
  66. VERIFY(stable_bytes.error().code() == ENOMEM);
  67. return false;
  68. }
  69. // 2. Compile stableBytes as a WebAssembly module and store the results as module.
  70. auto module_or_error = Detail::compile_a_webassembly_module(vm, stable_bytes.release_value());
  71. // 3. If module is error, return false.
  72. if (module_or_error.is_error())
  73. return false;
  74. // 4. Return true.
  75. return true;
  76. }
  77. // https://webassembly.github.io/spec/js-api/#dom-webassembly-compile
  78. WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> compile(JS::VM& vm, JS::Handle<WebIDL::BufferSource>& bytes)
  79. {
  80. auto& realm = *vm.current_realm();
  81. // 1. Let stableBytes be a copy of the bytes held by the buffer bytes.
  82. auto stable_bytes = WebIDL::get_buffer_source_copy(*bytes->raw_object());
  83. if (stable_bytes.is_error()) {
  84. VERIFY(stable_bytes.error().code() == ENOMEM);
  85. return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)));
  86. }
  87. // 2. Asynchronously compile a WebAssembly module from stableBytes and return the result.
  88. return asynchronously_compile_webassembly_module(vm, stable_bytes.release_value());
  89. }
  90. // https://webassembly.github.io/spec/web-api/index.html#dom-webassembly-compilestreaming
  91. WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> compile_streaming(JS::VM& vm, JS::Handle<WebIDL::Promise> source)
  92. {
  93. // The compileStreaming(source) method, when invoked, returns the result of compiling a potential WebAssembly response with source.
  94. return compile_potential_webassembly_response(vm, *source);
  95. }
  96. // https://webassembly.github.io/spec/js-api/#dom-webassembly-instantiate
  97. WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> instantiate(JS::VM& vm, JS::Handle<WebIDL::BufferSource>& bytes, Optional<JS::Handle<JS::Object>>& import_object_handle)
  98. {
  99. auto& realm = *vm.current_realm();
  100. // 1. Let stableBytes be a copy of the bytes held by the buffer bytes.
  101. auto stable_bytes = WebIDL::get_buffer_source_copy(*bytes->raw_object());
  102. if (stable_bytes.is_error()) {
  103. VERIFY(stable_bytes.error().code() == ENOMEM);
  104. return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)));
  105. }
  106. // 2. Asynchronously compile a WebAssembly module from stableBytes and let promiseOfModule be the result.
  107. auto promise_of_module = asynchronously_compile_webassembly_module(vm, stable_bytes.release_value());
  108. // 3. Instantiate promiseOfModule with imports importObject and return the result.
  109. JS::GCPtr<JS::Object> const import_object = import_object_handle.has_value() ? import_object_handle.value().ptr() : nullptr;
  110. return instantiate_promise_of_module(vm, promise_of_module, import_object);
  111. }
  112. // https://webassembly.github.io/spec/js-api/#dom-webassembly-instantiate-moduleobject-importobject
  113. WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> instantiate(JS::VM& vm, Module const& module_object, Optional<JS::Handle<JS::Object>>& import_object)
  114. {
  115. // 1. Asynchronously instantiate the WebAssembly module moduleObject importing importObject, and return the result.
  116. JS::NonnullGCPtr<Module> module { const_cast<Module&>(module_object) };
  117. JS::GCPtr<JS::Object> const imports = import_object.has_value() ? import_object.value().ptr() : nullptr;
  118. return asynchronously_instantiate_webassembly_module(vm, module, imports);
  119. }
  120. // https://webassembly.github.io/spec/web-api/index.html#dom-webassembly-instantiatestreaming
  121. WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> instantiate_streaming(JS::VM& vm, JS::Handle<WebIDL::Promise> source, Optional<JS::Handle<JS::Object>>& import_object)
  122. {
  123. // The instantiateStreaming(source, importObject) method, when invoked, performs the following steps:
  124. // 1. Let promiseOfModule be the result of compiling a potential WebAssembly response with source.
  125. auto promise_of_module = compile_potential_webassembly_response(vm, *source);
  126. // 2. Return the result of instantiating the promise of a module promiseOfModule with imports importObject.
  127. auto imports = JS::GCPtr { import_object.has_value() ? import_object.value().ptr() : nullptr };
  128. return instantiate_promise_of_module(vm, promise_of_module, imports);
  129. }
  130. namespace Detail {
  131. JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM& vm, Wasm::Module const& module, JS::GCPtr<JS::Object> import_object)
  132. {
  133. Wasm::Linker linker { module };
  134. HashMap<Wasm::Linker::Name, Wasm::ExternValue> resolved_imports;
  135. auto& cache = get_cache(*vm.current_realm());
  136. if (import_object) {
  137. dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve stuff because import object was specified");
  138. for (Wasm::Linker::Name const& import_name : linker.unresolved_imports()) {
  139. dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve {}::{}", import_name.module, import_name.name);
  140. auto value_or_error = import_object->get(import_name.module);
  141. if (value_or_error.is_error())
  142. break;
  143. auto value = value_or_error.release_value();
  144. auto object_or_error = value.to_object(vm);
  145. if (object_or_error.is_error())
  146. break;
  147. auto object = object_or_error.release_value();
  148. auto import_or_error = object->get(import_name.name);
  149. if (import_or_error.is_error())
  150. break;
  151. auto import_ = import_or_error.release_value();
  152. TRY(import_name.type.visit(
  153. [&](Wasm::TypeIndex index) -> JS::ThrowCompletionOr<void> {
  154. dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve a function {}::{}, type index {}", import_name.module, import_name.name, index.value());
  155. auto& type = module.type_section().types()[index.value()];
  156. // FIXME: IsCallable()
  157. if (!import_.is_function())
  158. return {};
  159. auto& function = import_.as_function();
  160. cache.add_imported_object(function);
  161. // FIXME: If this is a function created by create_native_function(),
  162. // just extract its address and resolve to that.
  163. Wasm::HostFunction host_function {
  164. [&](auto&, auto& arguments) -> Wasm::Result {
  165. JS::MarkedVector<JS::Value> argument_values { vm.heap() };
  166. size_t index = 0;
  167. for (auto& entry : arguments) {
  168. argument_values.append(to_js_value(vm, entry, type.parameters()[index]));
  169. ++index;
  170. }
  171. auto result = TRY(JS::call(vm, function, JS::js_undefined(), argument_values.span()));
  172. if (type.results().is_empty())
  173. return Wasm::Result { Vector<Wasm::Value> {} };
  174. if (type.results().size() == 1)
  175. return Wasm::Result { Vector<Wasm::Value> { TRY(to_webassembly_value(vm, result, type.results().first())) } };
  176. auto method = TRY(result.get_method(vm, vm.names.iterator));
  177. if (method == JS::js_undefined())
  178. return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, result.to_string_without_side_effects());
  179. auto values = TRY(JS::iterator_to_list(vm, TRY(JS::get_iterator_from_method(vm, result, *method))));
  180. if (values.size() != type.results().size())
  181. return vm.throw_completion<JS::TypeError>(ByteString::formatted("Invalid number of return values for multi-value wasm return of {} objects", type.results().size()));
  182. Vector<Wasm::Value> wasm_values;
  183. TRY_OR_THROW_OOM(vm, wasm_values.try_ensure_capacity(values.size()));
  184. size_t i = 0;
  185. for (auto& value : values)
  186. wasm_values.append(TRY(to_webassembly_value(vm, value, type.results()[i++])));
  187. return Wasm::Result { move(wasm_values) };
  188. },
  189. type,
  190. ByteString::formatted("func{}", resolved_imports.size()),
  191. };
  192. auto address = cache.abstract_machine().store().allocate(move(host_function));
  193. dbgln_if(LIBWEB_WASM_DEBUG, "Resolved to {}", address->value());
  194. // FIXME: LinkError instead.
  195. VERIFY(address.has_value());
  196. resolved_imports.set(import_name, Wasm::ExternValue { Wasm::FunctionAddress { *address } });
  197. return {};
  198. },
  199. [&](Wasm::GlobalType const& type) -> JS::ThrowCompletionOr<void> {
  200. Optional<Wasm::GlobalAddress> address;
  201. // https://webassembly.github.io/spec/js-api/#read-the-imports step 5.1
  202. if (import_.is_number() || import_.is_bigint()) {
  203. if (import_.is_number() && type.type().kind() == Wasm::ValueType::I64) {
  204. // FIXME: Throw a LinkError instead.
  205. return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a Number to a BigInteger"sv);
  206. }
  207. if (import_.is_bigint() && type.type().kind() != Wasm::ValueType::I64) {
  208. // FIXME: Throw a LinkError instead.
  209. return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a BigInteger to a Number"sv);
  210. }
  211. auto cast_value = TRY(to_webassembly_value(vm, import_, type.type()));
  212. address = cache.abstract_machine().store().allocate({ type.type(), false }, cast_value);
  213. } else {
  214. // FIXME: https://webassembly.github.io/spec/js-api/#read-the-imports step 5.2
  215. // if v implements Global
  216. // let globaladdr be v.[[Global]]
  217. // FIXME: Throw a LinkError instead
  218. return vm.throw_completion<JS::TypeError>("LinkError: Invalid value for global type"sv);
  219. }
  220. resolved_imports.set(import_name, Wasm::ExternValue { *address });
  221. return {};
  222. },
  223. [&](Wasm::MemoryType const&) -> JS::ThrowCompletionOr<void> {
  224. if (!import_.is_object() || !is<WebAssembly::Memory>(import_.as_object())) {
  225. // FIXME: Throw a LinkError instead
  226. return vm.throw_completion<JS::TypeError>("LinkError: Expected an instance of WebAssembly.Memory for a memory import"sv);
  227. }
  228. auto address = static_cast<WebAssembly::Memory const&>(import_.as_object()).address();
  229. resolved_imports.set(import_name, Wasm::ExternValue { address });
  230. return {};
  231. },
  232. [&](Wasm::TableType const&) -> JS::ThrowCompletionOr<void> {
  233. if (!import_.is_object() || !is<WebAssembly::Table>(import_.as_object())) {
  234. // FIXME: Throw a LinkError instead
  235. return vm.throw_completion<JS::TypeError>("LinkError: Expected an instance of WebAssembly.Table for a table import"sv);
  236. }
  237. auto address = static_cast<WebAssembly::Table const&>(import_.as_object()).address();
  238. resolved_imports.set(import_name, Wasm::ExternValue { address });
  239. return {};
  240. },
  241. [&](auto const&) -> JS::ThrowCompletionOr<void> {
  242. // FIXME: Implement these.
  243. dbgln("Unimplemented import of non-function attempted");
  244. return vm.throw_completion<JS::TypeError>("LinkError: Not Implemented"sv);
  245. }));
  246. }
  247. }
  248. linker.link(resolved_imports);
  249. auto link_result = linker.finish();
  250. if (link_result.is_error()) {
  251. // FIXME: Throw a LinkError.
  252. StringBuilder builder;
  253. builder.append("LinkError: Missing "sv);
  254. builder.join(' ', link_result.error().missing_imports);
  255. return vm.throw_completion<JS::TypeError>(MUST(builder.to_string()));
  256. }
  257. auto instance_result = cache.abstract_machine().instantiate(module, link_result.release_value());
  258. if (instance_result.is_error()) {
  259. // FIXME: Throw a LinkError instead.
  260. return vm.throw_completion<JS::TypeError>(instance_result.error().error);
  261. }
  262. return instance_result.release_value();
  263. }
  264. // // https://webassembly.github.io/spec/js-api/#compile-a-webassembly-module
  265. JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webassembly_module(JS::VM& vm, ByteBuffer data)
  266. {
  267. FixedMemoryStream stream { data.bytes() };
  268. auto module_result = Wasm::Module::parse(stream);
  269. if (module_result.is_error()) {
  270. // FIXME: Throw CompileError instead.
  271. return vm.throw_completion<JS::TypeError>(Wasm::parse_error_to_byte_string(module_result.error()));
  272. }
  273. auto& cache = get_cache(*vm.current_realm());
  274. if (auto validation_result = cache.abstract_machine().validate(module_result.value()); validation_result.is_error()) {
  275. // FIXME: Throw CompileError instead.
  276. return vm.throw_completion<JS::TypeError>(validation_result.error().error_string);
  277. }
  278. auto compiled_module = make_ref_counted<CompiledWebAssemblyModule>(module_result.release_value());
  279. cache.add_compiled_module(compiled_module);
  280. return compiled_module;
  281. }
  282. JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress address, ByteString const& name, Instance* instance)
  283. {
  284. auto& realm = *vm.current_realm();
  285. Optional<Wasm::FunctionType> type;
  286. auto& cache = get_cache(realm);
  287. cache.abstract_machine().store().get(address)->visit([&](auto const& value) { type = value.type(); });
  288. if (auto entry = cache.get_function_instance(address); entry.has_value())
  289. return *entry;
  290. auto function = JS::NativeFunction::create(
  291. realm,
  292. name,
  293. [address, type = type.release_value(), instance](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
  294. (void)instance;
  295. auto& realm = *vm.current_realm();
  296. Vector<Wasm::Value> values;
  297. values.ensure_capacity(type.parameters().size());
  298. // Grab as many values as needed and convert them.
  299. size_t index = 0;
  300. for (auto& type : type.parameters())
  301. values.append(TRY(to_webassembly_value(vm, vm.argument(index++), type)));
  302. auto& cache = get_cache(realm);
  303. auto result = cache.abstract_machine().invoke(address, move(values));
  304. // FIXME: Use the convoluted mapping of errors defined in the spec.
  305. if (result.is_trap())
  306. return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Wasm execution trapped (WIP): {}", result.trap().reason)));
  307. if (result.values().is_empty())
  308. return JS::js_undefined();
  309. if (result.values().size() == 1)
  310. return to_js_value(vm, result.values().first(), type.results().first());
  311. // Put result values into a JS::Array in reverse order.
  312. auto js_result_values = JS::MarkedVector<JS::Value> { realm.heap() };
  313. js_result_values.ensure_capacity(result.values().size());
  314. for (size_t i = result.values().size(); i > 0; i--) {
  315. // Safety: ensure_capacity is called just before this.
  316. js_result_values.unchecked_append(to_js_value(vm, result.values().at(i - 1), type.results().at(i - 1)));
  317. }
  318. return JS::Value(JS::Array::create_from(realm, js_result_values));
  319. });
  320. cache.add_function_instance(address, function);
  321. return function;
  322. }
  323. JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value value, Wasm::ValueType const& type)
  324. {
  325. static ::Crypto::SignedBigInteger two_64 = "1"_sbigint.shift_left(64);
  326. switch (type.kind()) {
  327. case Wasm::ValueType::I64: {
  328. auto bigint = TRY(value.to_bigint(vm));
  329. auto value = bigint->big_integer().divided_by(two_64).remainder;
  330. VERIFY(value.unsigned_value().trimmed_length() <= 2);
  331. i64 integer = static_cast<i64>(value.unsigned_value().to_u64());
  332. if (value.is_negative())
  333. integer = -integer;
  334. return Wasm::Value { integer };
  335. }
  336. case Wasm::ValueType::I32: {
  337. auto _i32 = TRY(value.to_i32(vm));
  338. return Wasm::Value { static_cast<i32>(_i32) };
  339. }
  340. case Wasm::ValueType::F64: {
  341. auto number = TRY(value.to_double(vm));
  342. return Wasm::Value { static_cast<double>(number) };
  343. }
  344. case Wasm::ValueType::F32: {
  345. auto number = TRY(value.to_double(vm));
  346. return Wasm::Value { static_cast<float>(number) };
  347. }
  348. case Wasm::ValueType::FunctionReference: {
  349. if (value.is_null())
  350. return Wasm::Value(Wasm::ValueType { Wasm::ValueType::Kind::FunctionReference });
  351. if (value.is_function()) {
  352. auto& function = value.as_function();
  353. auto& cache = get_cache(*vm.current_realm());
  354. for (auto& entry : cache.function_instances()) {
  355. if (entry.value == &function)
  356. return Wasm::Value { Wasm::Reference { Wasm::Reference::Func { entry.key, cache.abstract_machine().store().get_module_for(entry.key) } } };
  357. }
  358. }
  359. return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Exported function");
  360. }
  361. case Wasm::ValueType::ExternReference: {
  362. if (value.is_null())
  363. return Wasm::Value(Wasm::ValueType { Wasm::ValueType::Kind::ExternReference });
  364. auto& cache = get_cache(*vm.current_realm());
  365. for (auto& entry : cache.extern_values()) {
  366. if (entry.value == value)
  367. return Wasm::Value { Wasm::Reference { Wasm::Reference::Extern { entry.key } } };
  368. }
  369. Wasm::ExternAddress extern_addr = cache.extern_values().size();
  370. cache.add_extern_value(extern_addr, value);
  371. return Wasm::Value { Wasm::Reference { Wasm::Reference::Extern { extern_addr } } };
  372. }
  373. case Wasm::ValueType::V128:
  374. return vm.throw_completion<JS::TypeError>("Cannot convert a vector value to a javascript value"sv);
  375. }
  376. VERIFY_NOT_REACHED();
  377. }
  378. Wasm::Value default_webassembly_value(JS::VM& vm, Wasm::ValueType type)
  379. {
  380. switch (type.kind()) {
  381. case Wasm::ValueType::I32:
  382. case Wasm::ValueType::I64:
  383. case Wasm::ValueType::F32:
  384. case Wasm::ValueType::F64:
  385. case Wasm::ValueType::V128:
  386. case Wasm::ValueType::FunctionReference:
  387. return Wasm::Value(type);
  388. case Wasm::ValueType::ExternReference:
  389. return MUST(to_webassembly_value(vm, JS::js_undefined(), type));
  390. }
  391. VERIFY_NOT_REACHED();
  392. }
  393. // https://webassembly.github.io/spec/js-api/#tojsvalue
  394. JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type)
  395. {
  396. auto& realm = *vm.current_realm();
  397. switch (type.kind()) {
  398. case Wasm::ValueType::I64:
  399. return realm.heap().allocate<JS::BigInt>(realm, ::Crypto::SignedBigInteger { wasm_value.to<i64>() });
  400. case Wasm::ValueType::I32:
  401. return JS::Value(wasm_value.to<i32>());
  402. case Wasm::ValueType::F64:
  403. return JS::Value(wasm_value.to<double>());
  404. case Wasm::ValueType::F32:
  405. return JS::Value(static_cast<double>(wasm_value.to<float>()));
  406. case Wasm::ValueType::FunctionReference: {
  407. auto ref_ = wasm_value.to<Wasm::Reference>();
  408. if (ref_.ref().has<Wasm::Reference::Null>())
  409. return JS::js_null();
  410. auto address = ref_.ref().get<Wasm::Reference::Func>().address;
  411. auto& cache = get_cache(realm);
  412. auto* function = cache.abstract_machine().store().get(address);
  413. auto name = function->visit(
  414. [&](Wasm::WasmFunction& wasm_function) {
  415. auto index = *wasm_function.module().functions().find_first_index(address);
  416. return ByteString::formatted("func{}", index);
  417. },
  418. [](Wasm::HostFunction& host_function) {
  419. return host_function.name();
  420. });
  421. return create_native_function(vm, address, name);
  422. }
  423. case Wasm::ValueType::ExternReference: {
  424. auto ref_ = wasm_value.to<Wasm::Reference>();
  425. if (ref_.ref().has<Wasm::Reference::Null>())
  426. return JS::js_null();
  427. auto address = ref_.ref().get<Wasm::Reference::Extern>().address;
  428. auto& cache = get_cache(realm);
  429. auto value = cache.get_extern_value(address);
  430. return value.release_value();
  431. }
  432. case Wasm::ValueType::V128:
  433. VERIFY_NOT_REACHED();
  434. }
  435. VERIFY_NOT_REACHED();
  436. }
  437. }
  438. // https://webassembly.github.io/spec/js-api/#asynchronously-compile-a-webassembly-module
  439. JS::NonnullGCPtr<WebIDL::Promise> asynchronously_compile_webassembly_module(JS::VM& vm, ByteBuffer bytes, HTML::Task::Source task_source)
  440. {
  441. auto& realm = *vm.current_realm();
  442. // 1. Let promise be a new Promise.
  443. auto promise = WebIDL::create_promise(realm);
  444. // 2. Run the following steps in parallel:
  445. Platform::EventLoopPlugin::the().deferred_invoke(JS::create_heap_function(vm.heap(), [&vm, &realm, bytes = move(bytes), promise, task_source]() mutable {
  446. HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
  447. // 1. Compile the WebAssembly module bytes and store the result as module.
  448. auto module_or_error = Detail::compile_a_webassembly_module(vm, move(bytes));
  449. // 2. Queue a task to perform the following steps. If taskSource was provided, queue the task on that task source.
  450. HTML::queue_a_task(task_source, nullptr, nullptr, JS::create_heap_function(vm.heap(), [&vm, &realm, promise, module_or_error = move(module_or_error)]() mutable {
  451. HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
  452. auto& realm = HTML::relevant_realm(*promise->promise());
  453. // 1. If module is error, reject promise with a CompileError exception.
  454. if (module_or_error.is_error()) {
  455. WebIDL::reject_promise(realm, promise, module_or_error.error_value());
  456. }
  457. // 2. Otherwise,
  458. else {
  459. // 1. Construct a WebAssembly module object from module and bytes, and let moduleObject be the result.
  460. // FIXME: Save bytes to the Module instance instead of moving into compile_a_webassembly_module
  461. auto module_object = vm.heap().allocate<Module>(realm, realm, module_or_error.release_value());
  462. // 2. Resolve promise with moduleObject.
  463. WebIDL::resolve_promise(*vm.current_realm(), promise, module_object);
  464. }
  465. }));
  466. }));
  467. // 3. Return promise.
  468. return promise;
  469. }
  470. // https://webassembly.github.io/spec/js-api/#asynchronously-instantiate-a-webassembly-module
  471. JS::NonnullGCPtr<WebIDL::Promise> asynchronously_instantiate_webassembly_module(JS::VM& vm, JS::NonnullGCPtr<Module> module_object, JS::GCPtr<JS::Object> import_object)
  472. {
  473. auto& realm = *vm.current_realm();
  474. // 1. Let promise be a new promise.
  475. auto promise = WebIDL::create_promise(realm);
  476. // 2. Let module be moduleObject.[[Module]].
  477. auto module = module_object->compiled_module();
  478. // 3. Read the imports of module with imports importObject, and let imports be the result.
  479. // If this operation throws an exception, catch it, reject promise with the exception, and return promise.
  480. // Note: We do this at the same time as instantiation in instantiate_module.
  481. // 4. Run the following steps in parallel:
  482. // 1. Queue a task to perform the following steps: Note: Implementation-specific work may be performed here.
  483. HTML::queue_a_task(HTML::Task::Source::Unspecified, nullptr, nullptr, JS::create_heap_function(vm.heap(), [&vm, &realm, promise, module, import_object]() {
  484. HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
  485. auto& realm = HTML::relevant_realm(*promise->promise());
  486. // 1. Instantiate the core of a WebAssembly module module with imports, and let instance be the result.
  487. // If this throws an exception, catch it, reject promise with the exception, and terminate these substeps.
  488. auto result = Detail::instantiate_module(vm, module->module, import_object);
  489. if (result.is_error()) {
  490. WebIDL::reject_promise(realm, promise, result.error_value());
  491. return;
  492. }
  493. auto instance = result.release_value();
  494. // 2. Let instanceObject be a new Instance.
  495. // 3. Initialize instanceObject from module and instance. If this throws an exception, catch it, reject promise with the exception, and terminate these substeps.
  496. // FIXME: Investigate whether we are doing all the proper steps for "initialize an instance object"
  497. auto instance_object = vm.heap().allocate<Instance>(realm, realm, move(instance));
  498. // 4. Resolve promise with instanceObject.
  499. WebIDL::resolve_promise(realm, promise, instance_object);
  500. }));
  501. // 5. Return promise.
  502. return promise;
  503. }
  504. // https://webassembly.github.io/spec/js-api/#instantiate-a-promise-of-a-module
  505. JS::NonnullGCPtr<WebIDL::Promise> instantiate_promise_of_module(JS::VM& vm, JS::NonnullGCPtr<WebIDL::Promise> promise_of_module, JS::GCPtr<JS::Object> import_object)
  506. {
  507. auto& realm = *vm.current_realm();
  508. // 1. Let promise be a new Promise.
  509. auto promise = WebIDL::create_promise(realm);
  510. // FIXME: Spec should use react to promise here instead of separate upon fulfillment and upon rejection steps
  511. // 2. Upon fulfillment of promiseOfModule with value module:
  512. auto fulfillment_steps = JS::create_heap_function(vm.heap(), [&vm, promise, import_object](JS::Value module_value) -> WebIDL::ExceptionOr<JS::Value> {
  513. VERIFY(module_value.is_object() && is<Module>(module_value.as_object()));
  514. auto module = JS::NonnullGCPtr { static_cast<Module&>(module_value.as_object()) };
  515. // 1. Instantiate the WebAssembly module module importing importObject, and let innerPromise be the result.
  516. auto inner_promise = asynchronously_instantiate_webassembly_module(vm, module, import_object);
  517. // 2. Upon fulfillment of innerPromise with value instance.
  518. auto instantiate_fulfillment_steps = JS::create_heap_function(vm.heap(), [promise, module](JS::Value instance_value) -> WebIDL::ExceptionOr<JS::Value> {
  519. auto& realm = HTML::relevant_realm(*promise->promise());
  520. VERIFY(instance_value.is_object() && is<Instance>(instance_value.as_object()));
  521. auto instance = JS::NonnullGCPtr { static_cast<Instance&>(instance_value.as_object()) };
  522. // 1. Let result be the WebAssemblyInstantiatedSource value «[ "module" → module, "instance" → instance ]».
  523. auto result = JS::Object::create(realm, nullptr);
  524. result->define_direct_property("module", module, JS::default_attributes);
  525. result->define_direct_property("instance", instance, JS::default_attributes);
  526. // 2. Resolve promise with result.
  527. WebIDL::resolve_promise(realm, promise, result);
  528. return JS::js_undefined();
  529. });
  530. // 3. Upon rejection of innerPromise with reason reason.
  531. auto instantiate_rejection_steps = JS::create_heap_function(vm.heap(), [promise](JS::Value reason) -> WebIDL::ExceptionOr<JS::Value> {
  532. auto& realm = HTML::relevant_realm(*promise->promise());
  533. // 1. Reject promise with reason.
  534. WebIDL::reject_promise(realm, promise, reason);
  535. return JS::js_undefined();
  536. });
  537. WebIDL::react_to_promise(inner_promise, instantiate_fulfillment_steps, instantiate_rejection_steps);
  538. return JS::js_undefined();
  539. });
  540. // 3. Upon rejection of promiseOfModule with reason reason:
  541. auto rejection_steps = JS::create_heap_function(vm.heap(), [promise](JS::Value reason) -> WebIDL::ExceptionOr<JS::Value> {
  542. auto& realm = HTML::relevant_realm(*promise->promise());
  543. // 1. Reject promise with reason.
  544. WebIDL::reject_promise(realm, promise, reason);
  545. return JS::js_undefined();
  546. });
  547. WebIDL::react_to_promise(promise_of_module, fulfillment_steps, rejection_steps);
  548. // 4. Return promise.
  549. return promise;
  550. }
  551. // https://webassembly.github.io/spec/web-api/index.html#compile-a-potential-webassembly-response
  552. JS::NonnullGCPtr<WebIDL::Promise> compile_potential_webassembly_response(JS::VM& vm, JS::NonnullGCPtr<WebIDL::Promise> source)
  553. {
  554. auto& realm = *vm.current_realm();
  555. // Note: This algorithm accepts a Response object, or a promise for one, and compiles and instantiates the resulting bytes of the response.
  556. // This compilation can be performed in the background and in a streaming manner.
  557. // If the Response is not CORS-same-origin, does not represent an ok status, or does not match the `application/wasm` MIME type,
  558. // the returned promise will be rejected with a TypeError; if compilation or instantiation fails,
  559. // the returned promise will be rejected with a CompileError or other relevant error type, depending on the cause of failure.
  560. // 1. Let returnValue be a new promise
  561. auto return_value = WebIDL::create_promise(realm);
  562. // 2. Upon fulfillment of source with value unwrappedSource:
  563. auto fulfillment_steps = JS::create_heap_function(vm.heap(), [&vm, return_value](JS::Value unwrapped_source) -> WebIDL::ExceptionOr<JS::Value> {
  564. auto& realm = HTML::relevant_realm(*return_value->promise());
  565. // 1. Let response be unwrappedSource’s response.
  566. if (!unwrapped_source.is_object() || !is<Fetch::Response>(unwrapped_source.as_object())) {
  567. WebIDL::reject_promise(realm, return_value, *vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Response").value());
  568. return JS::js_undefined();
  569. }
  570. auto& response_object = static_cast<Fetch::Response&>(unwrapped_source.as_object());
  571. auto response = response_object.response();
  572. // 2. Let mimeType be the result of getting `Content-Type` from response’s header list.
  573. // 3. If mimeType is null, reject returnValue with a TypeError and abort these substeps.
  574. // 4. Remove all HTTP tab or space byte from the start and end of mimeType.
  575. // 5. If mimeType is not a byte-case-insensitive match for `application/wasm`, reject returnValue with a TypeError and abort these substeps.
  576. // Note: extra parameters are not allowed, including the empty `application/wasm;`.
  577. // FIXME: Validate these extra constraints that are not checked by extract_mime_type()
  578. if (auto mime = response->header_list()->extract_mime_type(); !mime.has_value() || mime.value().essence() != "application/wasm"sv) {
  579. WebIDL::reject_promise(realm, return_value, *vm.throw_completion<JS::TypeError>("Response does not match the application/wasm MIME type"sv).value());
  580. return JS::js_undefined();
  581. }
  582. // 6. If response is not CORS-same-origin, reject returnValue with a TypeError and abort these substeps.
  583. // https://html.spec.whatwg.org/#cors-same-origin
  584. auto type = response_object.type();
  585. if (type != Bindings::ResponseType::Basic && type != Bindings::ResponseType::Cors && type != Bindings::ResponseType::Default) {
  586. WebIDL::reject_promise(realm, return_value, *vm.throw_completion<JS::TypeError>("Response is not CORS-same-origin"sv).value());
  587. return JS::js_undefined();
  588. }
  589. // 7. If response’s status is not an ok status, reject returnValue with a TypeError and abort these substeps.
  590. if (!response_object.ok()) {
  591. WebIDL::reject_promise(realm, return_value, *vm.throw_completion<JS::TypeError>("Response does not represent an ok status"sv).value());
  592. return JS::js_undefined();
  593. }
  594. // 8. Consume response’s body as an ArrayBuffer, and let bodyPromise be the result.
  595. auto body_promise_or_error = response_object.array_buffer();
  596. if (body_promise_or_error.is_error()) {
  597. auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), body_promise_or_error.release_error());
  598. WebIDL::reject_promise(realm, return_value, *throw_completion.value());
  599. return JS::js_undefined();
  600. }
  601. auto body_promise = body_promise_or_error.release_value();
  602. // 9. Upon fulfillment of bodyPromise with value bodyArrayBuffer:
  603. auto body_fulfillment_steps = JS::create_heap_function(vm.heap(), [&vm, return_value](JS::Value body_array_buffer) -> WebIDL::ExceptionOr<JS::Value> {
  604. // 1. Let stableBytes be a copy of the bytes held by the buffer bodyArrayBuffer.
  605. VERIFY(body_array_buffer.is_object());
  606. auto stable_bytes = WebIDL::get_buffer_source_copy(body_array_buffer.as_object());
  607. if (stable_bytes.is_error()) {
  608. VERIFY(stable_bytes.error().code() == ENOMEM);
  609. WebIDL::reject_promise(HTML::relevant_realm(*return_value->promise()), return_value, *vm.throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)).value());
  610. return JS::js_undefined();
  611. }
  612. // 2. Asynchronously compile the WebAssembly module stableBytes using the networking task source and resolve returnValue with the result.
  613. auto result = asynchronously_compile_webassembly_module(vm, stable_bytes.release_value(), HTML::Task::Source::Networking);
  614. // Need to manually convert WebIDL promise to an ECMAScript value here to resolve
  615. WebIDL::resolve_promise(HTML::relevant_realm(*return_value->promise()), return_value, result->promise());
  616. return JS::js_undefined();
  617. });
  618. // 10. Upon rejection of bodyPromise with reason reason:
  619. auto body_rejection_steps = JS::create_heap_function(vm.heap(), [return_value](JS::Value reason) -> WebIDL::ExceptionOr<JS::Value> {
  620. // 1. Reject returnValue with reason.
  621. WebIDL::reject_promise(HTML::relevant_realm(*return_value->promise()), return_value, reason);
  622. return JS::js_undefined();
  623. });
  624. WebIDL::react_to_promise(body_promise, body_fulfillment_steps, body_rejection_steps);
  625. return JS::js_undefined();
  626. });
  627. // 3. Upon rejection of source with reason reason:
  628. auto rejection_steps = JS::create_heap_function(vm.heap(), [return_value](JS::Value reason) -> WebIDL::ExceptionOr<JS::Value> {
  629. // 1. Reject returnValue with reason.
  630. WebIDL::reject_promise(HTML::relevant_realm(*return_value->promise()), return_value, reason);
  631. return JS::js_undefined();
  632. });
  633. WebIDL::react_to_promise(source, fulfillment_steps, rejection_steps);
  634. // 4. Return returnValue.
  635. return return_value;
  636. }
  637. }