| // Copyright (c) 2020, 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. |
| |
| import 'runtime.dart'; |
| import 'function.dart'; |
| import 'wasmer_api.dart'; |
| import 'dart:typed_data'; |
| import 'dart:ffi'; |
| import 'package:ffi/ffi.dart'; |
| |
| /// WasmModule is a compiled module that can be instantiated. |
| class WasmModule { |
| Pointer<WasmerStore> _store; |
| late Pointer<WasmerModule> _module; |
| |
| /// Compile a module. |
| WasmModule(Uint8List data) : _store = WasmRuntime().newStore() { |
| _module = WasmRuntime().compile(_store, data); |
| } |
| |
| /// Returns a WasmInstanceBuilder that is used to add all the imports that the |
| /// module needs, and then instantiate it. |
| WasmInstanceBuilder instantiate() { |
| return WasmInstanceBuilder(this); |
| } |
| |
| /// Create a new memory with the given number of initial pages, and optional |
| /// maximum number of pages. |
| WasmMemory createMemory(int pages, [int? maxPages]) { |
| return WasmMemory._create(_store, pages, maxPages); |
| } |
| |
| /// Returns a description of all of the module's imports and exports, for |
| /// debugging. |
| String describe() { |
| var description = StringBuffer(); |
| var runtime = WasmRuntime(); |
| var imports = runtime.importDescriptors(_module); |
| for (var imp in imports) { |
| description.write("import $imp\n"); |
| } |
| var exports = runtime.exportDescriptors(_module); |
| for (var exp in exports) { |
| description.write("export $exp\n"); |
| } |
| return description.toString(); |
| } |
| } |
| |
| Pointer<WasmerTrap> _wasmFnImportTrampoline(Pointer<_WasmFnImport> imp, |
| Pointer<WasmerVal> args, Pointer<WasmerVal> results) { |
| try { |
| _WasmFnImport._call(imp, args, results); |
| } catch (e) { |
| // TODO: Use WasmerTrap to handle this case. For now just print the |
| // exception (if we ignore it, FFI will silently return a default result). |
| print(e); |
| } |
| return nullptr; |
| } |
| |
| void _wasmFnImportFinalizer(Pointer<_WasmFnImport> imp) { |
| _wasmFnImportToFn.remove(imp.address); |
| free(imp); |
| } |
| |
| final _wasmFnImportTrampolineNative = Pointer.fromFunction< |
| Pointer<WasmerTrap> Function(Pointer<_WasmFnImport>, Pointer<WasmerVal>, |
| Pointer<WasmerVal>)>(_wasmFnImportTrampoline); |
| final _wasmFnImportToFn = <int, Function>{}; |
| final _wasmFnImportFinalizerNative = |
| Pointer.fromFunction<Void Function(Pointer<_WasmFnImport>)>( |
| _wasmFnImportFinalizer); |
| |
| class _WasmFnImport extends Struct { |
| @Int32() |
| external int numArgs; |
| |
| @Int32() |
| external int returnType; |
| |
| static void _call(Pointer<_WasmFnImport> imp, Pointer<WasmerVal> rawArgs, |
| Pointer<WasmerVal> rawResult) { |
| Function fn = _wasmFnImportToFn[imp.address] as Function; |
| var args = []; |
| for (var i = 0; i < imp.ref.numArgs; ++i) { |
| args.add(rawArgs[i].toDynamic); |
| } |
| var result = Function.apply(fn, args); |
| switch (imp.ref.returnType) { |
| case WasmerValKindI32: |
| rawResult.ref.i32 = result; |
| break; |
| case WasmerValKindI64: |
| rawResult.ref.i64 = result; |
| break; |
| case WasmerValKindF32: |
| rawResult.ref.f32 = result; |
| break; |
| case WasmerValKindF64: |
| rawResult.ref.f64 = result; |
| break; |
| case WasmerValKindVoid: |
| // Do nothing. |
| } |
| } |
| } |
| |
| /// WasmInstanceBuilder is used collect all the imports that a WasmModule |
| /// requires before it is instantiated. |
| class WasmInstanceBuilder { |
| WasmModule _module; |
| late List<WasmImportDescriptor> _importDescs; |
| Map<String, int> _importIndex; |
| late Pointer<Pointer<WasmerExtern>> _imports; |
| Pointer<WasmerWasiEnv> _wasiEnv = nullptr; |
| |
| WasmInstanceBuilder(this._module) : _importIndex = {} { |
| _importDescs = WasmRuntime().importDescriptors(_module._module); |
| _imports = allocate<Pointer<WasmerExtern>>(count: _importDescs.length); |
| for (var i = 0; i < _importDescs.length; ++i) { |
| var imp = _importDescs[i]; |
| _importIndex["${imp.moduleName}::${imp.name}"] = i; |
| _imports[i] = nullptr; |
| } |
| } |
| |
| int _getIndex(String moduleName, String name) { |
| var index = _importIndex["${moduleName}::${name}"]; |
| if (index == null) { |
| throw Exception("Import not found: ${moduleName}::${name}"); |
| } else if (_imports[index] != nullptr) { |
| throw Exception("Import already filled: ${moduleName}::${name}"); |
| } else { |
| return index; |
| } |
| } |
| |
| /// Add a WasmMemory to the imports. |
| WasmInstanceBuilder addMemory( |
| String moduleName, String name, WasmMemory memory) { |
| var index = _getIndex(moduleName, name); |
| var imp = _importDescs[index]; |
| if (imp.kind != WasmerExternKindMemory) { |
| throw Exception("Import is not a memory: $imp"); |
| } |
| _imports[index] = WasmRuntime().memoryToExtern(memory._mem); |
| return this; |
| } |
| |
| /// Add a function to the imports. |
| WasmInstanceBuilder addFunction(String moduleName, String name, Function fn) { |
| var index = _getIndex(moduleName, name); |
| var imp = _importDescs[index]; |
| var runtime = WasmRuntime(); |
| |
| if (imp.kind != WasmerExternKindFunction) { |
| throw Exception("Import is not a function: $imp"); |
| } |
| |
| var argTypes = runtime.getArgTypes(imp.funcType); |
| var returnType = runtime.getReturnType(imp.funcType); |
| var wasmFnImport = allocate<_WasmFnImport>(); |
| wasmFnImport.ref.numArgs = argTypes.length; |
| wasmFnImport.ref.returnType = returnType; |
| _wasmFnImportToFn[wasmFnImport.address] = fn; |
| var fnImp = runtime.newFunc( |
| _module._store, |
| imp.funcType, |
| _wasmFnImportTrampolineNative, |
| wasmFnImport, |
| _wasmFnImportFinalizerNative); |
| _imports[index] = runtime.functionToExtern(fnImp); |
| return this; |
| } |
| |
| /// Enable WASI and add the default WASI imports. |
| WasmInstanceBuilder enableWasi( |
| {bool captureStdout = false, bool captureStderr = false}) { |
| if (_wasiEnv != nullptr) { |
| throw Exception("WASI is already enabled."); |
| } |
| var runtime = WasmRuntime(); |
| var config = runtime.newWasiConfig(); |
| if (captureStdout) runtime.captureWasiStdout(config); |
| if (captureStderr) runtime.captureWasiStderr(config); |
| _wasiEnv = runtime.newWasiEnv(config); |
| runtime.getWasiImports(_module._store, _module._module, _wasiEnv, _imports); |
| return this; |
| } |
| |
| /// Build the module instance. |
| WasmInstance build() { |
| for (var i = 0; i < _importDescs.length; ++i) { |
| if (_imports[i] == nullptr) { |
| throw Exception("Missing import: ${_importDescs[i]}"); |
| } |
| } |
| return WasmInstance(_module, _imports, _wasiEnv); |
| } |
| } |
| |
| /// WasmInstance is an instantiated WasmModule. |
| class WasmInstance { |
| WasmModule _module; |
| Pointer<WasmerInstance> _instance; |
| Pointer<WasmerMemory>? _exportedMemory; |
| Pointer<WasmerWasiEnv> _wasiEnv; |
| Stream<List<int>>? _stdout; |
| Stream<List<int>>? _stderr; |
| Map<String, WasmFunction> _functions = {}; |
| |
| WasmInstance( |
| this._module, Pointer<Pointer<WasmerExtern>> imports, this._wasiEnv) |
| : _instance = WasmRuntime() |
| .instantiate(_module._store, _module._module, imports) { |
| var runtime = WasmRuntime(); |
| var exports = runtime.exports(_instance); |
| var exportDescs = runtime.exportDescriptors(_module._module); |
| assert(exports.ref.length == exportDescs.length); |
| for (var i = 0; i < exports.ref.length; ++i) { |
| var e = exports.ref.data[i]; |
| var kind = runtime.externKind(exports.ref.data[i]); |
| String name = exportDescs[i].name; |
| if (kind == WasmerExternKindFunction) { |
| var f = runtime.externToFunction(e); |
| var ft = exportDescs[i].funcType; |
| _functions[name] = WasmFunction( |
| name, f, runtime.getArgTypes(ft), runtime.getReturnType(ft)); |
| } else if (kind == WasmerExternKindMemory) { |
| // WASM currently allows only one memory per module. |
| var mem = runtime.externToMemory(e); |
| _exportedMemory = mem; |
| if (_wasiEnv != nullptr) { |
| runtime.wasiEnvSetMemory(_wasiEnv as Pointer<WasmerWasiEnv>, mem); |
| } |
| } |
| } |
| } |
| |
| /// Searches the instantiated module for the given function. Returns null if |
| /// it is not found. |
| 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>); |
| } |
| |
| /// Returns a stream that reads from stdout. To use this, you must enable WASI |
| /// when instantiating the module, and set captureStdout to true. |
| Stream<List<int>> get stdout { |
| if (_wasiEnv == nullptr) { |
| throw Exception("Can't capture stdout without WASI enabled."); |
| } |
| return _stdout ??= WasmRuntime().getWasiStdoutStream(_wasiEnv); |
| } |
| |
| /// Returns a stream that reads from stderr. To use this, you must enable WASI |
| /// when instantiating the module, and set captureStderr to true. |
| Stream<List<int>> get stderr { |
| if (_wasiEnv == nullptr) { |
| throw Exception("Can't capture stderr without WASI enabled."); |
| } |
| return _stderr ??= WasmRuntime().getWasiStderrStream(_wasiEnv); |
| } |
| } |
| |
| /// 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._create(Pointer<WasmerStore> store, int pages, int? maxPages) |
| : _mem = WasmRuntime().newMemory(store, 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); |
| } |
| } |