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 <liama@google.com>
Reviewed-by: Samir Jindel <sjindel@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Liam Appelbe
2019-09-13 18:25:16 +00:00
committed by commit-bot@chromium.org
parent e8413cd08d
commit ed9e89ea38
16 changed files with 1132 additions and 18 deletions
+4
View File
@@ -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" ]
+611 -10
View File
@@ -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 <memory>
#include <sstream>
#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<char[]>(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 <typename T>
static void Finalize(void* isolate_callback_data,
Dart_WeakPersistentHandle handle,
void* peer) {
delete reinterpret_cast<T*>(peer);
}
static std::unique_ptr<char[]> ToUTF8(const String& str) {
const intptr_t str_size = Utf8::Length(str);
auto str_raw = std::unique_ptr<char[]>(new char[str_size + 1]);
str.ToUTF8(reinterpret_cast<uint8_t*>(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<char[]> 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<char[]> name, WasmMemory* memory) {
AddImport(std::move(name), wasmer_import_export_kind::WASM_MEMORY)->memory =
memory->memory();
}
void AddGlobal(std::unique_ptr<char[]> 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<char[]> _module_name;
MallocGrowableArray<const char*> _import_names;
MallocGrowableArray<wasmer_global_t*> _globals;
MallocGrowableArray<wasmer_import_t> _imports;
wasmer_import_export_value* AddImport(std::unique_ptr<char[]> name,
wasmer_import_export_kind tag) {
wasmer_import_t import;
import.module_name.bytes =
reinterpret_cast<const uint8_t*>(_module_name.get());
import.module_name.bytes_len = (uint32_t)strlen(_module_name.get());
import.import_name.bytes = reinterpret_cast<const uint8_t*>(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<classid_t> 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<classid_t>& args() const { return _args; }
bool SignatureMatches(const MallocGrowableArray<classid_t>& 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<classid_t> _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<classid_t>& 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<CStringKeyValueTrait<WasmFunction*>> _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<wasmer_value_tag[]>(new wasmer_value_tag[num_args]);
ThrowIfFailed(wasmer_export_func_params(fn, wasm_args.get(), num_args));
MallocGrowableArray<classid_t> 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<uint8_t[]> data_copy;
intptr_t len;
{
NoSafepointScope scope(thread);
len = data.LengthInBytes();
data_copy = std::unique_ptr<uint8_t[]>(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<intptr_t>(module));
FinalizablePersistentHandle::New(thread->isolate(), mod_wrap, module,
Finalize<WasmModule>, 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<intptr_t>(imports));
FinalizablePersistentHandle::New(thread->isolate(), imp_wrap, imports,
Finalize<WasmImports>, 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<WasmImports*>(imp_wrap.GetNativeField(0));
WasmMemory* memory =
reinterpret_cast<WasmMemory*>(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<WasmImports*>(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<intptr_t>(memory));
FinalizablePersistentHandle::New(thread->isolate(), mem_wrap, memory,
Finalize<WasmMemory>, 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<WasmMemory*>(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<WasmModule*>(mod_wrap.GetNativeField(0));
WasmImports* imports =
reinterpret_cast<WasmImports*>(imp_wrap.GetNativeField(0));
WasmInstance* inst;
{
TransitionVMToNative transition(thread);
inst = new WasmInstance(module, imports);
}
inst_wrap.SetNativeField(0, reinterpret_cast<intptr_t>(inst));
FinalizablePersistentHandle::New(thread->isolate(), inst_wrap, inst,
Finalize<WasmInstance>,
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<WasmInstance*>(inst_wrap.GetNativeField(0));
Function& sig = Function::Handle(fn_type.signature());
Array& args = Array::Handle(sig.parameter_types());
MallocGrowableArray<classid_t> 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<char[]> name_raw = ToUTF8(name);
WasmFunction* fn = inst->GetFunction(name_raw.get(), dart_args, dart_ret);
fn_wrap.SetNativeField(0, reinterpret_cast<intptr_t>(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<char> name = std::unique_ptr<char>(new char[len + 1]);
fn_name.ToUTF8(reinterpret_cast<uint8_t*>(name.get()), len);
name.get()[len] = 0;
ASSERT(fn_wrap.NumNativeFields() == 1);
WasmFunction* fn = reinterpret_cast<WasmFunction*>(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<wasmer_value_t[]>(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],
&params[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
+123 -2
View File
@@ -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<WasmMemory> _memories;
_NativeWasmImports(String moduleName) : _memories = [] {
_init(moduleName);
}
void addMemory(String name, WasmMemory memory) {
_memories.add(memory);
_addMemory(name, memory);
}
void addGlobal<T>(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<T> lookupFunction<T extends Function>(String name) {
return _NativeWasmFunction<T>(this, name);
}
void _init(_NativeWasmModule module, _NativeWasmImports imports)
native 'Wasm_initInstance';
}
class _NativeWasmFunction<T extends Function> extends NativeFieldWrapperClass1
implements WasmFunction<T> {
_NativeWasmInstance _inst;
_NativeWasmFunction(_NativeWasmInstance inst, String name) : _inst = inst {
_init(inst, name, T);
}
num call(List<num> args) {
var arg_copy = List<num>.from(args, growable: false);
return _call(arg_copy);
}
void _init(_NativeWasmInstance inst, String name, Type fnType)
native 'Wasm_initFunction';
num _call(List<num> args) native 'Wasm_callFunction';
}
+26
View File
@@ -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<T>(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; }
+3
View File
@@ -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() {
+3
View File
@@ -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 +
+8
View File
@@ -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.
+31
View File
@@ -107,4 +107,35 @@ ISOLATE_UNIT_TEST_CASE(GrowableHandlePtr) {
EXPECT_EQ(1, test2->length());
}
TEST_CASE(GrowableArrayMoveCtor) {
GrowableArray<int> a;
a.Add(4);
a.Add(5);
int* a_data = a.data();
GrowableArray<int> 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<int> 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
+58 -3
View File
@@ -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<T>(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<T> lookupFunction<T extends Function>(String name);
}
// WasmFunction is a callable function in a WasmInstance.
abstract class WasmFunction<T extends Function> {
num call(List<num> args);
}
+58 -3
View File
@@ -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<T>(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<T> lookupFunction<T extends Function>(String name);
}
// WasmFunction is a callable function in a WasmInstance.
abstract class WasmFunction<T extends Function> {
num call(List<num> args);
}
+31
View File
@@ -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<Int32>("__memory_base", 1024, false));
var fn = inst.lookupFunction<Int64 Function(Int64)>("square");
int n = fn.call([1234]);
Expect.equals(1234 * 1234, n);
}
@@ -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));
}
+31
View File
@@ -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<Int32>("__memory_base", 1024, false));
var fn = inst.lookupFunction<Int64 Function(Int64)>("square");
Expect.throwsArgumentError(() => fn.call([]));
Expect.throwsArgumentError(() => fn.call([1, 2, 3]));
Expect.throwsArgumentError(() => fn.call([1.23]));
}
@@ -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<Int32>("__memory_base", 1024, false));
Expect.isNotNull(inst.lookupFunction<Int64 Function(Int64)>("square"));
Expect.throwsArgumentError(
() => inst.lookupFunction<Int64 Function(Int64)>("blah"));
Expect.throwsArgumentError(
() => inst.lookupFunction<Int64 Function()>("square"));
Expect.throwsArgumentError(
() => inst.lookupFunction<Int64 Function(Int64, Int64)>("square"));
Expect.throwsArgumentError(
() => inst.lookupFunction<Void Function(Int64)>("square"));
Expect.throwsArgumentError(
() => inst.lookupFunction<Void Function(dynamic)>("square"));
}
+52
View File
@@ -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<Int32>("__memory_base", 1024, false));
var addI64 = inst.lookupFunction<Int64 Function(Int64, Int64)>("addI64");
var addI32 = inst.lookupFunction<Int32 Function(Int32, Int32)>("addI32");
var addF64 = inst.lookupFunction<Double Function(Double, Double)>("addF64");
var addF32 = inst.lookupFunction<Float Function(Float, Float)>("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);
}
+36
View File
@@ -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<Int32>("__memory_base", 1024, false));
var setFn = inst.lookupFunction<Void Function(Int64, Int64)>("set");
var getFn = inst.lookupFunction<Int64 Function()>("get");
Expect.isNull(setFn.call([123, 456]));
int n = getFn.call([]);
Expect.equals(123 + 456, n);
}