From ed9e89ea388e4bc0142bf6e967d4ca11999bdfdc Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 13 Sep 2019 18:25:16 +0000 Subject: [PATCH] Implement a basic WASM API using wasmer. Currently this only supports functions that take and return numeric types. Byte arrays, and callbacks will come later, in a separate wrapper package. Bug: https://github.com/dart-lang/sdk/issues/37882 Change-Id: I7bb82be83cbbb6062736b3e958f89d021f1af4bb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/114586 Commit-Queue: Liam Appelbe Reviewed-by: Samir Jindel Reviewed-by: Ryan Macnak --- runtime/BUILD.gn | 4 + runtime/lib/wasm.cc | 621 ++++++++++++++++++- runtime/lib/wasm_patch.dart | 125 +++- runtime/platform/growable_array.h | 26 + runtime/runtime_args.gni | 3 + runtime/vm/BUILD.gn | 3 + runtime/vm/bootstrap_natives.h | 8 + runtime/vm/growable_array_test.cc | 31 + sdk/lib/wasm/wasm.dart | 61 +- sdk_nnbd/lib/wasm/wasm.dart | 61 +- tests/lib_2/wasm/basic_test.dart | 31 + tests/lib_2/wasm/corrupted_error_test.dart | 20 + tests/lib_2/wasm/fn_call_error_test.dart | 31 + tests/lib_2/wasm/fn_mismatch_error_test.dart | 37 ++ tests/lib_2/wasm/numerics_test.dart | 52 ++ tests/lib_2/wasm/void_test.dart | 36 ++ 16 files changed, 1132 insertions(+), 18 deletions(-) create mode 100644 tests/lib_2/wasm/basic_test.dart create mode 100644 tests/lib_2/wasm/corrupted_error_test.dart create mode 100644 tests/lib_2/wasm/fn_call_error_test.dart create mode 100644 tests/lib_2/wasm/fn_mismatch_error_test.dart create mode 100644 tests/lib_2/wasm/numerics_test.dart create mode 100644 tests/lib_2/wasm/void_test.dart diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 529da63aaf4..a3cc3672830 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -240,6 +240,10 @@ library_for_all_configs("libdart") { "third_party/double-conversion/src:libdouble_conversion", ":generate_version_cc_file", ] + if (dart_enable_wasm) { + extra_deps += [ "//third_party/wasmer" ] + defines = [ "DART_ENABLE_WASM" ] + } if (is_fuchsia) { if (using_fuchsia_sdk) { extra_deps += [ "$fuchsia_sdk_root/pkg:fdio" ] diff --git a/runtime/lib/wasm.cc b/runtime/lib/wasm.cc index 7d0c06fe128..2d8e7a85b67 100644 --- a/runtime/lib/wasm.cc +++ b/runtime/lib/wasm.cc @@ -2,27 +2,628 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +#ifdef DART_ENABLE_WASM + +#include +#include + #include "platform/unicode.h" +#include "third_party/wasmer/wasmer.hh" #include "vm/bootstrap_natives.h" +#include "vm/dart_api_state.h" #include "vm/dart_entry.h" +#include "vm/exceptions.h" namespace dart { -int callWasm(const char* name, int n) { - return 100 * n; +static void ThrowIfFailed(wasmer_result_t status) { + if (status == wasmer_result_t::WASMER_OK) return; + int len = wasmer_last_error_length(); + auto error = std::unique_ptr(new char[len]); + int read_len = wasmer_last_error_message(error.get(), len); + ASSERT(read_len == len); + TransitionNativeToVM transition(Thread::Current()); + Exceptions::ThrowArgumentError( + String::Handle(String::NewFormatted("Wasmer error: %s", error.get()))); +} + +template +static void Finalize(void* isolate_callback_data, + Dart_WeakPersistentHandle handle, + void* peer) { + delete reinterpret_cast(peer); +} + +static std::unique_ptr ToUTF8(const String& str) { + const intptr_t str_size = Utf8::Length(str); + auto str_raw = std::unique_ptr(new char[str_size + 1]); + str.ToUTF8(reinterpret_cast(str_raw.get()), str_size); + str_raw[str_size] = '\0'; + return str_raw; +} + +static bool ToWasmValue(const Number& value, + classid_t type, + wasmer_value_t* out) { + switch (type) { + case kFfiInt32Cid: + if (!value.IsInteger()) return false; + out->tag = wasmer_value_tag::WASM_I32; + out->value.I32 = Integer::Cast(value).AsInt64Value(); + return true; + case kFfiInt64Cid: + if (!value.IsInteger()) return false; + out->tag = wasmer_value_tag::WASM_I64; + out->value.I64 = Integer::Cast(value).AsInt64Value(); + return true; + case kFfiFloatCid: + if (!value.IsDouble()) return false; + out->tag = wasmer_value_tag::WASM_F32; + out->value.F32 = Double::Cast(value).value(); + return true; + case kFfiDoubleCid: + if (!value.IsDouble()) return false; + out->tag = wasmer_value_tag::WASM_F64; + out->value.F64 = Double::Cast(value).value(); + return true; + default: + return false; + } +} + +static RawObject* ToDartObject(wasmer_value_t ret) { + switch (ret.tag) { + case wasmer_value_tag::WASM_I32: + return Integer::New(ret.value.I32); + case wasmer_value_tag::WASM_I64: + return Integer::New(ret.value.I64); + case wasmer_value_tag::WASM_F32: + return Double::New(ret.value.F32); + case wasmer_value_tag::WASM_F64: + return Double::New(ret.value.F64); + default: + FATAL("Unknown WASM type"); + return nullptr; + } +} + +class WasmModule { + public: + WasmModule(uint8_t* data, intptr_t len) { + ThrowIfFailed(wasmer_compile(&_module, data, len)); + } + + ~WasmModule() { wasmer_module_destroy(_module); } + wasmer_module_t* module() { return _module; } + + private: + wasmer_module_t* _module; + + DISALLOW_COPY_AND_ASSIGN(WasmModule); +}; + +class WasmMemory { + public: + WasmMemory(uint32_t init, int64_t max) { + wasmer_limits_t descriptor; + descriptor.min = init; + if (max < 0) { + descriptor.max.has_some = false; + } else { + descriptor.max.has_some = true; + descriptor.max.some = max; + } + ThrowIfFailed(wasmer_memory_new(&_memory, descriptor)); + } + + ~WasmMemory() { wasmer_memory_destroy(_memory); } + wasmer_memory_t* memory() { return _memory; } + + void Grow(intptr_t delta) { + ThrowIfFailed(wasmer_memory_grow(_memory, delta)); + } + + RawExternalTypedData* ToExternalTypedData() { + uint8_t* data = wasmer_memory_data(_memory); + uint32_t size = wasmer_memory_data_length(_memory); + return ExternalTypedData::New(kExternalTypedDataUint8ArrayCid, data, size); + } + + private: + wasmer_memory_t* _memory; + + DISALLOW_COPY_AND_ASSIGN(WasmMemory); +}; + +class WasmImports { + public: + WasmImports(std::unique_ptr module_name) + : _module_name(std::move(module_name)) {} + + ~WasmImports() { + for (wasmer_global_t* global : _globals) { + wasmer_global_destroy(global); + } + for (const char* name : _import_names) { + delete[] name; + } + } + + size_t NumImports() const { return _imports.length(); } + wasmer_import_t* RawImports() { return _imports.data(); } + + void AddMemory(std::unique_ptr name, WasmMemory* memory) { + AddImport(std::move(name), wasmer_import_export_kind::WASM_MEMORY)->memory = + memory->memory(); + } + + void AddGlobal(std::unique_ptr name, + wasmer_value_t value, + bool mutable_) { + wasmer_global_t* global = wasmer_global_new(value, mutable_); + _globals.Add(global); + AddImport(std::move(name), wasmer_import_export_kind::WASM_GLOBAL)->global = + global; + } + + private: + std::unique_ptr _module_name; + MallocGrowableArray _import_names; + MallocGrowableArray _globals; + MallocGrowableArray _imports; + + wasmer_import_export_value* AddImport(std::unique_ptr name, + wasmer_import_export_kind tag) { + wasmer_import_t import; + import.module_name.bytes = + reinterpret_cast(_module_name.get()); + import.module_name.bytes_len = (uint32_t)strlen(_module_name.get()); + import.import_name.bytes = reinterpret_cast(name.get()); + import.import_name.bytes_len = (uint32_t)strlen(name.get()); + import.tag = tag; + _import_names.Add(name.release()); + _imports.Add(import); + return &_imports.Last().value; + } + + DISALLOW_COPY_AND_ASSIGN(WasmImports); +}; + +class WasmFunction { + public: + WasmFunction(MallocGrowableArray args, + classid_t ret, + const wasmer_export_func_t* fn) + : _args(std::move(args)), _ret(ret), _fn(fn) {} + bool IsVoid() const { return _ret == kFfiVoidCid; } + const MallocGrowableArray& args() const { return _args; } + + bool SignatureMatches(const MallocGrowableArray& dart_args, + classid_t dart_ret) { + if (dart_args.length() != _args.length()) { + return false; + } + for (intptr_t i = 0; i < dart_args.length(); ++i) { + if (dart_args[i] != _args[i]) { + return false; + } + } + return dart_ret == _ret; + } + + wasmer_value_t Call(const wasmer_value_t* params) { + wasmer_value_t result; + ThrowIfFailed(wasmer_export_func_call(_fn, params, _args.length(), &result, + IsVoid() ? 0 : 1)); + return result; + } + + void Print(std::ostream& o, const char* name) const { + PrintFfiType(o, _ret); + o << ' ' << name << '('; + for (intptr_t i = 0; i < _args.length(); ++i) { + if (i > 0) o << ", "; + PrintFfiType(o, _args[i]); + } + o << ')'; + } + + private: + MallocGrowableArray _args; + const classid_t _ret; + const wasmer_export_func_t* _fn; + + static void PrintFfiType(std::ostream& o, classid_t type) { + switch (type) { + case kFfiInt32Cid: + o << "i32"; + break; + case kFfiInt64Cid: + o << "i64"; + break; + case kFfiFloatCid: + o << "f32"; + break; + case kFfiDoubleCid: + o << "f64"; + break; + case kFfiVoidCid: + o << "void"; + break; + } + } +}; + +class WasmInstance { + public: + explicit WasmInstance(WasmModule* module, WasmImports* imports) { + // Instantiate module. + ThrowIfFailed(wasmer_module_instantiate(module->module(), &_instance, + imports->RawImports(), + imports->NumImports())); + + // Load all functions. + wasmer_instance_exports(_instance, &_exports); + intptr_t num_exports = wasmer_exports_len(_exports); + for (intptr_t i = 0; i < num_exports; ++i) { + wasmer_export_t* exp = wasmer_exports_get(_exports, i); + if (wasmer_export_kind(exp) == wasmer_import_export_kind::WASM_FUNCTION) { + AddFunction(exp); + } + } + } + + ~WasmInstance() { + auto it = _functions.GetIterator(); + for (auto* kv = it.Next(); kv; kv = it.Next()) { + delete[] kv->key; + delete kv->value; + } + wasmer_exports_destroy(_exports); + wasmer_instance_destroy(_instance); + } + + WasmFunction* GetFunction(const char* name, + const MallocGrowableArray& dart_args, + classid_t dart_ret) { + WasmFunction* fn = _functions.LookupValue(name); + if (fn == nullptr) { + Exceptions::ThrowArgumentError(String::Handle(String::NewFormatted( + "Couldn't find a function called %s in the WASM module's exports", + name))); + return nullptr; + } + if (!fn->SignatureMatches(dart_args, dart_ret)) { + std::stringstream sig; + fn->Print(sig, name); + Exceptions::ThrowArgumentError(String::Handle(String::NewFormatted( + "Function signature doesn't match: %s", sig.str().c_str()))); + } + return fn; + } + + void PrintFunctions(std::ostream& o) const { + o << '{' << std::endl; + auto it = _functions.GetIterator(); + for (auto* kv = it.Next(); kv; kv = it.Next()) { + kv->value->Print(o, kv->key); + o << std::endl; + } + o << '}' << std::endl; + } + + private: + wasmer_instance_t* _instance; + wasmer_exports_t* _exports; + MallocDirectChainedHashMap> _functions; + + static classid_t ToFfiType(wasmer_value_tag wasm_type) { + switch (wasm_type) { + case wasmer_value_tag::WASM_I32: + return kFfiInt32Cid; + case wasmer_value_tag::WASM_I64: + return kFfiInt64Cid; + case wasmer_value_tag::WASM_F32: + return kFfiFloatCid; + case wasmer_value_tag::WASM_F64: + return kFfiDoubleCid; + } + FATAL("Unknown WASM type"); + return 0; + } + + void AddFunction(wasmer_export_t* exp) { + const wasmer_export_func_t* fn = wasmer_export_to_func(exp); + + uint32_t num_rets; + ThrowIfFailed(wasmer_export_func_returns_arity(fn, &num_rets)); + ASSERT(num_rets <= 1); + wasmer_value_tag wasm_ret; + ThrowIfFailed(wasmer_export_func_returns(fn, &wasm_ret, num_rets)); + classid_t ret = num_rets == 0 ? kFfiVoidCid : ToFfiType(wasm_ret); + + uint32_t num_args; + ThrowIfFailed(wasmer_export_func_params_arity(fn, &num_args)); + auto wasm_args = + std::unique_ptr(new wasmer_value_tag[num_args]); + ThrowIfFailed(wasmer_export_func_params(fn, wasm_args.get(), num_args)); + MallocGrowableArray args; + for (intptr_t i = 0; i < num_args; ++i) { + args.Add(ToFfiType(wasm_args[i])); + } + + wasmer_byte_array name_bytes = wasmer_export_name(exp); + char* name = new char[name_bytes.bytes_len + 1]; + for (size_t i = 0; i < name_bytes.bytes_len; ++i) { + name[i] = name_bytes.bytes[i]; + } + name[name_bytes.bytes_len] = '\0'; + + _functions.Insert({name, new WasmFunction(std::move(args), ret, fn)}); + } + + DISALLOW_COPY_AND_ASSIGN(WasmInstance); +}; + +DEFINE_NATIVE_ENTRY(Wasm_initModule, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, mod_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(TypedDataBase, data, arguments->NativeArgAt(1)); + + ASSERT(mod_wrap.NumNativeFields() == 1); + + std::unique_ptr data_copy; + intptr_t len; + { + NoSafepointScope scope(thread); + len = data.LengthInBytes(); + data_copy = std::unique_ptr(new uint8_t[len]); + // The memory does not overlap. + memcpy(data_copy.get(), data.DataAddr(0), len); // NOLINT + } + + WasmModule* module; + { + TransitionVMToNative transition(thread); + module = new WasmModule(data_copy.get(), len); + } + + mod_wrap.SetNativeField(0, reinterpret_cast(module)); + FinalizablePersistentHandle::New(thread->isolate(), mod_wrap, module, + Finalize, sizeof(WasmModule)); + + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Wasm_initImports, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, imp_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(String, module_name, arguments->NativeArgAt(1)); + + ASSERT(imp_wrap.NumNativeFields() == 1); + + WasmImports* imports = new WasmImports(ToUTF8(module_name)); + + imp_wrap.SetNativeField(0, reinterpret_cast(imports)); + FinalizablePersistentHandle::New(thread->isolate(), imp_wrap, imports, + Finalize, sizeof(WasmImports)); + + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Wasm_addMemoryImport, 0, 3) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, imp_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(String, name, arguments->NativeArgAt(1)); + GET_NON_NULL_NATIVE_ARGUMENT(Instance, mem_wrap, arguments->NativeArgAt(2)); + + ASSERT(imp_wrap.NumNativeFields() == 1); + ASSERT(mem_wrap.NumNativeFields() == 1); + + WasmImports* imports = + reinterpret_cast(imp_wrap.GetNativeField(0)); + WasmMemory* memory = + reinterpret_cast(mem_wrap.GetNativeField(0)); + + imports->AddMemory(ToUTF8(name), memory); + + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Wasm_addGlobalImport, 0, 5) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, imp_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(String, name, arguments->NativeArgAt(1)); + GET_NON_NULL_NATIVE_ARGUMENT(Number, value, arguments->NativeArgAt(2)); + GET_NON_NULL_NATIVE_ARGUMENT(Type, type, arguments->NativeArgAt(3)); + GET_NON_NULL_NATIVE_ARGUMENT(Bool, mutable_, arguments->NativeArgAt(4)); + + ASSERT(imp_wrap.NumNativeFields() == 1); + + WasmImports* imports = + reinterpret_cast(imp_wrap.GetNativeField(0)); + wasmer_value_t wasm_value; + if (!ToWasmValue(value, type.type_class_id(), &wasm_value)) { + Exceptions::ThrowArgumentError(String::Handle(String::NewFormatted( + "Can't convert dart value to WASM global variable"))); + } + + imports->AddGlobal(ToUTF8(name), wasm_value, mutable_.value()); + + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Wasm_initMemory, 0, 3) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, mem_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Integer, init, arguments->NativeArgAt(1)); + GET_NATIVE_ARGUMENT(Integer, max, arguments->NativeArgAt(2)); + + ASSERT(mem_wrap.NumNativeFields() == 1); + + WasmMemory* memory = new WasmMemory(init.AsInt64Value(), + max.IsNull() ? -1 : max.AsInt64Value()); + mem_wrap.SetNativeField(0, reinterpret_cast(memory)); + FinalizablePersistentHandle::New(thread->isolate(), mem_wrap, memory, + Finalize, sizeof(WasmMemory)); + return memory->ToExternalTypedData(); +} + +DEFINE_NATIVE_ENTRY(Wasm_growMemory, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, mem_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Integer, delta, arguments->NativeArgAt(1)); + + ASSERT(mem_wrap.NumNativeFields() == 1); + + WasmMemory* memory = + reinterpret_cast(mem_wrap.GetNativeField(0)); + memory->Grow(delta.AsInt64Value()); + return memory->ToExternalTypedData(); +} + +DEFINE_NATIVE_ENTRY(Wasm_initInstance, 0, 3) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, inst_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Instance, mod_wrap, arguments->NativeArgAt(1)); + GET_NON_NULL_NATIVE_ARGUMENT(Instance, imp_wrap, arguments->NativeArgAt(2)); + + ASSERT(inst_wrap.NumNativeFields() == 1); + ASSERT(mod_wrap.NumNativeFields() == 1); + ASSERT(imp_wrap.NumNativeFields() == 1); + + WasmModule* module = + reinterpret_cast(mod_wrap.GetNativeField(0)); + WasmImports* imports = + reinterpret_cast(imp_wrap.GetNativeField(0)); + + WasmInstance* inst; + { + TransitionVMToNative transition(thread); + inst = new WasmInstance(module, imports); + } + + inst_wrap.SetNativeField(0, reinterpret_cast(inst)); + FinalizablePersistentHandle::New(thread->isolate(), inst_wrap, inst, + Finalize, + sizeof(WasmInstance)); + + return Object::null(); +} + +DEFINE_NATIVE_ENTRY(Wasm_initFunction, 0, 4) { + GET_NON_NULL_NATIVE_ARGUMENT(Instance, fn_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Instance, inst_wrap, arguments->NativeArgAt(1)); + GET_NON_NULL_NATIVE_ARGUMENT(String, name, arguments->NativeArgAt(2)); + GET_NON_NULL_NATIVE_ARGUMENT(Type, fn_type, arguments->NativeArgAt(3)); + + ASSERT(fn_wrap.NumNativeFields() == 1); + ASSERT(inst_wrap.NumNativeFields() == 1); + + WasmInstance* inst = + reinterpret_cast(inst_wrap.GetNativeField(0)); + + Function& sig = Function::Handle(fn_type.signature()); + Array& args = Array::Handle(sig.parameter_types()); + MallocGrowableArray dart_args; + for (intptr_t i = sig.NumImplicitParameters(); i < args.Length(); ++i) { + dart_args.Add( + AbstractType::Cast(Object::Handle(args.At(i))).type_class_id()); + } + classid_t dart_ret = AbstractType::Handle(sig.result_type()).type_class_id(); + + std::unique_ptr name_raw = ToUTF8(name); + WasmFunction* fn = inst->GetFunction(name_raw.get(), dart_args, dart_ret); + + fn_wrap.SetNativeField(0, reinterpret_cast(fn)); + // Don't need a finalizer because WasmFunctions are owned their WasmInstance. + + return Object::null(); } -// This is a temporary API for prototyping. DEFINE_NATIVE_ENTRY(Wasm_callFunction, 0, 2) { - GET_NON_NULL_NATIVE_ARGUMENT(String, fn_name, arguments->NativeArgAt(0)); - GET_NON_NULL_NATIVE_ARGUMENT(Integer, arg, arguments->NativeArgAt(1)); + GET_NON_NULL_NATIVE_ARGUMENT(Instance, fn_wrap, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(Array, args, arguments->NativeArgAt(1)); - intptr_t len = Utf8::Length(fn_name); - std::unique_ptr name = std::unique_ptr(new char[len + 1]); - fn_name.ToUTF8(reinterpret_cast(name.get()), len); - name.get()[len] = 0; + ASSERT(fn_wrap.NumNativeFields() == 1); + WasmFunction* fn = reinterpret_cast(fn_wrap.GetNativeField(0)); - return Smi::New(callWasm(name.get(), arg.AsInt64Value())); + if (args.Length() != fn->args().length()) { + Exceptions::ThrowArgumentError(String::Handle(String::NewFormatted( + "Wrong number of args. Expected %" Pu " but found %" Pd ".", + fn->args().length(), args.Length()))); + } + intptr_t length = fn->args().length(); + if (length == 0) { + // Wasmer requires that our params ptr is valid, even if params_len is 0. + // TODO(liama): Remove after https://github.com/wasmerio/wasmer/issues/753 + length = 1; + } + auto params = std::unique_ptr(new wasmer_value_t[length]); + for (intptr_t i = 0; i < args.Length(); ++i) { + if (!ToWasmValue(Number::Cast(Object::Handle(args.At(i))), fn->args()[i], + ¶ms[i])) { + Exceptions::ThrowArgumentError(String::Handle( + String::NewFormatted("Arg %" Pd " is the wrong type.", i))); + } + } + + wasmer_value_t ret; + { + TransitionVMToNative transition(Thread::Current()); + ret = fn->Call(params.get()); + } + return fn->IsVoid() ? Object::null() : ToDartObject(ret); } } // namespace dart + +#else // DART_ENABLE_WASM + +#include "vm/bootstrap_natives.h" +#include "vm/dart_entry.h" +#include "vm/exceptions.h" + +namespace dart { + +DEFINE_NATIVE_ENTRY(Wasm_initModule, 0, 2) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_initImports, 0, 2) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_addMemoryImport, 0, 3) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_addGlobalImport, 0, 5) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_initMemory, 0, 3) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_growMemory, 0, 3) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_initInstance, 0, 3) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_initFunction, 0, 4) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +DEFINE_NATIVE_ENTRY(Wasm_callFunction, 0, 2) { + Exceptions::ThrowUnsupportedError("WASM is disabled"); + return nullptr; +} + +} // namespace dart + +#endif // DART_ENABLE_WASM diff --git a/runtime/lib/wasm_patch.dart b/runtime/lib/wasm_patch.dart index 2af1b2081ab..e5ab3f53a73 100644 --- a/runtime/lib/wasm_patch.dart +++ b/runtime/lib/wasm_patch.dart @@ -2,7 +2,128 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import "dart:_internal" show patch; +import 'dart:_internal' show patch; +import "dart:nativewrappers" show NativeFieldWrapperClass1; +import 'dart:typed_data'; @patch -int _callWasm(String name, int arg) native "Wasm_callFunction"; +class WasmModule { + @patch + factory WasmModule(Uint8List data) { + return _NativeWasmModule(data); + } +} + +@patch +class WasmMemory { + @patch + factory WasmMemory(int initialPages, [int maxPages]) { + return _NativeWasmMemory(initialPages, maxPages); + } +} + +@patch +class WasmImports { + @patch + factory WasmImports(String moduleName) { + return _NativeWasmImports(moduleName); + } +} + +class _NativeWasmModule extends NativeFieldWrapperClass1 implements WasmModule { + _NativeWasmModule(Uint8List data) { + _init(data); + } + + WasmInstance instantiate(covariant _NativeWasmImports imports) { + return _NativeWasmInstance(this, imports); + } + + void _init(Uint8List data) native 'Wasm_initModule'; +} + +class _NativeWasmImports extends NativeFieldWrapperClass1 + implements WasmImports { + List _memories; + + _NativeWasmImports(String moduleName) : _memories = [] { + _init(moduleName); + } + + void addMemory(String name, WasmMemory memory) { + _memories.add(memory); + _addMemory(name, memory); + } + + void addGlobal(String name, num value, bool mutable) { + _addGlobal(name, value, T, mutable); + } + + void _init(String moduleName) native 'Wasm_initImports'; + void _addMemory(String name, WasmMemory memory) native 'Wasm_addMemoryImport'; + void _addGlobal(String name, num value, Type type, bool mutable) + native 'Wasm_addGlobalImport'; +} + +class _NativeWasmMemory extends NativeFieldWrapperClass1 implements WasmMemory { + int _pages; + Uint8List _buffer; + + _NativeWasmMemory(int initialPages, int maxPages) : _pages = initialPages { + _buffer = _init(initialPages, maxPages); + } + + int get lengthInPages => _pages; + int get lengthInBytes => _buffer.lengthInBytes; + int operator [](int index) => _buffer[index]; + void operator []=(int index, int value) { + _buffer[index] = value; + } + + int grow(int deltaPages) { + int oldPages = _pages; + _buffer = _grow(deltaPages); + _pages += deltaPages; + return oldPages; + } + + Uint8List _init(int initialPages, int maxPages) native 'Wasm_initMemory'; + Uint8List _grow(int deltaPages) native 'Wasm_growMemory'; +} + +class _NativeWasmInstance extends NativeFieldWrapperClass1 + implements WasmInstance { + _NativeWasmModule _module; + _NativeWasmImports _imports; + + _NativeWasmInstance(_NativeWasmModule module, _NativeWasmImports imports) + : _module = module, + _imports = imports { + _init(module, imports); + } + + WasmFunction lookupFunction(String name) { + return _NativeWasmFunction(this, name); + } + + void _init(_NativeWasmModule module, _NativeWasmImports imports) + native 'Wasm_initInstance'; +} + +class _NativeWasmFunction extends NativeFieldWrapperClass1 + implements WasmFunction { + _NativeWasmInstance _inst; + + _NativeWasmFunction(_NativeWasmInstance inst, String name) : _inst = inst { + _init(inst, name, T); + } + + num call(List args) { + var arg_copy = List.from(args, growable: false); + return _call(arg_copy); + } + + void _init(_NativeWasmInstance inst, String name, Type fnType) + native 'Wasm_initFunction'; + num _call(List args) native 'Wasm_callFunction'; +} diff --git a/runtime/platform/growable_array.h b/runtime/platform/growable_array.h index 75922621e3b..615b616c26f 100644 --- a/runtime/platform/growable_array.h +++ b/runtime/platform/growable_array.h @@ -29,8 +29,34 @@ class BaseGrowableArray : public B { } } + BaseGrowableArray(BaseGrowableArray&& other) + : length_(other.length_), + capacity_(other.capacity_), + data_(other.data_), + allocator_(other.allocator_) { + other.length_ = 0; + other.capacity_ = 0; + other.data_ = NULL; + } + ~BaseGrowableArray() { allocator_->template Free(data_, capacity_); } + BaseGrowableArray& operator=(BaseGrowableArray&& other) { + intptr_t temp = other.length_; + other.length_ = length_; + length_ = temp; + temp = other.capacity_; + other.capacity_ = capacity_; + capacity_ = temp; + T* temp_data = other.data_; + other.data_ = data_; + data_ = temp_data; + Allocator* temp_allocator = other.allocator_; + other.allocator_ = allocator_; + allocator_ = temp_allocator; + return *this; + } + intptr_t length() const { return length_; } T* data() const { return data_; } bool is_empty() const { return length_ == 0; } diff --git a/runtime/runtime_args.gni b/runtime/runtime_args.gni index cf9e71d5de0..381d94acca5 100644 --- a/runtime/runtime_args.gni +++ b/runtime/runtime_args.gni @@ -89,6 +89,9 @@ declare_args() { # Whether libdart should export the symbols of the Dart API. dart_lib_export_symbols = true + + # Whether dart:wasm should be enabled. + dart_enable_wasm = false } declare_args() { diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn index a526aec442e..02fd376e817 100644 --- a/runtime/vm/BUILD.gn +++ b/runtime/vm/BUILD.gn @@ -102,6 +102,9 @@ library_for_all_configs("libdart_lib") { ] } } + if (dart_enable_wasm) { + defines = [ "DART_ENABLE_WASM" ] + } include_dirs = [ ".." ] allsources = async_runtime_cc_files + collection_runtime_cc_files + core_runtime_cc_files + developer_runtime_cc_files + diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 876d03a7fd1..abf2199ca13 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -390,6 +390,14 @@ namespace dart { V(Ffi_dl_executableLibrary, 0) \ V(TransferableTypedData_factory, 2) \ V(TransferableTypedData_materialize, 1) \ + V(Wasm_initModule, 2) \ + V(Wasm_initImports, 2) \ + V(Wasm_addMemoryImport, 3) \ + V(Wasm_addGlobalImport, 5) \ + V(Wasm_initMemory, 3) \ + V(Wasm_growMemory, 2) \ + V(Wasm_initInstance, 3) \ + V(Wasm_initFunction, 4) \ V(Wasm_callFunction, 2) // List of bootstrap native entry points used in the dart:mirror library. diff --git a/runtime/vm/growable_array_test.cc b/runtime/vm/growable_array_test.cc index 2b9b23b63d5..7c0ee5da268 100644 --- a/runtime/vm/growable_array_test.cc +++ b/runtime/vm/growable_array_test.cc @@ -107,4 +107,35 @@ ISOLATE_UNIT_TEST_CASE(GrowableHandlePtr) { EXPECT_EQ(1, test2->length()); } +TEST_CASE(GrowableArrayMoveCtor) { + GrowableArray a; + a.Add(4); + a.Add(5); + int* a_data = a.data(); + + GrowableArray b(std::move(a)); + + EXPECT_EQ(0, a.length()); + EXPECT_EQ((int*)nullptr, a.data()); + EXPECT_EQ(2, b.length()); + EXPECT_EQ(a_data, b.data()); +} + +TEST_CASE(GrowableArrayMoveAssign) { + GrowableArray a, b; + a.Add(1); + a.Add(2); + a.Add(3); + b.Add(7); + int* a_data = a.data(); + int* b_data = b.data(); + + a = std::move(b); + + EXPECT_EQ(1, a.length()); + EXPECT_EQ(b_data, a.data()); + EXPECT_EQ(3, b.length()); + EXPECT_EQ(a_data, b.data()); +} + } // namespace dart diff --git a/sdk/lib/wasm/wasm.dart b/sdk/lib/wasm/wasm.dart index edf0887a3f3..f68ccaa87ef 100644 --- a/sdk/lib/wasm/wasm.dart +++ b/sdk/lib/wasm/wasm.dart @@ -6,8 +6,63 @@ /// {@nodoc} library dart.wasm; -int callWasm(String name, int arg) { - return _callWasm(name, arg); +import 'dart:typed_data'; + +export 'dart:ffi' show Int32, Int64, Float, Double, Void; + +// WasmModule is a compiled module that can be instantiated. +abstract class WasmModule { + // Compile a module. + external factory WasmModule(Uint8List data); + + // Instantiate the module with the given imports. + WasmInstance instantiate(WasmImports imports); } -external int _callWasm(String name, int arg); +// WasmImports holds all the imports for a WasmInstance. +abstract class WasmImports { + // Create an imports object. + external factory WasmImports(String moduleName); + + // Add a global variable to the imports. + void addGlobal(String name, num value, bool mutable); + + // Add a memory to the imports. + void addMemory(String name, WasmMemory memory); +} + +// WasmMemory is a sandbox for a WasmInstance to run in. +abstract class WasmMemory { + // Create a new memory with the given number of initial pages, and optional + // maximum number of pages. + external factory WasmMemory(int initialPages, [int maxPages]); + + // The WASM spec defines the page size as 64KiB. + static const int kPageSizeInBytes = 64 * 1024; + + // Returns the length of the memory in pages. + int get lengthInPages; + + // Returns the length of the memory in bytes. + int get lengthInBytes; + + // Returns the byte at the given index. + int operator [](int index); + + // Sets the byte at the iven index to value. + void operator []=(int index, int value); + + // Grow the memory by deltaPages. Returns the number of pages before resizing. + int grow(int deltaPages); +} + +// WasmInstance is an instantiated WasmModule. +abstract class WasmInstance { + // Find an exported function with the given signature. + WasmFunction lookupFunction(String name); +} + +// WasmFunction is a callable function in a WasmInstance. +abstract class WasmFunction { + num call(List args); +} diff --git a/sdk_nnbd/lib/wasm/wasm.dart b/sdk_nnbd/lib/wasm/wasm.dart index edf0887a3f3..f68ccaa87ef 100644 --- a/sdk_nnbd/lib/wasm/wasm.dart +++ b/sdk_nnbd/lib/wasm/wasm.dart @@ -6,8 +6,63 @@ /// {@nodoc} library dart.wasm; -int callWasm(String name, int arg) { - return _callWasm(name, arg); +import 'dart:typed_data'; + +export 'dart:ffi' show Int32, Int64, Float, Double, Void; + +// WasmModule is a compiled module that can be instantiated. +abstract class WasmModule { + // Compile a module. + external factory WasmModule(Uint8List data); + + // Instantiate the module with the given imports. + WasmInstance instantiate(WasmImports imports); } -external int _callWasm(String name, int arg); +// WasmImports holds all the imports for a WasmInstance. +abstract class WasmImports { + // Create an imports object. + external factory WasmImports(String moduleName); + + // Add a global variable to the imports. + void addGlobal(String name, num value, bool mutable); + + // Add a memory to the imports. + void addMemory(String name, WasmMemory memory); +} + +// WasmMemory is a sandbox for a WasmInstance to run in. +abstract class WasmMemory { + // Create a new memory with the given number of initial pages, and optional + // maximum number of pages. + external factory WasmMemory(int initialPages, [int maxPages]); + + // The WASM spec defines the page size as 64KiB. + static const int kPageSizeInBytes = 64 * 1024; + + // Returns the length of the memory in pages. + int get lengthInPages; + + // Returns the length of the memory in bytes. + int get lengthInBytes; + + // Returns the byte at the given index. + int operator [](int index); + + // Sets the byte at the iven index to value. + void operator []=(int index, int value); + + // Grow the memory by deltaPages. Returns the number of pages before resizing. + int grow(int deltaPages); +} + +// WasmInstance is an instantiated WasmModule. +abstract class WasmInstance { + // Find an exported function with the given signature. + WasmFunction lookupFunction(String name); +} + +// WasmFunction is a callable function in a WasmInstance. +abstract class WasmFunction { + num call(List args); +} diff --git a/tests/lib_2/wasm/basic_test.dart b/tests/lib_2/wasm/basic_test.dart new file mode 100644 index 00000000000..83f257b107e --- /dev/null +++ b/tests/lib_2/wasm/basic_test.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test that we can load a wasm module, find a function, and call it. + +import "package:expect/expect.dart"; +import 'dart:ffi'; +import "dart:wasm"; +import "dart:typed_data"; + +void main() { + // int64_t square(int64_t n) { return n * n; } + var data = Uint8List.fromList([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, + 0x01, 0x7e, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x04, 0x05, 0x01, 0x70, + 0x01, 0x01, 0x01, 0x05, 0x03, 0x01, 0x00, 0x02, 0x06, 0x08, 0x01, 0x7f, + 0x01, 0x41, 0x80, 0x88, 0x04, 0x0b, 0x07, 0x13, 0x02, 0x06, 0x6d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x06, 0x73, 0x71, 0x75, 0x61, 0x72, + 0x65, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x7e, 0x0b, + ]); + + var inst = WasmModule(data).instantiate(WasmImports("env") + ..addMemory("memory", WasmMemory(256, 1024)) + ..addGlobal("__memory_base", 1024, false)); + var fn = inst.lookupFunction("square"); + int n = fn.call([1234]); + + Expect.equals(1234 * 1234, n); +} diff --git a/tests/lib_2/wasm/corrupted_error_test.dart b/tests/lib_2/wasm/corrupted_error_test.dart new file mode 100644 index 00000000000..fcc40859b2a --- /dev/null +++ b/tests/lib_2/wasm/corrupted_error_test.dart @@ -0,0 +1,20 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test error thrown when the wasm module is corrupted. + +import "package:expect/expect.dart"; +import "dart:wasm"; +import "dart:typed_data"; + +void main() { + var data = Uint8List.fromList([ + 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01, 0x7e, 0x01, 0x7e, + 0x07, 0x13, 0x02, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, + 0x06, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x7e, 0x0b, + ]); + + Expect.throwsArgumentError(() => WasmModule(data)); +} diff --git a/tests/lib_2/wasm/fn_call_error_test.dart b/tests/lib_2/wasm/fn_call_error_test.dart new file mode 100644 index 00000000000..e1d24412602 --- /dev/null +++ b/tests/lib_2/wasm/fn_call_error_test.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test error thrown when a function is called with the wrong args. + +import "package:expect/expect.dart"; +import "dart:wasm"; +import "dart:typed_data"; + +void main() { + // int64_t square(int64_t n) { return n * n; } + var data = Uint8List.fromList([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, + 0x01, 0x7e, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x04, 0x05, 0x01, 0x70, + 0x01, 0x01, 0x01, 0x05, 0x03, 0x01, 0x00, 0x02, 0x06, 0x08, 0x01, 0x7f, + 0x01, 0x41, 0x80, 0x88, 0x04, 0x0b, 0x07, 0x13, 0x02, 0x06, 0x6d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x06, 0x73, 0x71, 0x75, 0x61, 0x72, + 0x65, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x7e, 0x0b, + ]); + + var inst = WasmModule(data).instantiate(WasmImports("env") + ..addMemory("memory", WasmMemory(256, 1024)) + ..addGlobal("__memory_base", 1024, false)); + var fn = inst.lookupFunction("square"); + + Expect.throwsArgumentError(() => fn.call([])); + Expect.throwsArgumentError(() => fn.call([1, 2, 3])); + Expect.throwsArgumentError(() => fn.call([1.23])); +} diff --git a/tests/lib_2/wasm/fn_mismatch_error_test.dart b/tests/lib_2/wasm/fn_mismatch_error_test.dart new file mode 100644 index 00000000000..308a829daa1 --- /dev/null +++ b/tests/lib_2/wasm/fn_mismatch_error_test.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test error thrown when the loaded function can't be found. + +import "package:expect/expect.dart"; +import "dart:wasm"; +import "dart:typed_data"; + +void main() { + // int64_t square(int64_t n) { return n * n; } + var data = Uint8List.fromList([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, + 0x01, 0x7e, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x04, 0x05, 0x01, 0x70, + 0x01, 0x01, 0x01, 0x05, 0x03, 0x01, 0x00, 0x02, 0x06, 0x08, 0x01, 0x7f, + 0x01, 0x41, 0x80, 0x88, 0x04, 0x0b, 0x07, 0x13, 0x02, 0x06, 0x6d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x06, 0x73, 0x71, 0x75, 0x61, 0x72, + 0x65, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x7e, 0x0b, + ]); + + var inst = WasmModule(data).instantiate(WasmImports("env") + ..addMemory("memory", WasmMemory(256, 1024)) + ..addGlobal("__memory_base", 1024, false)); + Expect.isNotNull(inst.lookupFunction("square")); + Expect.throwsArgumentError( + () => inst.lookupFunction("blah")); + Expect.throwsArgumentError( + () => inst.lookupFunction("square")); + Expect.throwsArgumentError( + () => inst.lookupFunction("square")); + Expect.throwsArgumentError( + () => inst.lookupFunction("square")); + Expect.throwsArgumentError( + () => inst.lookupFunction("square")); +} diff --git a/tests/lib_2/wasm/numerics_test.dart b/tests/lib_2/wasm/numerics_test.dart new file mode 100644 index 00000000000..c4040144d80 --- /dev/null +++ b/tests/lib_2/wasm/numerics_test.dart @@ -0,0 +1,52 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test numeric types. + +import "package:expect/expect.dart"; +import 'dart:ffi'; +import "dart:wasm"; +import "dart:typed_data"; + +void main() { + // int64_t addI64(int64_t x, int64_t y) { return x + y; } + // int32_t addI32(int32_t x, int32_t y) { return x + y; } + // double addF64(double x, double y) { return x + y; } + // float addF32(float x, float y) { return x + y; } + var data = Uint8List.fromList([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x19, 0x04, 0x60, + 0x02, 0x7e, 0x7e, 0x01, 0x7e, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x60, + 0x02, 0x7c, 0x7c, 0x01, 0x7c, 0x60, 0x02, 0x7d, 0x7d, 0x01, 0x7d, 0x03, + 0x05, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x01, 0x70, 0x01, 0x01, + 0x01, 0x05, 0x03, 0x01, 0x00, 0x02, 0x06, 0x08, 0x01, 0x7f, 0x01, 0x41, + 0x80, 0x88, 0x04, 0x0b, 0x07, 0x2e, 0x05, 0x06, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x02, 0x00, 0x06, 0x61, 0x64, 0x64, 0x49, 0x36, 0x34, 0x00, + 0x00, 0x06, 0x61, 0x64, 0x64, 0x49, 0x33, 0x32, 0x00, 0x01, 0x06, 0x61, + 0x64, 0x64, 0x46, 0x36, 0x34, 0x00, 0x02, 0x06, 0x61, 0x64, 0x64, 0x46, + 0x33, 0x32, 0x00, 0x03, 0x0a, 0x21, 0x04, 0x07, 0x00, 0x20, 0x01, 0x20, + 0x00, 0x7c, 0x0b, 0x07, 0x00, 0x20, 0x01, 0x20, 0x00, 0x6a, 0x0b, 0x07, + 0x00, 0x20, 0x00, 0x20, 0x01, 0xa0, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x20, + 0x01, 0x92, 0x0b, + ]); + + var inst = WasmModule(data).instantiate(WasmImports("env") + ..addMemory("memory", WasmMemory(256, 1024)) + ..addGlobal("__memory_base", 1024, false)); + var addI64 = inst.lookupFunction("addI64"); + var addI32 = inst.lookupFunction("addI32"); + var addF64 = inst.lookupFunction("addF64"); + var addF32 = inst.lookupFunction("addF32"); + + int i64 = addI64.call([0x123456789ABCDEF, 0xFEDCBA987654321]); + Expect.equals(0x1111111111111110, i64); + + int i32 = addI32.call([0xABCDEF, 0xFEDCBA]); + Expect.equals(0x1aaaaa9, i32); + + double f64 = addF64.call([1234.5678, 8765.4321]); + Expect.approxEquals(9999.9999, f64, 1e-6); + + double f32 = addF32.call([1234.5678, 8765.4321]); + Expect.approxEquals(9999.9999, f32, 1e-3); +} diff --git a/tests/lib_2/wasm/void_test.dart b/tests/lib_2/wasm/void_test.dart new file mode 100644 index 00000000000..c2f0707a8ba --- /dev/null +++ b/tests/lib_2/wasm/void_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test functions with void return type, and functions that take no args. + +import "package:expect/expect.dart"; +import "dart:wasm"; +import "dart:typed_data"; + +void main() { + // int64_t x = 0; + // void set(int64_t a, int64_t b) { x = a + b; } + // int64_t get() { return x; } + var data = Uint8List.fromList([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x02, 0x60, + 0x02, 0x7e, 0x7e, 0x00, 0x60, 0x00, 0x01, 0x7e, 0x03, 0x03, 0x02, 0x00, + 0x01, 0x04, 0x05, 0x01, 0x70, 0x01, 0x01, 0x01, 0x05, 0x03, 0x01, 0x00, + 0x02, 0x06, 0x08, 0x01, 0x7f, 0x01, 0x41, 0x90, 0x88, 0x04, 0x0b, 0x07, + 0x16, 0x03, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, + 0x73, 0x65, 0x74, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, 0x01, 0x0a, + 0x1e, 0x02, 0x10, 0x00, 0x41, 0x00, 0x20, 0x01, 0x20, 0x00, 0x7c, 0x37, + 0x03, 0x80, 0x88, 0x80, 0x80, 0x00, 0x0b, 0x0b, 0x00, 0x41, 0x00, 0x29, + 0x03, 0x80, 0x88, 0x80, 0x80, 0x00, 0x0b, 0x0b, 0x0f, 0x01, 0x00, 0x41, + 0x80, 0x08, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + + var inst = WasmModule(data).instantiate(WasmImports("env") + ..addMemory("memory", WasmMemory(256, 1024)) + ..addGlobal("__memory_base", 1024, false)); + var setFn = inst.lookupFunction("set"); + var getFn = inst.lookupFunction("get"); + Expect.isNull(setFn.call([123, 456])); + int n = getFn.call([]); + Expect.equals(123 + 456, n); +}