Skip to content

Commit e2840ed

Browse files
liamappelbecommit-bot@chromium.org
authored andcommitted
[wasm] Basic WASI support.
Enabling WASI when building an instance sets up the default WASI imports, and can optionally capture stdout/stderr. I'd like to set up something similar for stdin, but Wasmer doesn't support that yet. I also improved error handling in WasmRuntime by adding Wasmer's last error to the thrown exceptions where appropriate. Bug: #37882 Change-Id: I2d8546e878bcb43c7093490eac085aa62d318e96 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/167904 Reviewed-by: Ryan Macnak <[email protected]> Commit-Queue: Liam Appelbe <[email protected]>
1 parent 043bebc commit e2840ed

11 files changed

+1223
-101
lines changed

pkg/wasm/lib/src/module.dart

+45-3
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class WasmInstanceBuilder {
114114
late List<WasmImportDescriptor> _importDescs;
115115
Map<String, int> _importIndex;
116116
late Pointer<Pointer<WasmerExtern>> _imports;
117+
Pointer<WasmerWasiEnv> _wasiEnv = nullptr;
117118

118119
WasmInstanceBuilder(this._module) : _importIndex = {} {
119120
_importDescs = WasmRuntime().importDescriptors(_module._module);
@@ -174,14 +175,29 @@ class WasmInstanceBuilder {
174175
return this;
175176
}
176177

178+
/// Enable WASI and add the default WASI imports.
179+
WasmInstanceBuilder enableWasi(
180+
{bool captureStdout = false, bool captureStderr = false}) {
181+
if (_wasiEnv != nullptr) {
182+
throw Exception("WASI is already enabled.");
183+
}
184+
var runtime = WasmRuntime();
185+
var config = runtime.newWasiConfig();
186+
if (captureStdout) runtime.captureWasiStdout(config);
187+
if (captureStderr) runtime.captureWasiStderr(config);
188+
_wasiEnv = runtime.newWasiEnv(config);
189+
runtime.getWasiImports(_module._store, _module._module, _wasiEnv, _imports);
190+
return this;
191+
}
192+
177193
/// Build the module instance.
178194
WasmInstance build() {
179195
for (var i = 0; i < _importDescs.length; ++i) {
180196
if (_imports[i] == nullptr) {
181197
throw Exception("Missing import: ${_importDescs[i]}");
182198
}
183199
}
184-
return WasmInstance(_module, _imports);
200+
return WasmInstance(_module, _imports, _wasiEnv);
185201
}
186202
}
187203

@@ -190,9 +206,13 @@ class WasmInstance {
190206
WasmModule _module;
191207
Pointer<WasmerInstance> _instance;
192208
Pointer<WasmerMemory>? _exportedMemory;
209+
Pointer<WasmerWasiEnv> _wasiEnv;
210+
Stream<List<int>>? _stdout;
211+
Stream<List<int>>? _stderr;
193212
Map<String, WasmFunction> _functions = {};
194213

195-
WasmInstance(this._module, Pointer<Pointer<WasmerExtern>> imports)
214+
WasmInstance(
215+
this._module, Pointer<Pointer<WasmerExtern>> imports, this._wasiEnv)
196216
: _instance = WasmRuntime()
197217
.instantiate(_module._store, _module._module, imports) {
198218
var runtime = WasmRuntime();
@@ -210,7 +230,11 @@ class WasmInstance {
210230
name, f, runtime.getArgTypes(ft), runtime.getReturnType(ft));
211231
} else if (kind == WasmerExternKindMemory) {
212232
// WASM currently allows only one memory per module.
213-
_exportedMemory = runtime.externToMemory(e);
233+
var mem = runtime.externToMemory(e);
234+
_exportedMemory = mem;
235+
if (_wasiEnv != nullptr) {
236+
runtime.wasiEnvSetMemory(_wasiEnv as Pointer<WasmerWasiEnv>, mem);
237+
}
214238
}
215239
}
216240
}
@@ -228,6 +252,24 @@ class WasmInstance {
228252
}
229253
return WasmMemory._fromExport(_exportedMemory as Pointer<WasmerMemory>);
230254
}
255+
256+
/// Returns a stream that reads from stdout. To use this, you must enable WASI
257+
/// when instantiating the module, and set captureStdout to true.
258+
Stream<List<int>> get stdout {
259+
if (_wasiEnv == nullptr) {
260+
throw Exception("Can't capture stdout without WASI enabled.");
261+
}
262+
return _stdout ??= WasmRuntime().getWasiStdoutStream(_wasiEnv);
263+
}
264+
265+
/// Returns a stream that reads from stderr. To use this, you must enable WASI
266+
/// when instantiating the module, and set captureStderr to true.
267+
Stream<List<int>> get stderr {
268+
if (_wasiEnv == nullptr) {
269+
throw Exception("Can't capture stderr without WASI enabled.");
270+
}
271+
return _stderr ??= WasmRuntime().getWasiStderrStream(_wasiEnv);
272+
}
231273
}
232274

