Skip to content
This repository was archived by the owner on Jul 1, 2023. It is now read-only.

Support module precompilation #61

Merged
merged 8 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wasm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
- All WASM modules and isntances use a singleton store, to enable sharing of
memory and functions.
- Add options to setup.dart for configuring the build.
- Add Module.serialize and Module.deserialize.
- Add bin/precompile.dart, which compiles and serializes wasm modules.

## 0.1.0+1

Expand Down
2 changes: 2 additions & 0 deletions wasm/bin/module.g.def
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ EXPORTS
wasm_memorytype_delete
wasm_memorytype_new
wasm_module_delete
wasm_module_deserialize
wasm_module_exports
wasm_module_imports
wasm_module_new
wasm_module_serialize
wasm_store_delete
wasm_store_new
wasm_trap_delete
Expand Down
61 changes: 61 additions & 0 deletions wasm/bin/precompile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2021, 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.

// Precompiles and serializes a wasm module, so that it can be deserialized at
// runtime using WasmModule.deserialize. This skips doing the compilation at
// runtime, decreasing startup times.
// Usage: dart run wasm:precompile wasm_module.wasm -o serialized

import 'dart:io';

import 'package:args/args.dart';
import 'package:wasm/wasm.dart';

void main(List<String> arguments) {
final parser = ArgParser()
..addOption(
'out',
abbr: 'o',
help: 'Output file.',
)
..addFlag(
'help',
abbr: 'h',
negatable: false,
help: 'Show this help.',
);
final args = parser.parse(arguments);

if (args['help'] as bool) {
print('Usage: dart run wasm:precompile wasm_module.wasm -o serialized\n');
print(parser.usage);
return;
}

if (args.rest.isEmpty) {
print('No input file.');
exitCode = 1;
return;
}

final inFile = args.rest[0];
final outFile = args['out'] as String?;
if (outFile == null) {
print('No output file.');
exitCode = 1;
return;
}

final inBytes = File(inFile).readAsBytesSync();
final mod = WasmModule(inBytes);
print('$inFile compiled successfully');

print('\n=== Wasm module info ===');
print(mod.describe());

final outBytes = mod.serialize();
File(outFile).writeAsBytesSync(outBytes);
print('$outFile written successfully');
return;
}
18 changes: 15 additions & 3 deletions wasm/lib/src/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ class WasmModule {
late final Pointer<WasmerModule> _module;

/// Compile a module.
WasmModule(Uint8List data) {
_module = runtime.compile(this, data);
}
WasmModule(Uint8List data) : this._(data, false);

/// Deserialize a module. See [WasmModule.serialize] for more details.
WasmModule.deserialize(Uint8List data) : this._(data, true);

/// Returns a [WasmInstanceBuilder] that is used to add all the imports that
/// the module needs before instantiating it.
Expand All @@ -29,6 +30,13 @@ class WasmModule {
WasmMemory createMemory(int pages, [int? maxPages]) =>
WasmMemory._create(pages, maxPages);

/// Serialize the module to a [Uint8List].
///
/// This buffer can be saved to a file, sent to another device, even with a
/// different CPU architecture, and then passed to [WasmModule.deserialize].
/// Doing so speeds up start up time, by skipping compilation.
Uint8List serialize() => runtime.serialize(_module);

/// Returns a description of all of the module's imports and exports, for
/// debugging.
String describe() {
Expand All @@ -43,6 +51,10 @@ class WasmModule {
}
return description.toString();
}

WasmModule._(Uint8List data, bool isSerialized) {
_module = runtime.loadModule(this, data, isSerialized);
}
}

Pointer<WasmerTrap> _wasmFnImportTrampoline(
Expand Down
34 changes: 31 additions & 3 deletions wasm/lib/src/runtime.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ class WasmRuntime {
late final WasmerMemorytypeDeleteFn _memorytype_delete;
late final WasmerMemorytypeNewFn _memorytype_new;
late final WasmerModuleDeleteFn _module_delete;
late final WasmerModuleDeserializeFn _module_deserialize;
late final WasmerModuleExportsFn _module_exports;
late final WasmerModuleImportsFn _module_imports;
late final WasmerModuleNewFn _module_new;
late final WasmerModuleSerializeFn _module_serialize;
late final WasmerStoreDeleteFn _store_delete;
late final WasmerStoreNewFn _store_new;
late final WasmerTrapDeleteFn _trap_delete;
Expand Down Expand Up @@ -436,6 +438,10 @@ class WasmRuntime {
_lib.lookupFunction<NativeWasmerModuleDeleteFn, WasmerModuleDeleteFn>(
'wasm_module_delete',
);
_module_deserialize = _lib.lookupFunction<NativeWasmerModuleDeserializeFn,
WasmerModuleDeserializeFn>(
'wasm_module_deserialize',
);
_module_exports =
_lib.lookupFunction<NativeWasmerModuleExportsFn, WasmerModuleExportsFn>(
'wasm_module_exports',
Expand All @@ -448,6 +454,10 @@ class WasmRuntime {
_lib.lookupFunction<NativeWasmerModuleNewFn, WasmerModuleNewFn>(
'wasm_module_new',
);
_module_serialize = _lib
.lookupFunction<NativeWasmerModuleSerializeFn, WasmerModuleSerializeFn>(
'wasm_module_serialize',
);
_store_delete =
_lib.lookupFunction<NativeWasmerStoreDeleteFn, WasmerStoreDeleteFn>(
'wasm_store_delete',
Expand Down Expand Up @@ -511,9 +521,10 @@ class WasmRuntime {
_set_finalizer_for_store(this, _store);
}

Pointer<WasmerModule> compile(
Pointer<WasmerModule> loadModule(
Object owner,
Uint8List data,
bool isSerialized,
) {
var dataPtr = calloc<Uint8>(data.length);
for (var i = 0; i < data.length; ++i) {
Expand All @@ -523,16 +534,33 @@ class WasmRuntime {
dataVec.ref.data = dataPtr;
dataVec.ref.length = data.length;

var modulePtr = _module_new(_store, dataVec);
var modulePtr = isSerialized
? _module_deserialize(_store, dataVec)
: _module_new(_store, dataVec);

calloc.free(dataPtr);
calloc.free(dataVec);

_checkNotEqual(modulePtr, nullptr, 'Wasm module compile failed.');
_checkNotEqual(
modulePtr,
nullptr,
'Wasm module ${isSerialized ? 'deserialization' : 'compilation'} failed.',
);
_set_finalizer_for_module(owner, modulePtr);
return modulePtr;
}

Uint8List serialize(Pointer<WasmerModule> module) {
final outVec = calloc<WasmerByteVec>();
outVec.ref.data = nullptr;
outVec.ref.length = 0;
_module_serialize(module, outVec);
final result = Uint8List.fromList(outVec.ref.list);
_byte_vec_delete(outVec);
calloc.free(outVec);
return result;
}

Pointer _externTypeToFuncOrGlobalType(
int kind,
Pointer<WasmerExterntype> extern,
Expand Down
12 changes: 12 additions & 0 deletions wasm/lib/src/wasmer_api.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,12 @@ typedef WasmerMemorytypeNewFn = Pointer<WasmerMemorytype> Function(
typedef NativeWasmerModuleDeleteFn = Void Function(Pointer<WasmerModule>);
typedef WasmerModuleDeleteFn = void Function(Pointer<WasmerModule>);

// wasm_module_deserialize
typedef NativeWasmerModuleDeserializeFn = Pointer<WasmerModule> Function(
Pointer<WasmerStore>, Pointer<WasmerByteVec>);
typedef WasmerModuleDeserializeFn = Pointer<WasmerModule> Function(
Pointer<WasmerStore>, Pointer<WasmerByteVec>);

// wasm_module_exports
typedef NativeWasmerModuleExportsFn = Void Function(
Pointer<WasmerModule>, Pointer<WasmerExporttypeVec>);
Expand All @@ -589,6 +595,12 @@ typedef NativeWasmerModuleNewFn = Pointer<WasmerModule> Function(
typedef WasmerModuleNewFn = Pointer<WasmerModule> Function(
Pointer<WasmerStore>, Pointer<WasmerByteVec>);

// wasm_module_serialize
typedef NativeWasmerModuleSerializeFn = Void Function(
Pointer<WasmerModule>, Pointer<WasmerByteVec>);
typedef WasmerModuleSerializeFn = void Function(
Pointer<WasmerModule>, Pointer<WasmerByteVec>);

// wasm_store_delete
typedef NativeWasmerStoreDeleteFn = Void Function(Pointer<WasmerStore>);
typedef WasmerStoreDeleteFn = void Function(Pointer<WasmerStore>);
Expand Down
2 changes: 1 addition & 1 deletion wasm/test/corrupted_error_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ void main() {
() => WasmModule(data),
throwsWasmError(
allOf(
contains('Wasm module compile failed.'),
contains('Wasm module compilation failed.'),
contains('Validation error: Bad magic number (at offset 0)'),
),
),
Expand Down
42 changes: 42 additions & 0 deletions wasm/test/serialize_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2021, 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 serialize and deserialize a wasm module.
import 'dart:io';
import 'dart:typed_data';

import 'package:test/test.dart';
import 'package:wasm/wasm.dart';

void main() {
test('serializing and deserializing module', () {
// int64_t square(int64_t n) { return n * n; }
final 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,
]);

final mod = WasmModule(data);
final serialized = mod.serialize();

final inst = WasmModule.deserialize(serialized).builder().build();
final fn = inst.lookupFunction('square');
final n = fn(1234) as int;
expect(n, 1234 * 1234);
});

test('deserializing module from file', () {
// int64_t square(int64_t n) { return n * n; }
final serialized = File('test/test_files/serialized').readAsBytesSync();
final inst = WasmModule.deserialize(serialized).builder().build();
final fn = inst.lookupFunction('square');
final n = fn(1234) as int;
expect(n, 1234 * 1234);
});
}
Binary file added wasm/test/test_files/serialized
Binary file not shown.
2 changes: 2 additions & 0 deletions wasm/tool/generate_ffi_boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ def declareType(name, withCopy=True):
WASM_API_EXTERN own wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t*);
WASM_API_EXTERN void wasm_trap_message(const wasm_trap_t*, own wasm_message_t* out);
WASM_API_EXTERN wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t*);
WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out);
WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*);

wasi_config_t* wasi_config_new(const uint8_t* program_name);
wasi_env_t* wasi_env_new(wasi_config_t* config);
Expand Down
24 changes: 21 additions & 3 deletions wasm/tool/runtime_template.dart.t
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ class WasmRuntime {
_set_finalizer_for_store(this, _store);
}

Pointer<WasmerModule> compile(
Pointer<WasmerModule> loadModule(
Object owner,
Uint8List data,
bool isSerialized,
) {
var dataPtr = calloc<Uint8>(data.length);
for (var i = 0; i < data.length; ++i) {
Expand All @@ -44,16 +45,33 @@ class WasmRuntime {
dataVec.ref.data = dataPtr;
dataVec.ref.length = data.length;

var modulePtr = _module_new(_store, dataVec);
var modulePtr = isSerialized
? _module_deserialize(_store, dataVec)
: _module_new(_store, dataVec);

calloc.free(dataPtr);
calloc.free(dataVec);

_checkNotEqual(modulePtr, nullptr, 'Wasm module compile failed.');
_checkNotEqual(
modulePtr,
nullptr,
'Wasm module ${isSerialized ? 'deserialization' : 'compilation'} failed.',
);
_set_finalizer_for_module(owner, modulePtr);
return modulePtr;
}

Uint8List serialize(Pointer<WasmerModule> module) {
final outVec = calloc<WasmerByteVec>();
outVec.ref.data = nullptr;
outVec.ref.length = 0;
_module_serialize(module, outVec);
final result = Uint8List.fromList(outVec.ref.list);
_byte_vec_delete(outVec);
calloc.free(outVec);
return result;
}

Pointer _externTypeToFuncOrGlobalType(
int kind,
Pointer<WasmerExterntype> extern,
Expand Down