[wasm] Add WasmMemory class.

Also, port the old wasm memory tests, and fix some NNBD issues.

Bug: https://github.com/dart-lang/sdk/issues/37882
Change-Id: I131ba5836bb0a3dd946cf9b0fa3f2e186b6b132e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162801
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Liam Appelbe 2020-09-16 19:24:28 +00:00 committed by commit-bot@chromium.org
parent 53e707f26d
commit cc8fd04924
9 changed files with 255 additions and 84 deletions

View file

@ -16,9 +16,9 @@ class WasmFunction {
Pointer<WasmerValue> _args;
Pointer<WasmerValue> _result;
WasmFunction(this._name, this._func, this._argTypes, this._returnType) {
_args = allocate<WasmerValue>(count: _argTypes.length);
_result = allocate<WasmerValue>();
WasmFunction(this._name, this._func, this._argTypes, this._returnType)
: _args = allocate<WasmerValue>(count: _argTypes.length),
_result = allocate<WasmerValue>() {
for (var i = 0; i < _argTypes.length; ++i) {
_args[i].tag = _argTypes[i];
}
@ -43,6 +43,7 @@ class WasmFunction {
_args[i].f64 = arg;
return true;
}
return false;
}
dynamic apply(List<dynamic> args) {

View file

@ -14,9 +14,7 @@ class WasmModule {
Pointer<WasmerModule> _module;
/// Compile a module.
WasmModule(Uint8List data) {
_module = WasmRuntime().compile(data);
}
WasmModule(Uint8List data) : _module = WasmRuntime().compile(data) {}
/// Instantiate the module with the given imports.
WasmInstance instantiate(WasmImports imports) {
@ -49,9 +47,9 @@ class WasmImports {
int _length;
/// Create an imports object.
WasmImports([this._capacity = 4]) : _length = 0 {
_imports = allocate<WasmerImport>(count: this._capacity);
}
WasmImports([this._capacity = 4])
: _imports = allocate<WasmerImport>(count: _capacity),
_length = 0 {}
/// Returns the number of imports.
int get length => _length;
@ -61,12 +59,13 @@ class WasmImports {
class WasmInstance {
Pointer<WasmerModule> _module;
Pointer<WasmerInstance> _instance;
Map<String, WasmFunction> _functions;
Pointer<WasmerMemory>? _exportedMemory;
Map<String, WasmFunction> _functions = {};
WasmInstance(this._module, WasmImports imports) {
WasmInstance(this._module, WasmImports imports)
: _instance = WasmRuntime()
.instantiate(_module, imports._imports, imports.length) {
var runtime = WasmRuntime();
_instance = runtime.instantiate(_module, imports._imports, imports.length);
_functions = {};
var exps = runtime.exports(_instance);
for (var e in exps) {
var kind = runtime.exportKind(e);
@ -75,6 +74,9 @@ class WasmInstance {
var f = runtime.exportToFunction(e);
_functions[name] = WasmFunction(
name, f, runtime.getArgTypes(f), runtime.getReturnType(f));
} else if (kind == WasmerImpExpKindMemory) {
// WASM currently allows only one memory per module.
_exportedMemory = runtime.exportToMemory(e);
}
}
}
@ -84,4 +86,55 @@ class WasmInstance {
dynamic lookupFunction(String name) {
return _functions[name];
}
/// Returns the memory exported from this instance.
WasmMemory get memory {
if (_exportedMemory == null) {
throw Exception("Wasm module did not export its memory.");
}
return WasmMemory._fromExport(_exportedMemory as Pointer<WasmerMemory>);
}
}
/// WasmMemory contains the memory of a WasmInstance.
class WasmMemory {
Pointer<WasmerMemory> _mem;
late Uint8List _view;
WasmMemory._fromExport(this._mem) {
_view = WasmRuntime().memoryView(_mem);
}
/// Create a new memory with the given number of initial pages, and optional
/// maximum number of pages.
WasmMemory(int pages, [int? maxPages])
: _mem = WasmRuntime().newMemory(pages, maxPages) {
_view = WasmRuntime().memoryView(_mem);
}
/// 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 {
return WasmRuntime().memoryLength(_mem);
}
/// Returns the length of the memory in bytes.
int get lengthInBytes => _view.lengthInBytes;
/// Returns the byte at the given index.
int operator [](int index) => _view[index];
/// Sets the byte at the given index to value.
void operator []=(int index, int value) {
_view[index] = value;
}
/// Grow the memory by deltaPages.
void grow(int deltaPages) {
var runtime = WasmRuntime();
runtime.growMemory(_mem, deltaPages);
_view = runtime.memoryView(_mem);
}
}

View file

@ -24,41 +24,47 @@ class WasmExportDescriptor {
}
class WasmRuntime {
static WasmRuntime _inst;
static WasmRuntime? _inst;
DynamicLibrary _lib;
WasmerCompileFn _compile;
WasmerInstantiateFn _instantiate;
WasmerInstanceExportsFn _instance_exports;
WasmerExportsLenFn _exports_len;
WasmerExportsGetFn _exports_get;
WasmerExportKindFn _export_kind;
WasmerExportToFuncFn _export_to_func;
WasmerExportFuncReturnsArityFn _export_func_returns_arity;
WasmerExportFuncReturnsFn _export_func_returns;
WasmerExportFuncParamsArityFn _export_func_params_arity;
WasmerExportFuncParamsFn _export_func_params;
WasmerExportFuncCallFn _export_func_call;
WasmerExportNamePtrFn _export_name_ptr;
WasmerExportDescriptorsFn _export_descriptors;
WasmerExportDescriptorsDestroyFn _export_descriptors_destroy;
WasmerExportDescriptorsLenFn _export_descriptors_len;
WasmerExportDescriptorsGetFn _export_descriptors_get;
WasmerExportDescriptorKindFn _export_descriptor_kind;
WasmerExportDescriptorNamePtrFn _export_descriptor_name_ptr;
WasmerImportDescriptorModuleNamePtrFn _import_descriptor_module_name_ptr;
WasmerImportDescriptorNamePtrFn _import_descriptor_name_ptr;
WasmerImportDescriptorsFn _import_descriptors;
WasmerImportDescriptorsDestroyFn _import_descriptors_destroy;
WasmerImportDescriptorsLenFn _import_descriptors_len;
WasmerImportDescriptorsGetFn _import_descriptors_get;
WasmerImportDescriptorKindFn _import_descriptor_kind;
late WasmerCompileFn _compile;
late WasmerInstantiateFn _instantiate;
late WasmerInstanceExportsFn _instance_exports;
late WasmerExportsLenFn _exports_len;
late WasmerExportsGetFn _exports_get;
late WasmerExportKindFn _export_kind;
late WasmerExportToFuncFn _export_to_func;
late WasmerExportFuncReturnsArityFn _export_func_returns_arity;
late WasmerExportFuncReturnsFn _export_func_returns;
late WasmerExportFuncParamsArityFn _export_func_params_arity;
late WasmerExportFuncParamsFn _export_func_params;
late WasmerExportFuncCallFn _export_func_call;
late WasmerExportNamePtrFn _export_name_ptr;
late WasmerExportDescriptorsFn _export_descriptors;
late WasmerExportDescriptorsDestroyFn _export_descriptors_destroy;
late WasmerExportDescriptorsLenFn _export_descriptors_len;
late WasmerExportDescriptorsGetFn _export_descriptors_get;
late WasmerExportDescriptorKindFn _export_descriptor_kind;
late WasmerExportDescriptorNamePtrFn _export_descriptor_name_ptr;
late WasmerImportDescriptorModuleNamePtrFn _import_descriptor_module_name_ptr;
late WasmerImportDescriptorNamePtrFn _import_descriptor_name_ptr;
late WasmerImportDescriptorsFn _import_descriptors;
late WasmerImportDescriptorsDestroyFn _import_descriptors_destroy;
late WasmerImportDescriptorsLenFn _import_descriptors_len;
late WasmerImportDescriptorsGetFn _import_descriptors_get;
late WasmerImportDescriptorKindFn _import_descriptor_kind;
late WasmerExportToMemoryFn _export_to_memory;
late WasmerMemoryNewPtrFn _memory_new_ptr;
late WasmerMemoryGrowFn _memory_grow;
late WasmerMemoryLengthFn _memory_length;
late WasmerMemoryDataFn _memory_data;
late WasmerMemoryDataLengthFn _memory_data_length;
factory WasmRuntime() {
if (_inst == null) {
_inst = WasmRuntime._init();
}
return _inst;
return _inst as WasmRuntime;
}
static String _getLibName() {
@ -96,9 +102,8 @@ class WasmRuntime {
return commonLibDir;
}
WasmRuntime._init() {
var libPath = path.join(_getLibDir(), _getLibName());
_lib = DynamicLibrary.open(libPath);
WasmRuntime._init()
: _lib = DynamicLibrary.open(path.join(_getLibDir(), _getLibName())) {
_compile = _lib.lookupFunction<NativeWasmerCompileFn, WasmerCompileFn>(
'wasmer_compile');
_instantiate =
@ -171,6 +176,22 @@ class WasmRuntime {
_import_descriptor_name_ptr = _lib.lookupFunction<
NativeWasmerImportDescriptorNamePtrFn,
WasmerImportDescriptorNamePtrFn>('wasmer_import_descriptor_name_ptr');
_export_to_memory = _lib.lookupFunction<NativeWasmerExportToMemoryFn,
WasmerExportToMemoryFn>('wasmer_export_to_memory');
_memory_new_ptr =
_lib.lookupFunction<NativeWasmerMemoryNewPtrFn, WasmerMemoryNewPtrFn>(
'wasmer_memory_new_ptr');
_memory_grow =
_lib.lookupFunction<NativeWasmerMemoryGrowFn, WasmerMemoryGrowFn>(
'wasmer_memory_grow');
_memory_length =
_lib.lookupFunction<NativeWasmerMemoryLengthFn, WasmerMemoryLengthFn>(
'wasmer_memory_length');
_memory_data =
_lib.lookupFunction<NativeWasmerMemoryDataFn, WasmerMemoryDataFn>(
'wasmer_memory_data');
_memory_data_length = _lib.lookupFunction<NativeWasmerMemoryDataLengthFn,
WasmerMemoryDataLengthFn>('wasmer_memory_data_length');
}
Pointer<WasmerModule> compile(Uint8List data) {
@ -331,4 +352,48 @@ class WasmRuntime {
throw Exception("Failed to call WASM function");
}
}
Pointer<WasmerMemory> exportToMemory(Pointer<WasmerExport> export) {
var memPtrPtr = allocate<Pointer<WasmerMemory>>();
var result = _export_to_memory(export, memPtrPtr);
if (result != WasmerResultOk) {
free(memPtrPtr);
throw Exception("Failed to get exported memory");
}
Pointer<WasmerMemory> memPtr = memPtrPtr.value;
free(memPtrPtr);
return memPtr;
}
Pointer<WasmerMemory> newMemory(int pages, int? maxPages) {
var memPtrPtr = allocate<Pointer<WasmerMemory>>();
var limPtr = allocate<WasmerLimits>();
limPtr.ref.min = pages;
limPtr.ref.has_max = maxPages != null ? 1 : 0;
limPtr.ref.max = maxPages ?? 0;
var result = _memory_new_ptr(memPtrPtr, limPtr);
free(limPtr);
if (result != WasmerResultOk) {
free(memPtrPtr);
throw Exception("Failed to create memory");
}
Pointer<WasmerMemory> memPtr = memPtrPtr.value;
free(memPtrPtr);
return memPtr;
}
void growMemory(Pointer<WasmerMemory> memory, int deltaPages) {
var result = _memory_grow(memory, deltaPages);
if (result != WasmerResultOk) {
throw Exception("Failed to grow memory");
}
}
int memoryLength(Pointer<WasmerMemory> memory) {
return _memory_length(memory);
}
Uint8List memoryView(Pointer<WasmerMemory> memory) {
return _memory_data(memory).asTypedList(_memory_data_length(memory));
}
}

View file

@ -67,34 +67,37 @@ class WasmerImportDescriptors extends Struct {}
// wasmer_import_descriptor_t
class WasmerImportDescriptor extends Struct {}
// wasmer_memory_t
class WasmerMemory extends Struct {}
// wasmer_import_t
class WasmerImport extends Struct {
Pointer<Uint8> module_name;
external Pointer<Uint8> module_name;
@Uint32()
int module_name_length;
external int module_name_length;
Pointer<Uint8> import_name;
external Pointer<Uint8> import_name;
@Uint32()
int import_name_length;
external int import_name_length;
// wasmer_import_export_kind
@Uint32()
int tag;
external int tag;
// wasmer_import_export_value, which is a union of wasmer_import_func_t*,
// wasmer_table_t*, wasmer_memory_t*, and wasmer_global_t*. The tag determines
// which type it is.
Pointer<Void> value;
external Pointer<Void> value;
}
// wasmer_byte_array
class WasmerByteArray extends Struct {
Pointer<Uint8> bytes;
external Pointer<Uint8> bytes;
@Uint32()
int length;
external int length;
Uint8List get list => bytes.asTypedList(length);
String get string => utf8.decode(list);
@ -104,13 +107,13 @@ class WasmerByteArray extends Struct {
class WasmerValue extends Struct {
// wasmer_value_tag
@Uint32()
int tag;
external int tag;
// wasmer_value, which is a union of int32_t, int64_t, float, and double. The
// tag determines which type it is. It's declared as an int64_t because that's
// large enough to hold all the types. We use ByteData to get the other types.
@Int64()
int value;
external int value;
int get _off32 => Endian.host == Endian.little ? 0 : 4;
int get i64 => value;
@ -122,8 +125,10 @@ class WasmerValue extends Struct {
set i64(int val) => value = val;
set _val(ByteData bytes) => value = bytes.getInt64(0, Endian.host);
set i32(int val) => _val = ByteData(8)..setInt32(_off32, val, Endian.host);
set f32(num val) => _val = ByteData(8)..setFloat32(_off32, val, Endian.host);
set f64(num val) => _val = ByteData(8)..setFloat64(0, val, Endian.host);
set f32(num val) =>
_val = ByteData(8)..setFloat32(_off32, val as double, Endian.host);
set f64(num val) =>
_val = ByteData(8)..setFloat64(0, val as double, Endian.host);
bool get isI32 => tag == WasmerValueTagI32;
bool get isI64 => tag == WasmerValueTagI64;
@ -131,6 +136,19 @@ class WasmerValue extends Struct {
bool get isF64 => tag == WasmerValueTagF64;
}
// wasmer_limits_t
class WasmerLimits extends Struct {
@Uint32()
external int min;
// bool
@Uint8()
external int has_max;
@Uint32()
external int max;
}
// wasmer_compile
typedef NativeWasmerCompileFn = Uint32 Function(
Pointer<Pointer<WasmerModule>>, Pointer<Uint8>, Uint32);
@ -286,3 +304,33 @@ typedef NativeWasmerExportFuncCallFn = Uint32 Function(
Uint32);
typedef WasmerExportFuncCallFn = int Function(Pointer<WasmerExportFunc>,
Pointer<WasmerValue>, int, Pointer<WasmerValue>, int);
// wasmer_export_to_memory
typedef NativeWasmerExportToMemoryFn = Uint32 Function(
Pointer<WasmerExport>, Pointer<Pointer<WasmerMemory>>);
typedef WasmerExportToMemoryFn = int Function(
Pointer<WasmerExport>, Pointer<Pointer<WasmerMemory>>);
// wasmer_memory_new_ptr
typedef NativeWasmerMemoryNewPtrFn = Uint32 Function(
Pointer<Pointer<WasmerMemory>>, Pointer<WasmerLimits>);
typedef WasmerMemoryNewPtrFn = int Function(
Pointer<Pointer<WasmerMemory>>, Pointer<WasmerLimits>);
// wasmer_memory_grow
typedef NativeWasmerMemoryGrowFn = Uint32 Function(
Pointer<WasmerMemory>, Uint32);
typedef WasmerMemoryGrowFn = int Function(Pointer<WasmerMemory>, int);
// wasmer_memory_length
typedef NativeWasmerMemoryLengthFn = Uint32 Function(Pointer<WasmerMemory>);
typedef WasmerMemoryLengthFn = int Function(Pointer<WasmerMemory>);
// wasmer_memory_data
typedef NativeWasmerMemoryDataFn = Pointer<Uint8> Function(
Pointer<WasmerMemory>);
typedef WasmerMemoryDataFn = Pointer<Uint8> Function(Pointer<WasmerMemory>);
// wasmer_memory_data_length
typedef NativeWasmerMemoryDataLengthFn = Uint32 Function(Pointer<WasmerMemory>);
typedef WasmerMemoryDataLengthFn = int Function(Pointer<WasmerMemory>);

View file

@ -5,11 +5,13 @@
// Test errors thrown by WasmMemory.
import "package:expect/expect.dart";
import "dart:wasm";
import "package:wasm/wasm.dart";
import "dart:typed_data";
void main() {
Expect.throwsArgumentError(() => WasmMemory(1000000000));
var mem = WasmMemory(1000);
Expect.throwsArgumentError(() => mem.grow(1000000000));
Expect.throws(() => WasmMemory(1000000000));
var mem = WasmMemory(100);
Expect.throws(() => mem.grow(1000000000));
mem = WasmMemory(100, 200);
Expect.throws(() => mem.grow(300));
}

View file

@ -5,22 +5,19 @@
// Test that we can create a WasmMemory, edit it, and grow it.
import "package:expect/expect.dart";
import "dart:wasm";
import "package:wasm/wasm.dart";
import "dart:typed_data";
void main() {
var mem = WasmMemory(1000);
Expect.equals(1000, mem.lengthInPages);
Expect.equals(1000 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
var mem = WasmMemory(100);
Expect.equals(100, mem.lengthInPages);
Expect.equals(100 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
mem[123] = 45;
Expect.equals(45, mem[123]);
mem.grow(100);
Expect.equals(1100, mem.lengthInPages);
Expect.equals(1100 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
mem.grow(10);
Expect.equals(110, mem.lengthInPages);
Expect.equals(110 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
Expect.equals(45, mem[123]);
Expect.throwsArgumentError(() => WasmMemory(1000000000));
Expect.throwsArgumentError(() => mem.grow(1000000000));
}

View file

@ -5,11 +5,13 @@
// Test errors thrown by WasmMemory.
import "package:expect/expect.dart";
import "dart:wasm";
import "package:wasm/wasm.dart";
import "dart:typed_data";
void main() {
Expect.throwsArgumentError(() => WasmMemory(1000000000));
var mem = WasmMemory(1000);
Expect.throwsArgumentError(() => mem.grow(1000000000));
Expect.throws(() => WasmMemory(1000000000));
var mem = WasmMemory(100);
Expect.throws(() => mem.grow(1000000000));
mem = WasmMemory(100, 200);
Expect.throws(() => mem.grow(300));
}

View file

@ -5,22 +5,19 @@
// Test that we can create a WasmMemory, edit it, and grow it.
import "package:expect/expect.dart";
import "dart:wasm";
import "package:wasm/wasm.dart";
import "dart:typed_data";
void main() {
var mem = WasmMemory(1000);
Expect.equals(1000, mem.lengthInPages);
Expect.equals(1000 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
var mem = WasmMemory(100);
Expect.equals(100, mem.lengthInPages);
Expect.equals(100 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
mem[123] = 45;
Expect.equals(45, mem[123]);
mem.grow(100);
Expect.equals(1100, mem.lengthInPages);
Expect.equals(1100 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
mem.grow(10);
Expect.equals(110, mem.lengthInPages);
Expect.equals(110 * WasmMemory.kPageSizeInBytes, mem.lengthInBytes);
Expect.equals(45, mem[123]);
Expect.throwsArgumentError(() => WasmMemory(1000000000));
Expect.throwsArgumentError(() => mem.grow(1000000000));
}

View file

@ -36,4 +36,10 @@ void wasmer_import_descriptor_name_ptr(
wasmer_byte_array* out_name) {
*out_name = wasmer_import_descriptor_name(import_descriptor);
}
// Wraps wasmer_memory_new.
wasmer_result_t wasmer_memory_new_ptr(wasmer_memory_t** memory,
wasmer_limits_t* limits) {
return wasmer_memory_new(memory, *limits);
}
}