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

Commit f998c23

Browse files
authored
Support module precompilation (#61)
* Verify that expected build outputs exist * Support module precompilation * Delete files that weren't supposed to be committed * Add a changlog entry * Set exit code in precompile.dart
1 parent dba082a commit f998c23

11 files changed

+189
-10
lines changed

wasm/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
- All WASM modules and isntances use a singleton store, to enable sharing of
55
memory and functions.
66
- Add options to setup.dart for configuring the build.
7+
- Add Module.serialize and Module.deserialize.
8+
- Add bin/precompile.dart, which compiles and serializes wasm modules.
79

810
## 0.1.0+1
911

wasm/bin/module.g.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ EXPORTS
8282
wasm_memorytype_delete
8383
wasm_memorytype_new
8484
wasm_module_delete
85+
wasm_module_deserialize
8586
wasm_module_exports
8687
wasm_module_imports
8788
wasm_module_new
89+
wasm_module_serialize
8890
wasm_store_delete
8991
wasm_store_new
9092
wasm_trap_delete

wasm/bin/precompile.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Precompiles and serializes a wasm module, so that it can be deserialized at
6+
// runtime using WasmModule.deserialize. This skips doing the compilation at
7+
// runtime, decreasing startup times.
8+
// Usage: dart run wasm:precompile wasm_module.wasm -o serialized
9+
10+
import 'dart:io';
11+
12+
import 'package:args/args.dart';
13+
import 'package:wasm/wasm.dart';
14+
15+
void main(List<String> arguments) {
16+
final parser = ArgParser()
17+
..addOption(
18+
'out',
19+
abbr: 'o',
20+
help: 'Output file.',
21+
)
22+
..addFlag(
23+
'help',
24+
abbr: 'h',
25+
negatable: false,
26+
help: 'Show this help.',
27+
);
28+
final args = parser.parse(arguments);
29+
30+
if (args['help'] as bool) {
31+
print('Usage: dart run wasm:precompile wasm_module.wasm -o serialized\n');
32+
print(parser.usage);
33+
return;
34+
}
35+
36+
if (args.rest.isEmpty) {
37+
print('No input file.');
38+
exitCode = 1;
39+
return;
40+
}
41+
42+
final inFile = args.rest[0];
43+
final outFile = args['out'] as String?;
44+
if (outFile == null) {
45+
print('No output file.');
46+
exitCode = 1;
47+
return;
48+
}
49+
50+
final inBytes = File(inFile).readAsBytesSync();
51+
final mod = WasmModule(inBytes);
52+
print('$inFile compiled successfully');
53+
54+
print('\n=== Wasm module info ===');
55+
print(mod.describe());
56+
57+
final outBytes = mod.serialize();
58+
File(outFile).writeAsBytesSync(outBytes);
59+
print('$outFile written successfully');
60+
return;
61+
}

wasm/lib/src/module.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ class WasmModule {
1616
late final Pointer<WasmerModule> _module;
1717

1818
/// Compile a module.
19-
WasmModule(Uint8List data) {
20-
_module = runtime.compile(this, data);
21-
}
19+
WasmModule(Uint8List data) : this._(data, false);
20+
21+
/// Deserialize a module. See [WasmModule.serialize] for more details.
22+
WasmModule.deserialize(Uint8List data) : this._(data, true);
2223

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

33+
/// Serialize the module to a [Uint8List].
34+
///
35+
/// This buffer can be saved to a file, sent to another device, even with a
36+
/// different CPU architecture, and then passed to [WasmModule.deserialize].
37+
/// Doing so speeds up start up time, by skipping compilation.
38+
Uint8List serialize() => runtime.serialize(_module);
39+
3240
/// Returns a description of all of the module's imports and exports, for
3341
/// debugging.
3442
String describe() {
@@ -43,6 +51,10 @@ class WasmModule {
4351
}
4452
return description.toString();
4553
}
54+
55+
WasmModule._(Uint8List data, bool isSerialized) {
56+
_module = runtime.loadModule(this, data, isSerialized);
57+
}
4658
}
4759

4860
Pointer<WasmerTrap> _wasmFnImportTrampoline(

wasm/lib/src/runtime.g.dart

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ class WasmRuntime {
9999
late final WasmerMemorytypeDeleteFn _memorytype_delete;
100100
late final WasmerMemorytypeNewFn _memorytype_new;
101101
late final WasmerModuleDeleteFn _module_delete;
102+
late final WasmerModuleDeserializeFn _module_deserialize;
102103
late final WasmerModuleExportsFn _module_exports;
103104
late final WasmerModuleImportsFn _module_imports;
104105
late final WasmerModuleNewFn _module_new;
106+
late final WasmerModuleSerializeFn _module_serialize;
105107
late final WasmerStoreDeleteFn _store_delete;
106108
late final WasmerStoreNewFn _store_new;
107109
late final WasmerTrapDeleteFn _trap_delete;
@@ -436,6 +438,10 @@ class WasmRuntime {
436438
_lib.lookupFunction<NativeWasmerModuleDeleteFn, WasmerModuleDeleteFn>(
437439
'wasm_module_delete',
438440
);
441+
_module_deserialize = _lib.lookupFunction<NativeWasmerModuleDeserializeFn,
442+
WasmerModuleDeserializeFn>(
443+
'wasm_module_deserialize',
444+
);
439445
_module_exports =
440446
_lib.lookupFunction<NativeWasmerModuleExportsFn, WasmerModuleExportsFn>(
441447
'wasm_module_exports',
@@ -448,6 +454,10 @@ class WasmRuntime {
448454
_lib.lookupFunction<NativeWasmerModuleNewFn, WasmerModuleNewFn>(
449455
'wasm_module_new',
450456
);
457+
_module_serialize = _lib
458+
.lookupFunction<NativeWasmerModuleSerializeFn, WasmerModuleSerializeFn>(
459+
'wasm_module_serialize',
460+
);
451461
_store_delete =
452462
_lib.lookupFunction<NativeWasmerStoreDeleteFn, WasmerStoreDeleteFn>(
453463
'wasm_store_delete',
@@ -511,9 +521,10 @@ class WasmRuntime {
511521
_set_finalizer_for_store(this, _store);
512522
}
513523

514-
Pointer<WasmerModule> compile(
524+
Pointer<WasmerModule> loadModule(
515525
Object owner,
516526
Uint8List data,
527+
bool isSerialized,
517528
) {
518529
var dataPtr = calloc<Uint8>(data.length);
519530
for (var i = 0; i < data.length; ++i) {
@@ -523,16 +534,33 @@ class WasmRuntime {
523534
dataVec.ref.data = dataPtr;
524535
dataVec.ref.length = data.length;
525536

526-
var modulePtr = _module_new(_store, dataVec);
537+
var modulePtr = isSerialized
538+
? _module_deserialize(_store, dataVec)
539+
: _module_new(_store, dataVec);
527540

528541
calloc.free(dataPtr);
529542
calloc.free(dataVec);
530543

531-
_checkNotEqual(modulePtr, nullptr, 'Wasm module compile failed.');
544+
_checkNotEqual(
545+
modulePtr,
546+
nullptr,
547+
'Wasm module ${isSerialized ? 'deserialization' : 'compilation'} failed.',
548+
);
532549
_set_finalizer_for_module(owner, modulePtr);
533550
return modulePtr;
534551
}
535552

553+
Uint8List serialize(Pointer<WasmerModule> module) {
554+
final outVec = calloc<WasmerByteVec>();
555+
outVec.ref.data = nullptr;
556+
outVec.ref.length = 0;
557+
_module_serialize(module, outVec);
558+
final result = Uint8List.fromList(outVec.ref.list);
559+
_byte_vec_delete(outVec);
560+
calloc.free(outVec);
561+
return result;
562+
}
563+
536564
Pointer _externTypeToFuncOrGlobalType(
537565
int kind,
538566
Pointer<WasmerExterntype> extern,

wasm/lib/src/wasmer_api.g.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,12 @@ typedef WasmerMemorytypeNewFn = Pointer<WasmerMemorytype> Function(
571571
typedef NativeWasmerModuleDeleteFn = Void Function(Pointer<WasmerModule>);
572572
typedef WasmerModuleDeleteFn = void Function(Pointer<WasmerModule>);
573573

574+
// wasm_module_deserialize
575+
typedef NativeWasmerModuleDeserializeFn = Pointer<WasmerModule> Function(
576+
Pointer<WasmerStore>, Pointer<WasmerByteVec>);
577+
typedef WasmerModuleDeserializeFn = Pointer<WasmerModule> Function(
578+
Pointer<WasmerStore>, Pointer<WasmerByteVec>);
579+
574580
// wasm_module_exports
575581
typedef NativeWasmerModuleExportsFn = Void Function(
576582
Pointer<WasmerModule>, Pointer<WasmerExporttypeVec>);
@@ -589,6 +595,12 @@ typedef NativeWasmerModuleNewFn = Pointer<WasmerModule> Function(
589595
typedef WasmerModuleNewFn = Pointer<WasmerModule> Function(
590596
Pointer<WasmerStore>, Pointer<WasmerByteVec>);
591597

598+
// wasm_module_serialize
599+
typedef NativeWasmerModuleSerializeFn = Void Function(
600+
Pointer<WasmerModule>, Pointer<WasmerByteVec>);
601+
typedef WasmerModuleSerializeFn = void Function(
602+
Pointer<WasmerModule>, Pointer<WasmerByteVec>);
603+
592604
// wasm_store_delete
593605
typedef NativeWasmerStoreDeleteFn = Void Function(Pointer<WasmerStore>);
594606
typedef WasmerStoreDeleteFn = void Function(Pointer<WasmerStore>);

wasm/test/corrupted_error_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ void main() {
2323
() => WasmModule(data),
2424
throwsWasmError(
2525
allOf(
26-
contains('Wasm module compile failed.'),
26+
contains('Wasm module compilation failed.'),
2727
contains('Validation error: Bad magic number (at offset 0)'),
2828
),
2929
),

wasm/test/serialize_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Test that we can serialize and deserialize a wasm module.
6+
import 'dart:io';
7+
import 'dart:typed_data';
8+
9+
import 'package:test/test.dart';
10+
import 'package:wasm/wasm.dart';
11+
12+
void main() {
13+
test('serializing and deserializing module', () {
14+
// int64_t square(int64_t n) { return n * n; }
15+
final data = Uint8List.fromList([
16+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, //
17+
0x01, 0x7e, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x04, 0x05, 0x01, 0x70,
18+
0x01, 0x01, 0x01, 0x05, 0x03, 0x01, 0x00, 0x02, 0x06, 0x08, 0x01, 0x7f,
19+
0x01, 0x41, 0x80, 0x88, 0x04, 0x0b, 0x07, 0x13, 0x02, 0x06, 0x6d, 0x65,
20+
0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x06, 0x73, 0x71, 0x75, 0x61, 0x72,
21+
0x65, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x00,
22+
0x7e, 0x0b,
23+
]);
24+
25+
final mod = WasmModule(data);
26+
final serialized = mod.serialize();
27+
28+
final inst = WasmModule.deserialize(serialized).builder().build();
29+
final fn = inst.lookupFunction('square');
30+
final n = fn(1234) as int;
31+
expect(n, 1234 * 1234);
32+
});
33+
34+
test('deserializing module from file', () {
35+
// int64_t square(int64_t n) { return n * n; }
36+
final serialized = File('test/test_files/serialized').readAsBytesSync();
37+
final inst = WasmModule.deserialize(serialized).builder().build();
38+
final fn = inst.lookupFunction('square');
39+
final n = fn(1234) as int;
40+
expect(n, 1234 * 1234);
41+
});
42+
}

wasm/test/test_files/serialized

931 Bytes
Binary file not shown.

wasm/tool/generate_ffi_boilerplate.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ def declareType(name, withCopy=True):
339339
WASM_API_EXTERN own wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t*);
340340
WASM_API_EXTERN void wasm_trap_message(const wasm_trap_t*, own wasm_message_t* out);
341341
WASM_API_EXTERN wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t*);
342+
WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out);
343+
WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*);
342344
343345
wasi_config_t* wasi_config_new(const uint8_t* program_name);
344346
wasi_env_t* wasi_env_new(wasi_config_t* config);

wasm/tool/runtime_template.dart.t

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ class WasmRuntime {
3232
_set_finalizer_for_store(this, _store);
3333
}
3434

35-
Pointer<WasmerModule> compile(
35+
Pointer<WasmerModule> loadModule(
3636
Object owner,
3737
Uint8List data,
38+
bool isSerialized,
3839
) {
3940
var dataPtr = calloc<Uint8>(data.length);
4041
for (var i = 0; i < data.length; ++i) {
@@ -44,16 +45,33 @@ class WasmRuntime {
4445
dataVec.ref.data = dataPtr;
4546
dataVec.ref.length = data.length;
4647

47-
var modulePtr = _module_new(_store, dataVec);
48+
var modulePtr = isSerialized
49+
? _module_deserialize(_store, dataVec)
50+
: _module_new(_store, dataVec);
4851

4952
calloc.free(dataPtr);
5053
calloc.free(dataVec);
5154

52-
_checkNotEqual(modulePtr, nullptr, 'Wasm module compile failed.');
55+
_checkNotEqual(
56+
modulePtr,
57+
nullptr,
58+
'Wasm module ${isSerialized ? 'deserialization' : 'compilation'} failed.',
59+
);
5360
_set_finalizer_for_module(owner, modulePtr);
5461
return modulePtr;
5562
}
5663

64+
Uint8List serialize(Pointer<WasmerModule> module) {
65+
final outVec = calloc<WasmerByteVec>();
66+
outVec.ref.data = nullptr;
67+
outVec.ref.length = 0;
68+
_module_serialize(module, outVec);
69+
final result = Uint8List.fromList(outVec.ref.list);
70+
_byte_vec_delete(outVec);
71+
calloc.free(outVec);
72+
return result;
73+
}
74+
5775
Pointer _externTypeToFuncOrGlobalType(
5876
int kind,
5977
Pointer<WasmerExterntype> extern,

0 commit comments

Comments
 (0)