233275
/// WasmMemory contains the memory of a WasmInstance.

pkg/wasm/lib/src/runtime.dart

+129-24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// To regenerate the file, use the following command
77
// "generate_ffi_boilerplate.py".
88

9+
import 'dart:async';
910
import 'dart:convert';
1011
import 'dart:ffi';
1112
import 'dart:io';
@@ -58,6 +59,15 @@ class WasmRuntime {
5859

5960
DynamicLibrary _lib;
6061
late Pointer<WasmerEngine> _engine;
62+
late WasmerWasiConfigInheritStderrFn _wasi_config_inherit_stderr;
63+
late WasmerWasiConfigInheritStdoutFn _wasi_config_inherit_stdout;
64+
late WasmerWasiConfigNewFn _wasi_config_new;
65+
late WasmerWasiEnvDeleteFn _wasi_env_delete;
66+
late WasmerWasiEnvNewFn _wasi_env_new;
67+
late WasmerWasiEnvReadStderrFn _wasi_env_read_stderr;
68+
late WasmerWasiEnvReadStdoutFn _wasi_env_read_stdout;
69+
late WasmerWasiEnvSetMemoryFn _wasi_env_set_memory;
70+
late WasmerWasiGetImportsFn _wasi_get_imports;
6171
late WasmerByteVecDeleteFn _byte_vec_delete;
6272
late WasmerByteVecNewFn _byte_vec_new;
6373
late WasmerByteVecNewEmptyFn _byte_vec_new_empty;
@@ -120,11 +130,11 @@ class WasmRuntime {
120130
late WasmerValtypeVecNewFn _valtype_vec_new;
121131
late WasmerValtypeVecNewEmptyFn _valtype_vec_new_empty;
122132
late WasmerValtypeVecNewUninitializedFn _valtype_vec_new_uninitialized;
133+
late WasmerWasmerLastErrorLengthFn _wasmer_last_error_length;
134+
late WasmerWasmerLastErrorMessageFn _wasmer_last_error_message;
123135

124136
factory WasmRuntime() {
125-
WasmRuntime inst = _inst ?? WasmRuntime._init();
126-
_inst = inst;
127-
return inst;
137+
return _inst ??= WasmRuntime._init();
128138
}
129139

130140
static String _getLibName() {
@@ -165,6 +175,29 @@ class WasmRuntime {
165175

166176
WasmRuntime._init()
167177
: _lib = DynamicLibrary.open(path.join(_getLibDir(), _getLibName())) {
178+
_wasi_config_inherit_stderr = _lib.lookupFunction<
179+
NativeWasmerWasiConfigInheritStderrFn,
180+
WasmerWasiConfigInheritStderrFn>('wasi_config_inherit_stderr');
181+
_wasi_config_inherit_stdout = _lib.lookupFunction<
182+
NativeWasmerWasiConfigInheritStdoutFn,
183+
WasmerWasiConfigInheritStdoutFn>('wasi_config_inherit_stdout');
184+
_wasi_config_new =
185+
_lib.lookupFunction<NativeWasmerWasiConfigNewFn, WasmerWasiConfigNewFn>(
186+
'wasi_config_new');
187+
_wasi_env_delete =
188+
_lib.lookupFunction<NativeWasmerWasiEnvDeleteFn, WasmerWasiEnvDeleteFn>(
189+
'wasi_env_delete');
190+
_wasi_env_new =
191+
_lib.lookupFunction<NativeWasmerWasiEnvNewFn, WasmerWasiEnvNewFn>(
192+
'wasi_env_new');
193+
_wasi_env_read_stderr = _lib.lookupFunction<NativeWasmerWasiEnvReadStderrFn,
194+
WasmerWasiEnvReadStderrFn>('wasi_env_read_stderr');
195+
_wasi_env_read_stdout = _lib.lookupFunction<NativeWasmerWasiEnvReadStdoutFn,
196+
WasmerWasiEnvReadStdoutFn>('wasi_env_read_stdout');
197+
_wasi_env_set_memory = _lib.lookupFunction<NativeWasmerWasiEnvSetMemoryFn,
198+
WasmerWasiEnvSetMemoryFn>('wasi_env_set_memory');
199+
_wasi_get_imports = _lib.lookupFunction<NativeWasmerWasiGetImportsFn,
200+
WasmerWasiGetImportsFn>('wasi_get_imports');
168201
_byte_vec_delete =
169202
_lib.lookupFunction<NativeWasmerByteVecDeleteFn, WasmerByteVecDeleteFn>(
170203
'wasm_byte_vec_delete');
@@ -329,6 +362,12 @@ class WasmRuntime {
329362
NativeWasmerValtypeVecNewUninitializedFn,
330363
WasmerValtypeVecNewUninitializedFn>(
331364
'wasm_valtype_vec_new_uninitialized');
365+
_wasmer_last_error_length = _lib.lookupFunction<
366+
NativeWasmerWasmerLastErrorLengthFn,
367+
WasmerWasmerLastErrorLengthFn>('wasmer_last_error_length');
368+
_wasmer_last_error_message = _lib.lookupFunction<
369+
NativeWasmerWasmerLastErrorMessageFn,
370+
WasmerWasmerLastErrorMessageFn>('wasmer_last_error_message');
332371

333372
_engine = _engine_new();
334373
}
@@ -351,11 +390,7 @@ class WasmRuntime {
351390
free(dataPtr);
352391
free(dataVec);
353392

354-
if (modulePtr == nullptr) {
355-
throw Exception("Wasm module compile failed");
356-
}
357-
358-
return modulePtr;
393+
return _checkNotEqual(modulePtr, nullptr, "Wasm module compile failed.");
359394
}
360395

361396
List<WasmExportDescriptor> exportDescriptors(Pointer<WasmerModule> module) {
@@ -399,12 +434,8 @@ class WasmRuntime {
399434

400435
Pointer<WasmerInstance> instantiate(Pointer<WasmerStore> store,
401436
Pointer<WasmerModule> module, Pointer<Pointer<WasmerExtern>> imports) {
402-
var instancePtr = _instance_new(store, module, imports, nullptr);
403-
if (instancePtr == nullptr) {
404-
throw Exception("Wasm module instantiation failed");
405-
}
406-
407-
return instancePtr;
437+
return _checkNotEqual(_instance_new(store, module, imports, nullptr),
438+
nullptr, "Wasm module instantiation failed.");
408439
}
409440

410441
Pointer<WasmerExternVec> exports(Pointer<WasmerInstance> instancePtr) {
@@ -464,19 +495,13 @@ class WasmRuntime {
464495
limPtr.ref.max = maxPages ?? wasm_limits_max_default;
465496
var memType = _memorytype_new(limPtr);
466497
free(limPtr);
467-
Pointer<WasmerMemory> memPtr = _memory_new(store, memType);
468-
469-
if (memPtr == nullptr) {
470-
throw Exception("Failed to create memory");
471-
}
472-
return memPtr;
498+
return _checkNotEqual(
499+
_memory_new(store, memType), nullptr, "Failed to create memory.");
473500
}
474501

475502
void growMemory(Pointer<WasmerMemory> memory, int deltaPages) {
476-
var result = _memory_grow(memory, deltaPages);
477-
if (result == 0) {
478-
throw Exception("Failed to grow memory");
479-
}
503+
_checkNotEqual(
504+
_memory_grow(memory, deltaPages), 0, "Failed to grow memory.");
480505
}
481506

482507
int memoryLength(Pointer<WasmerMemory> memory) {
@@ -497,9 +522,89 @@ class WasmRuntime {
497522
store, funcType, func.cast(), env.cast(), finalizer.cast());
498523
}
499524

525+
Pointer<WasmerWasiConfig> newWasiConfig() {
526+
var name = allocate<Uint8>();
527+
name[0] = 0;
528+
var config = _wasi_config_new(name);
529+
free(name);
530+
return _checkNotEqual(config, nullptr, "Failed to create WASI config.");
531+
}
532+
533+
void captureWasiStdout(Pointer<WasmerWasiConfig> config) {
534+
_wasi_config_inherit_stdout(config);
535+
}
536+
537+
void captureWasiStderr(Pointer<WasmerWasiConfig> config) {
538+
_wasi_config_inherit_stderr(config);
539+
}
540+
541+
Pointer<WasmerWasiEnv> newWasiEnv(Pointer<WasmerWasiConfig> config) {
542+
return _checkNotEqual(
543+
_wasi_env_new(config), nullptr, "Failed to create WASI environment.");
544+
}
545+
546+
void wasiEnvSetMemory(
547+
Pointer<WasmerWasiEnv> env, Pointer<WasmerMemory> memory) {
548+
_wasi_env_set_memory(env, memory);
549+
}
550+
551+
void getWasiImports(Pointer<WasmerStore> store, Pointer<WasmerModule> mod,
552+
Pointer<WasmerWasiEnv> env, Pointer<Pointer<WasmerExtern>> imports) {
553+
_checkNotEqual(_wasi_get_imports(store, mod, env, imports), 0,
554+
"Failed to fill WASI imports.");
555+
}
556+
557+
Stream<List<int>> getWasiStdoutStream(Pointer<WasmerWasiEnv> env) {
558+
return Stream.fromIterable(_WasiStreamIterable(env, _wasi_env_read_stdout));
559+
}
560+
561+
Stream<List<int>> getWasiStderrStream(Pointer<WasmerWasiEnv> env) {
562+
return Stream.fromIterable(_WasiStreamIterable(env, _wasi_env_read_stderr));
563+
}
564+
565+
String _getLastError() {
566+
var length = _wasmer_last_error_length();
567+
var buf = allocate<Uint8>(count: length);
568+
_wasmer_last_error_message(buf, length);
569+
String message = utf8.decode(buf.asTypedList(length));
570+
free(buf);
571+
return message;
572+
}
573+
574+
T _checkNotEqual<T>(T x, T y, String errorMessage) {
575+
if (x == y) {
576+
throw Exception("$errorMessage\n${_getLastError()}");
577+
}
578+
return x;
579+
}
580+
500581
static String getSignatureString(
501582
String name, List<int> argTypes, int returnType) {
502583
return "${wasmerValKindName(returnType)} $name" +
503584
"(${argTypes.map(wasmerValKindName).join(", ")})";
504585
}
505586
}
587+
588+
class _WasiStreamIterator implements Iterator<List<int>> {
589+
static final int _bufferLength = 1024;
590+
Pointer<WasmerWasiEnv> _env;
591+
Function _reader;
592+
Pointer<Uint8> _buf = allocate<Uint8>(count: _bufferLength);
593+
int _length = 0;
594+
_WasiStreamIterator(this._env, this._reader) {}
595+
596+
bool moveNext() {
597+
_length = _reader(_env, _buf, _bufferLength);
598+
return true;
599+
}
600+
601+
List<int> get current => _buf.asTypedList(_length);
602+
}
603+
604+
class _WasiStreamIterable extends Iterable<List<int>> {
605+
Pointer<WasmerWasiEnv> _env;
606+
Function _reader;
607+
_WasiStreamIterable(this._env, this._reader) {}
608+
@override
609+
Iterator<List<int>> get iterator => _WasiStreamIterator(_env, _reader);
610+
}

0 commit comments

Comments
 (0)