WebAssemblyObject.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/Array.h>
  7. #include <LibJS/Runtime/ArrayBuffer.h>
  8. #include <LibJS/Runtime/BigInt.h>
  9. #include <LibJS/Runtime/TypedArray.h>
  10. #include <LibWasm/AbstractMachine/Interpreter.h>
  11. #include <LibWeb/WebAssembly/WebAssemblyObject.h>
  12. namespace Web::Bindings {
  13. static JS::NativeFunction* create_native_function(Wasm::FunctionAddress address, String name, JS::GlobalObject& global_object);
  14. static JS::Value to_js_value(Wasm::Value& wasm_value, JS::GlobalObject& global_object);
  15. static Optional<Wasm::Value> to_webassembly_value(JS::Value value, const Wasm::ValueType& type, JS::GlobalObject& global_object);
  16. WebAssemblyObject::WebAssemblyObject(JS::GlobalObject& global_object)
  17. : Object(*global_object.object_prototype())
  18. {
  19. }
  20. void WebAssemblyObject::initialize(JS::GlobalObject& global_object)
  21. {
  22. Object::initialize(global_object);
  23. define_native_function("validate", validate, 1);
  24. define_native_function("compile", compile, 1);
  25. define_native_function("instantiate", instantiate, 1);
  26. }
  27. NonnullOwnPtrVector<WebAssemblyObject::CompiledWebAssemblyModule> WebAssemblyObject::s_compiled_modules;
  28. NonnullOwnPtrVector<Wasm::ModuleInstance> WebAssemblyObject::s_instantiated_modules;
  29. Wasm::AbstractMachine WebAssemblyObject::s_abstract_machine;
  30. JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::validate)
  31. {
  32. // FIXME: Implement this once module validation is implemented in LibWasm.
  33. dbgln("Hit WebAssemblyObject::validate() stub!");
  34. return JS::Value { true };
  35. }
  36. static Result<size_t, JS::Value> parse_module(JS::GlobalObject& global_object, JS::Object* buffer)
  37. {
  38. ByteBuffer* bytes;
  39. if (is<JS::ArrayBuffer>(buffer)) {
  40. auto array_buffer = static_cast<JS::ArrayBuffer*>(buffer);
  41. bytes = &array_buffer->buffer();
  42. } else if (is<JS::TypedArrayBase>(buffer)) {
  43. auto array = static_cast<JS::TypedArrayBase*>(buffer);
  44. bytes = &array->viewed_array_buffer()->buffer();
  45. } else {
  46. auto error = JS::TypeError::create(global_object, String::formatted("{} is not an ArrayBuffer", buffer->class_name()));
  47. return JS::Value { error };
  48. }
  49. InputMemoryStream stream { *bytes };
  50. auto module_result = Wasm::Module::parse(stream);
  51. if (module_result.is_error()) {
  52. // FIXME: Throw CompileError instead.
  53. auto error = JS::TypeError::create(global_object, Wasm::parse_error_to_string(module_result.error()));
  54. return JS::Value { error };
  55. }
  56. WebAssemblyObject::s_compiled_modules.append(make<WebAssemblyObject::CompiledWebAssemblyModule>(module_result.release_value()));
  57. return WebAssemblyObject::s_compiled_modules.size() - 1;
  58. }
  59. JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::compile)
  60. {
  61. // FIXME: This shouldn't block!
  62. auto buffer = vm.argument(0).to_object(global_object);
  63. JS::Value rejection_value;
  64. if (vm.exception()) {
  65. rejection_value = vm.exception()->value();
  66. vm.clear_exception();
  67. }
  68. auto promise = JS::Promise::create(global_object);
  69. if (!rejection_value.is_empty()) {
  70. promise->reject(rejection_value);
  71. return promise;
  72. }
  73. auto result = parse_module(global_object, buffer);
  74. if (result.is_error())
  75. promise->reject(result.error());
  76. else
  77. promise->fulfill(vm.heap().allocate<WebAssemblyModuleObject>(global_object, global_object, result.value()));
  78. return promise;
  79. }
  80. JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate)
  81. {
  82. // FIXME: This shouldn't block!
  83. auto buffer = vm.argument(0).to_object(global_object);
  84. auto promise = JS::Promise::create(global_object);
  85. auto take_exception_and_reject_if_needed = [&] {
  86. if (vm.exception()) {
  87. auto rejection_value = vm.exception()->value();
  88. vm.clear_exception();
  89. promise->reject(rejection_value);
  90. return true;
  91. }
  92. return false;
  93. };
  94. if (take_exception_and_reject_if_needed())
  95. return promise;
  96. const Wasm::Module* module { nullptr };
  97. if (is<JS::ArrayBuffer>(buffer) || is<JS::TypedArrayBase>(buffer)) {
  98. auto result = parse_module(global_object, buffer);
  99. if (result.is_error()) {
  100. promise->reject(result.error());
  101. return promise;
  102. }
  103. module = &WebAssemblyObject::s_compiled_modules.at(result.value()).module;
  104. } else if (is<WebAssemblyModuleObject>(buffer)) {
  105. module = &static_cast<WebAssemblyModuleObject*>(buffer)->module();
  106. } else {
  107. auto error = JS::TypeError::create(global_object, String::formatted("{} is not an ArrayBuffer or a Module", buffer->class_name()));
  108. promise->reject(error);
  109. return promise;
  110. }
  111. VERIFY(module);
  112. Wasm::Linker linker { *module };
  113. HashMap<Wasm::Linker::Name, Wasm::ExternValue> resolved_imports;
  114. auto import_argument = vm.argument(1);
  115. if (!import_argument.is_undefined()) {
  116. [[maybe_unused]] auto import_object = import_argument.to_object(global_object);
  117. if (take_exception_and_reject_if_needed())
  118. return promise;
  119. dbgln("Trying to resolve stuff because import object was specified");
  120. for (const Wasm::Linker::Name& import_name : linker.unresolved_imports()) {
  121. dbgln("Trying to resolve {}::{}", import_name.module, import_name.name);
  122. auto value = import_object->get(import_name.module);
  123. if (vm.exception())
  124. break;
  125. auto object = value.to_object(global_object);
  126. if (vm.exception())
  127. break;
  128. auto import_ = object->get(import_name.name);
  129. if (vm.exception())
  130. break;
  131. import_name.type.visit(
  132. [&](Wasm::TypeIndex index) {
  133. dbgln("Trying to resolve a function {}::{}, type index {}", import_name.module, import_name.name, index.value());
  134. auto& type = module->type(index);
  135. // FIXME: IsCallable()
  136. if (!import_.is_function())
  137. return;
  138. auto& function = import_.as_function();
  139. // FIXME: If this is a function created by create_native_function(),
  140. // just extract its address and resolve to that.
  141. Wasm::HostFunction host_function {
  142. [&](auto&, auto& arguments) -> Wasm::Result {
  143. JS::MarkedValueList argument_values { vm.heap() };
  144. for (auto& entry : arguments)
  145. argument_values.append(to_js_value(entry, global_object));
  146. auto result = vm.call(function, JS::js_undefined(), move(argument_values));
  147. if (vm.exception()) {
  148. vm.clear_exception();
  149. return Wasm::Trap();
  150. }
  151. if (type.results().is_empty())
  152. return Wasm::Result { Vector<Wasm::Value> {} };
  153. if (type.results().size() == 1) {
  154. auto value = to_webassembly_value(result, type.results().first(), global_object);
  155. if (!value.has_value())
  156. return Wasm::Trap {};
  157. return Wasm::Result { Vector<Wasm::Value> { value.release_value() } };
  158. }
  159. // FIXME: Multiple returns
  160. TODO();
  161. },
  162. type
  163. };
  164. auto address = s_abstract_machine.store().allocate(move(host_function));
  165. dbgln("Resolved to {}", address->value());
  166. // FIXME: LinkError instead.
  167. VERIFY(address.has_value());
  168. resolved_imports.set(import_name, Wasm::ExternValue { Wasm::FunctionAddress { *address } });
  169. },
  170. [&](const auto&) {
  171. // FIXME: Implement these.
  172. });
  173. }
  174. if (take_exception_and_reject_if_needed())
  175. return promise;
  176. }
  177. linker.link(resolved_imports);
  178. auto link_result = linker.finish();
  179. if (link_result.is_error()) {
  180. // FIXME: Throw a LinkError.
  181. StringBuilder builder;
  182. builder.append("LinkError: Missing ");
  183. builder.join(' ', link_result.error().missing_imports);
  184. auto error = JS::TypeError::create(global_object, builder.build());
  185. promise->reject(error);
  186. return promise;
  187. }
  188. auto instance_result = s_abstract_machine.instantiate(*module, link_result.release_value());
  189. if (instance_result.is_error()) {
  190. // FIXME: Throw a LinkError instead.
  191. auto error = JS::TypeError::create(global_object, instance_result.error().error);
  192. promise->reject(error);
  193. return promise;
  194. }
  195. s_instantiated_modules.append(instance_result.release_value());
  196. promise->fulfill(vm.heap().allocate<WebAssemblyInstanceObject>(global_object, global_object, s_instantiated_modules.size() - 1));
  197. return promise;
  198. }
  199. WebAssemblyModuleObject::WebAssemblyModuleObject(JS::GlobalObject& global_object, size_t index)
  200. : Object(*global_object.object_prototype())
  201. , m_index(index)
  202. {
  203. }
  204. WebAssemblyInstanceObject::WebAssemblyInstanceObject(JS::GlobalObject& global_object, size_t index)
  205. : Object(*global_object.object_prototype())
  206. , m_index(index)
  207. {
  208. }
  209. JS::Value to_js_value(Wasm::Value& wasm_value, JS::GlobalObject& global_object)
  210. {
  211. switch (wasm_value.type().kind()) {
  212. case Wasm::ValueType::I64:
  213. // FIXME: This is extremely silly...
  214. return global_object.heap().allocate<JS::BigInt>(global_object, Crypto::SignedBigInteger::from_base10(String::number(wasm_value.to<i64>().value())));
  215. case Wasm::ValueType::I32:
  216. return JS::Value(wasm_value.to<i32>().value());
  217. case Wasm::ValueType::F64:
  218. return JS::Value(static_cast<double>(wasm_value.to<float>().value()));
  219. case Wasm::ValueType::F32:
  220. return JS::Value(wasm_value.to<double>().value());
  221. case Wasm::ValueType::FunctionReference:
  222. // FIXME: What's the name of a function reference that isn't exported?
  223. return create_native_function(wasm_value.to<Wasm::FunctionAddress>().value(), "FIXME_IHaveNoIdeaWhatThisShouldBeCalled", global_object);
  224. case Wasm::ValueType::ExternReference:
  225. TODO();
  226. }
  227. VERIFY_NOT_REACHED();
  228. }
  229. Optional<Wasm::Value> to_webassembly_value(JS::Value value, const Wasm::ValueType& type, JS::GlobalObject& global_object)
  230. {
  231. static Crypto::SignedBigInteger two_64 = "1"_sbigint.shift_left(64);
  232. auto& vm = global_object.vm();
  233. switch (type.kind()) {
  234. case Wasm::ValueType::I64: {
  235. auto bigint = value.to_bigint(global_object);
  236. if (vm.exception())
  237. return {};
  238. auto value = bigint->big_integer().divided_by(two_64).remainder;
  239. VERIFY(value.trimmed_length() <= 2);
  240. BigEndian<i64> integer { 0 };
  241. value.export_data({ &integer, 2 });
  242. return Wasm::Value { static_cast<i64>(integer) };
  243. }
  244. case Wasm::ValueType::I32: {
  245. auto _i32 = value.to_i32(global_object);
  246. if (vm.exception())
  247. return {};
  248. return Wasm::Value { static_cast<i32>(_i32) };
  249. }
  250. case Wasm::ValueType::F64: {
  251. auto number = value.to_double(global_object);
  252. if (vm.exception())
  253. return {};
  254. return Wasm::Value { static_cast<double>(number) };
  255. }
  256. case Wasm::ValueType::F32: {
  257. auto number = value.to_double(global_object);
  258. if (vm.exception())
  259. return {};
  260. return Wasm::Value { static_cast<float>(number) };
  261. }
  262. case Wasm::ValueType::FunctionReference:
  263. case Wasm::ValueType::ExternReference:
  264. TODO();
  265. }
  266. VERIFY_NOT_REACHED();
  267. }
  268. JS::NativeFunction* create_native_function(Wasm::FunctionAddress address, String name, JS::GlobalObject& global_object)
  269. {
  270. // FIXME: Cache these.
  271. return JS::NativeFunction::create(
  272. global_object,
  273. name,
  274. [address](JS::VM& vm, JS::GlobalObject& global_object) -> JS::Value {
  275. Vector<Wasm::Value> values;
  276. Optional<Wasm::FunctionType> type;
  277. WebAssemblyObject::s_abstract_machine.store().get(address)->visit([&](const auto& value) { type = value.type(); });
  278. // Grab as many values as needed and convert them.
  279. size_t index = 0;
  280. for (auto& type : type.value().parameters()) {
  281. auto result = to_webassembly_value(vm.argument(index++), type, global_object);
  282. if (result.has_value())
  283. values.append(result.release_value());
  284. else
  285. return {};
  286. }
  287. auto result = WebAssemblyObject::s_abstract_machine.invoke(address, move(values));
  288. // FIXME: Use the convoluted mapping of errors defined in the spec.
  289. if (result.is_trap()) {
  290. vm.throw_exception<JS::TypeError>(global_object, "Wasm execution trapped (WIP)");
  291. return {};
  292. }
  293. if (result.values().is_empty())
  294. return JS::js_undefined();
  295. if (result.values().size() == 1)
  296. return to_js_value(result.values().first(), global_object);
  297. Vector<JS::Value> result_values;
  298. for (auto& entry : result.values())
  299. result_values.append(to_js_value(entry, global_object));
  300. return JS::Array::create_from(global_object, result_values);
  301. });
  302. }
  303. void WebAssemblyInstancePrototype::initialize(JS::GlobalObject& global_object)
  304. {
  305. Object::initialize(global_object);
  306. define_native_property("exports", exports_getter, nullptr);
  307. }
  308. void WebAssemblyInstanceObject::initialize(JS::GlobalObject& global_object)
  309. {
  310. Object::initialize(global_object);
  311. VERIFY(!m_exports_object);
  312. m_exports_object = JS::Object::create_empty(global_object);
  313. m_exports_object->set_prototype(nullptr);
  314. auto& instance = this->instance();
  315. for (auto& export_ : instance.exports()) {
  316. export_.value().visit(
  317. [&](const Wasm::FunctionAddress& address) {
  318. auto function = create_native_function(address, export_.name(), global_object);
  319. m_exports_object->define_property(export_.name(), function);
  320. },
  321. [&](const auto&) {
  322. // FIXME: Implement other exports!
  323. });
  324. }
  325. m_exports_object->set_integrity_level(IntegrityLevel::Frozen);
  326. }
  327. JS_DEFINE_NATIVE_GETTER(WebAssemblyInstancePrototype::exports_getter)
  328. {
  329. auto this_value = vm.this_value(global_object);
  330. auto this_object = this_value.to_object(global_object);
  331. if (vm.exception())
  332. return {};
  333. if (!is<WebAssemblyInstanceObject>(this_object)) {
  334. vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAn, "WebAssemblyInstance");
  335. return {};
  336. }
  337. auto object = static_cast<WebAssemblyInstanceObject*>(this_object);
  338. return object->m_exports_object;
  339. }
  340. void WebAssemblyInstanceObject::visit_edges(Cell::Visitor& visitor)
  341. {
  342. Object::visit_edges(visitor);
  343. visitor.visit(m_exports_object);
  344. }
  345. }