wasm_run 0.1.0+1 copy "wasm_run: ^0.1.0+1" to clipboard
wasm_run: ^0.1.0+1 copied to clipboard

Web Assembly executor. Uses Rust's wasmtime optimizing runtime or wasmi interpreter.

example/lib/main.dart

// ignore_for_file: avoid_print, prefer_const_literals_to_create_immutables

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:test/test.dart';
import 'package:wasm_run/load_module.dart';
// TODO(wat): implement wat in main api
// ignore: implementation_imports
import 'package:wasm_run/src/ffi.dart' show defaultInstance;
// ignore: implementation_imports
import 'package:wasm_run/src/ffi/setup_dynamic_library.dart'
    show setUpDesktopDynamicLibrary;
import 'package:wasm_run/wasm_run.dart';
import 'package:wasm_run_example/runner_identity/runner_identity.dart';
import 'package:wasm_run_example/simd_test.dart' show simdTests;
import 'package:wasm_run_example/threads_test.dart';
import 'package:wasm_run_example/wasi_test.dart';
import 'package:wasm_wit_component_example/types_gen_big_int_test.dart'
    show typesGenBigIntWitComponentTests;
import 'package:wasm_wit_component_example/types_gen_test.dart'
    show typesGenWitComponentTests;
import 'package:wasm_wit_component_example/wit_generator_test.dart';

const isWeb = identical(0, 0.0);
const compiledWasmLibraryPath =
    // ignore: unnecessary_const
    const String.fromEnvironment('compiledWasmLibraryPath');
final isLibrary = !isWeb || compiledWasmLibraryPath.isNotEmpty;

Future<Uint8List> getBinary({
  required String wat,
  required String base64Binary,
}) async {
  Uint8List binary;
  try {
    final w = defaultInstance();
    binary = await w.parseWatFormat(wat: wat);
    // ignore: avoid_catching_errors
  } catch (_) {
    if (isWeb) {
      return base64Decode(base64Binary);
    } else {
      rethrow;
    }
  }

  final encoded = base64Encode(binary);
  if (encoded != base64Binary) {
    throw StateError(
      'Invalid base64 encoded module.\nParam: $base64Binary\nProcessedWat: $encoded',
    );
  }

  return binary;
}

class TestArgs {
  final Future<Uint8List> Function()? getWasiExampleBytes;
  final Future<Uint8List> Function()? getThreadsExampleBytes;
  final Future<Uint8List> Function()? getWitComponentExampleBytes;
  final Future<Directory> Function()? getDirectory;

  TestArgs({
    required this.getWasiExampleBytes,
    required this.getThreadsExampleBytes,
    required this.getWitComponentExampleBytes,
    required this.getDirectory,
  });
}

void testAll({TestArgs? testArgs}) {
  print('RUNNING ALL TEST IN ${getRunnerIdentity()}');

  test('WasmFeature', () async {
    final runtime = await wasmRuntimeFeatures();
    final defaultFeatures = runtime.defaultFeatures;
    final supportedFeatures = runtime.supportedFeatures;

    expect(defaultFeatures.wasiFeatures, isNotNull);
    expect(supportedFeatures.wasiFeatures, isNotNull);

    final alwaysTrue = [
      supportedFeatures.mutableGlobal,
      supportedFeatures.saturatingFloatToInt,
      supportedFeatures.signExtension,
      supportedFeatures.referenceTypes,
      supportedFeatures.multiValue,
      supportedFeatures.bulkMemory,
      supportedFeatures.floats,
      defaultFeatures.mutableGlobal,
      defaultFeatures.saturatingFloatToInt,
      defaultFeatures.signExtension,
      defaultFeatures.referenceTypes,
      defaultFeatures.multiValue,
      defaultFeatures.bulkMemory,
      defaultFeatures.floats,
    ];
    expect(alwaysTrue, Iterable.generate(alwaysTrue.length, (_) => true));

    final alwaysFalse = [
      supportedFeatures.componentModel,
      supportedFeatures.memoryControl,
      supportedFeatures.garbageCollection,
      defaultFeatures.componentModel,
      defaultFeatures.memoryControl,
      defaultFeatures.garbageCollection,
    ];
    expect(alwaysFalse, Iterable.generate(alwaysFalse.length, (_) => false));

    expect(supportedFeatures.exceptions, !isLibrary);
    if (!isLibrary) {
      expect(defaultFeatures, supportedFeatures);
    }
    expect(runtime.name, isIn(['wasmi', 'wasmtime', 'browser']));
    switch (runtime.name) {
      case 'wasmi':
        expect(runtime.version, '0.31.0');
        expect(runtime.isBrowser, false);
        break;
      case 'wasmtime':
        expect(runtime.version, '14.0.4');
        expect(runtime.isBrowser, false);
        break;
      case 'browser':
        expect(runtime.version, WasmRunLibrary.version);
        expect(runtime.isBrowser, true);
        break;
      default:
        throw StateError('Unknown runtime: ${runtime.name}');
    }
  });

  test('simple add', () async {
    final Uint8List binary = await getBinary(
      wat: r'''
(module
    (func (export "add") (param $a i32) (param $b i32) (result i32)
        local.get $a
        local.get $b
        i32.add
    )
)
''',
      base64Binary:
          'AGFzbQEAAAABBwFgAn9/AX8DAgEABwcBA2FkZAAACgkBBwAgACABagsAEARuYW1lAgkBAAIAAWEBAWI=',
    );

    final module = compileWasmModuleSync(binary);

    expect(
      module.getExports().map((e) => e.toString()),
      [
        const WasmModuleExport('add', WasmExternalKind.function).toString(),
      ],
    );
    expect(module.getImports(), isEmpty);

    final instance = module.builder().buildSync();
    final add = instance.getFunction('add')!;
    expect(
      add.params,
      isLibrary ? [ValueTy.i32, ValueTy.i32] : [null, null],
    );
    expect(add.results, isLibrary ? [ValueTy.i32] : null);
    final result = add.call([1, 4]);
    expect(result, [5]);
    print('result $result');
  });

  test('import function', () async {
    final binary = await getBinary(
      wat: r'''
        (module
            (import "host" "hello" (func $host_hello (param i32)))
            (func (export "hello")
                (call $host_hello (i32.const 3))
            )
        )
    ''',
      base64Binary:
          'AGFzbQEAAAABCAJgAX8AYAAAAg4BBGhvc3QFaGVsbG8AAAMCAQEHCQEFaGVsbG8AAQoIAQYAQQMQAAsAFARuYW1lAQ0BAApob3N0X2hlbGxv',
    );

    final module = compileWasmModuleSync(binary);
    expect(
      module.getExports().map((e) => e.toString()),
      [
        const WasmModuleExport('hello', WasmExternalKind.function).toString(),
      ],
    );
    expect(
      module.getImports().map((e) => e.toString()),
      [
        const WasmModuleImport(
          'host',
          'hello',
          WasmExternalKind.function,
        ).toString(),
      ],
    );

    int? argsList;

    final hostHello = WasmFunction.voidReturn(
      (int args) {
        argsList = args;
      },
      params: [ValueTy.i32],
    );

    final instance =
        (module.builder()..addImport('host', 'hello', hostHello)).buildSync();

    expect(argsList, isNull);
    final hello = instance.getFunction('hello')!;
    hello.inner();
    final result = hello();

    expect(result, isEmpty);
    expect(argsList, 3);
  });

  test('globals', () async {
    final binary = await getBinary(
      wat: r'''
(module
   (global $g (import "js" "global") (mut i32))
   (func (export "getGlobal") (result i32)
        (global.get $g))
   (func (export "incGlobal")
        (global.set $g
            (i32.add (global.get $g) (i32.const 1))))
)''',
      base64Binary:
          'AGFzbQEAAAABCAJgAAF/YAAAAg4BAmpzBmdsb2JhbAN/AQMDAgABBxkCCWdldEdsb2JhbAAACWluY0dsb2JhbAABChACBAAjAAsJACMAQQFqJAALAAsEbmFtZQcEAQABZw==',
    );

    final module = await compileWasmModule(binary);
    expect(
      module.getExports().map((e) => e.toString()),
      [
        const WasmModuleExport('getGlobal', WasmExternalKind.function)
            .toString(),
        const WasmModuleExport('incGlobal', WasmExternalKind.function)
            .toString(),
      ],
    );
    expect(
      module.getImports().map((e) => e.toString()),
      [
        const WasmModuleImport(
          'js',
          'global',
          WasmExternalKind.global,
        ).toString(),
      ],
    );

    final builder = module.builder();
    final global = builder.createGlobal(WasmValue.i32(2), mutable: true);
    expect(global.get(), 2);
    global.set(WasmValue.i32(1));
    expect(global.get(), 1);

    final instance =
        builder.addImports([WasmImport('js', 'global', global)]).buildSync();

    final getGlobal = instance.getFunction('getGlobal')!;
    expect(getGlobal.params, <ValueTy>[]);
    expect(getGlobal.results, isLibrary ? [ValueTy.i32] : null);

    final incGlobal = instance.getFunction('incGlobal')!;
    expect(incGlobal.params, <ValueTy>[]);
    expect(incGlobal.results, isLibrary ? <ValueTy>[] : null);

    expect(getGlobal([]), [1]);
    expect(incGlobal([]), <dynamic>[]);
    expect(getGlobal([]), [2]);
    expect(global.get(), 2);
    global.set(WasmValue.i32(4));
    expect(getGlobal([]), [4]);
    expect(global.get(), 4);
  });

  test('memory utf8 string', () async {
    final binary = await getBinary(
      wat: r'''
(module
(import "console" "logUtf8" (func $log (param i32 i32)))
(import "js" "mem" (memory 1))
(data (i32.const 0) "Hi")
(func (export "writeHi")
  i32.const 0  ;; pass offset 0 to log
  i32.const 2  ;; pass length 2 to log
  call $log))''',
      base64Binary:
          'AGFzbQEAAAABCQJgAn9/AGAAAAIdAgdjb25zb2xlB2xvZ1V0ZjgAAAJqcwNtZW0CAAEDAgEBBwsBB3dyaXRlSGkAAQoKAQgAQQBBAhAACwsIAQBBAAsCSGkADQRuYW1lAQYBAANsb2c=',
    );

    final module = await compileWasmModule(binary);
    expect(
      module.getExports().map((e) => e.toString()),
      [
        const WasmModuleExport('writeHi', WasmExternalKind.function).toString(),
      ],
    );
    expect(
      module.getImports().map((e) => e.toString()),
      [
        const WasmModuleImport(
          'console',
          'logUtf8',
          WasmExternalKind.function,
        ).toString(),
        const WasmModuleImport(
          'js',
          'mem',
          WasmExternalKind.memory,
        ).toString(),
      ],
    );

    final builder = module.builder();
    final memory = builder.createMemory(minPages: 1);
    expect(memory.lengthInBytes, WasmMemory.bytesPerPage);
    expect(memory.lengthInPages, 1);
    expect(memory.view, Uint8List(WasmMemory.bytesPerPage));

    String? result;
    final logUtf8 = WasmFunction.voidReturn(
      (int offset, int length) {
        final bytes = memory.view.sublist(offset, offset + length);
        result = utf8.decode(bytes);
      },
      params: [ValueTy.i32, ValueTy.i32],
    );
    final instance = builder
        .addImports([WasmImport('js', 'mem', memory)])
        .addImport('console', 'logUtf8', logUtf8)
        .buildSync();

    // expect(memory[1], utf8.encode('i').first);
    expect(memory.view[1], utf8.encode('i').first);

    final writeHi = instance.getFunction('writeHi')!;
    expect(writeHi.params, <ValueTy>[]);
    expect(writeHi.results, isLibrary ? <ValueTy>[] : null);

    expect(result, isNull);
    expect(writeHi([]), <dynamic>[]);
    expect(result, 'Hi');

    memory.grow(2);
    expect(memory.lengthInBytes, WasmMemory.bytesPerPage * 3);
    expect(memory.lengthInPages, 3);
    final m = Uint8List(WasmMemory.bytesPerPage * 3);
    m.setRange(0, 2, utf8.encode('Hi'));
    expect(memory.view, m);

    memory.view[1] = utf8.encoder.convert('o')[0];
    // memory[1] = utf8.encode('o').first;
    expect(writeHi([]), <dynamic>[]);
    expect(result, 'Ho');
  });

  test('table func call', () async {
    final binary = await getBinary(
      wat: r'''
(module
  (table 2 funcref)
  (func $f1 (result i32)
    i32.const 42)
  (func $f2 (result i32)
    i32.const 13)
  (elem (i32.const 0) $f1 $f2)
  (type $return_i32 (func (result i32)))
  (func (export "callByIndex") (param $i i32) (result i32)
    local.get $i
    call_indirect (type $return_i32))
)''',
      base64Binary:
          'AGFzbQEAAAABCgJgAAF/YAF/AX8DBAMAAAEEBAFwAAIHDwELY2FsbEJ5SW5kZXgAAgkIAQBBAAsCAAEKEwMEAEEqCwQAQQ0LBwAgABEAAAsAJwRuYW1lAQkCAAJmMQECZjICBgECAQABaQQNAQAKcmV0dXJuX2kzMg==',
    );

    final module = compileWasmModuleSync(binary);

    expect(
      module.getExports().map((e) => e.toString()),
      [
        const WasmModuleExport('callByIndex', WasmExternalKind.function)
            .toString(),
      ],
    );
    expect(
      module.getImports().map((e) => e.toString()),
      <String>[],
    );

    final instance = await module.builder().build();

    final call = instance.getFunction('callByIndex')!;

    expect(call.params, [isLibrary ? ValueTy.i32 : null]);
    expect(call.results, isLibrary ? [ValueTy.i32] : null);
    expect(call([0]), [42]);
    expect(call.inner(1), 13);

    expect(() => call([2]), throwsA(isA<Object>()));
  });

  test('tables import', () async {
    final binary = await getBinary(
      wat: r'''
(module
    (import "js" "tbl" (table 3 funcref))
    (func $f42 (result i32) i32.const 42)
    (func $f83 (result i32) i32.const 83)
    (func $f64p9 (param i64) (result i64) (i64.add (local.get 0) (i64.const 9)))
    (elem (i32.const 0) $f42 $f83 $f64p9)
)
''',
      base64Binary:
          'AGFzbQEAAAABCgJgAAF/YAF+AX4CDAECanMDdGJsAXAAAwMEAwAAAQkJAQBBAAsDAAECChQDBABBKgsFAEHTAAsHACAAQgl8CwAZBG5hbWUBEgMAA2Y0MgEDZjgzAgVmNjRwOQ==',
    );

    final module = compileWasmModuleSync(binary);

    expect(
      module.getExports().map((e) => e.toString()),
      <String>[],
    );
    expect(
      module.getImports().map((e) => e.toString()),
      [
        const WasmModuleImport(
          'js',
          'tbl',
          WasmExternalKind.table,
        ).toString(),
      ],
    );

    final builder = module.builder();
    final table = builder.createTable(
      minSize: 3,
      value: WasmValue.funcRef(null),
    );
    expect(table.length, 3);

    expect(table[0], isNull);

    await builder.addImports([WasmImport('js', 'tbl', table)]).build();

    final f42 = table.get(0)! as WasmFunction;
    expect(f42.inner(), 42);
    expect((table[1]! as WasmFunction)(), [83]);

    final i64p9 = table.get(2)! as WasmFunction;
    expect(
      i64p9([i64.fromBigInt(BigInt.from(5))]),
      [i64.fromInt(5 + 9)],
    );
    expect(
      i64p9.inner(i64.fromInt(208302802)),
      i64.fromBigInt(BigInt.from(208302802 + 9)),
    );

    table[1] = WasmValueRef.funcRef(f42);
    expect((table.get(1)! as WasmFunction)(), [42]);
    if (!isLibrary) {
      // Can's update table with user created functions in web browsers.
      // The can only be set by exports
      return;
    }
    final f43 = WasmFunction(
      () => 43,
      params: [],
      results: [ValueTy.i32],
    );
    final f84 = WasmFunction(
      () => 84.3,
      params: [],
      results: [ValueTy.f64],
    );
    table[0] = WasmValueRef.funcRef(f43);
    table[1] = WasmValueRef.funcRef(f84);

    expect(table[0], isNot(f42));
    expect(table[0], isNot(null));

    expect((table.get(0)! as WasmFunction).inner(), 43);
    expect((table[1]! as WasmFunction)(), [84.3]);

    table.set(
      0,
      WasmValueRef.funcRef(WasmFunction(
        (I64 p) => [-1.4, p],
        params: [ValueTy.i64],
        results: [ValueTy.f64, ValueTy.i64],
      )),
    );

    expect(
      (table.get(0)! as WasmFunction)([i64.fromBigInt(BigInt.from(5))]),
      [-1.4, i64.fromInt(5)],
    );
    // TODO: should we allow this? 5 and BigInt.from(5) are both valid
    expect(
      (table.get(0)! as WasmFunction)([i64.fromInt(5)]),
      [-1.4, i64.fromBigInt(BigInt.from(5))],
    );
  });
  test(
    'loading with WasmFileUris',
    () async {
      final initialUri = WasmFileUris(
        uri: Uri.parse('https://example.com/assets/wasm.wasm'),
        simdUri: Uri.parse('https://example.com/assets/wasm.simd.wasm'),
        threadsSimdUri:
            Uri.parse('https://example.com/assets/wasm.threadsSimd.wasm'),
        fallback: WasmFileUris(
          uri: Uri.parse('https://example.com/assets/wasm.fallback.wasm'),
        ),
      );
      final uris = WasmFileUris.fromList([initialUri]);
      // TODO: save to file? fallback to uri if not found in simd/threads?

      final runtimeFeatures = await wasmRuntimeFeatures();
      try {
        await uris.loadModule(getUriBodyBytes: (uri) async => Uint8List(0));
        throw Exception('should not reach');
      } on WasmFileUrisException catch (e) {
        expect(e.errors, hasLength(2));
        final toString = e.toString();
        expect(
          toString,
          contains(
            'WasmFileUrisException(WasmFileUris(uri: https://example.com/assets/wasm.wasm',
          ),
        );
        expect(
          toString,
          contains('fallbackUri: https://example.com/assets/wasm.wasm'),
        );
        expect(
          toString,
          contains(
            runtimeFeatures.supportedFeatures.threads &&
                    runtimeFeatures.supportedFeatures.simd
                ? 'Url "https://example.com/assets/wasm.threadsSimd.wasm" returned an empty body'
                : runtimeFeatures.supportedFeatures.simd
                    ? 'Url "https://example.com/assets/wasm.simd.wasm" returned an empty body'
                    : 'Url "https://example.com/assets/wasm.wasm" returned an empty body',
          ),
        );
      }

      for (final v in [
        {
          'threads': false,
          'simd': false,
          'uri': initialUri.uri,
        },
        {
          'threads': true,
          'simd': true,
          'uri': initialUri.threadsSimdUri,
        },
        {
          'threads': false,
          'simd': true,
          'uri': initialUri.simdUri,
        },
        {
          'threads': true,
          'simd': false,
          'uri': initialUri.uri,
        },
      ]) {
        final features = WasmFeatures(
          mutableGlobal: true,
          saturatingFloatToInt: true,
          signExtension: true,
          referenceTypes: true,
          multiValue: true,
          bulkMemory: true,
          simd: v['simd']! as bool,
          relaxedSimd: true,
          threads: v['threads']! as bool,
          tailCall: true,
          floats: true,
          multiMemory: true,
          exceptions: true,
          memory64: true,
          extendedConst: true,
          componentModel: true,
          memoryControl: true,
          garbageCollection: true,
          typeReflection: true,
        );

        final uri = uris.uriForFeatures(
          WasmRuntimeFeatures(
            isBrowser: false,
            name: 'wasmtime',
            version: '0.2.1',
            defaultFeatures: features,
            supportedFeatures: features,
          ),
        );
        expect(uri, v['uri']);
      }
    },
  );

  /// WIT Component tests
  typesGenWitComponentTests(
    getWitComponentExampleBytes: testArgs?.getWitComponentExampleBytes,
  );
  typesGenBigIntWitComponentTests(
    getWitComponentExampleBytes: testArgs?.getWitComponentExampleBytes,
  );

  /// WIT Component Generator tests
  witDartGeneratorTests(getDirectory: testArgs?.getDirectory);

  /// WASI tests
  wasiTest(testArgs: testArgs);

  /// Threads tests
  threadsTest(testArgs: testArgs);

  test(
    'setUpDesktopDynamicLibrary',
    testOn: 'windows || mac-os || linux',
    () async {
      if (Platform.isAndroid || Platform.isIOS) return;

      final library = Directory.current.uri
          .resolve('dart_wasm_run_dynamic_library')
          .toFilePath();
      await setUpDesktopDynamicLibrary(dynamicLibraryPath: library);
      addTearDown(() => File(library).deleteSync());
      expect(WasmRunLibrary.isReachable(), true);

      final dynLib = openDynamicLibrary(library);
      expect(
        () => WasmRunLibrary.set(dynLib),
        throwsA(
          predicate(
            (p0) => p0
                .toString()
                .contains('WasmRun bindings were already configured'),
          ),
        ),
      );
      expect(
        () => WasmRunLibrary.setUp(override: true),
        throwsA(
          predicate(
            (p0) => p0
                .toString()
                .contains('WasmRun bindings were already configured'),
          ),
        ),
      );
    },
  );

  test('multi value', () async {
    final binary = await getBinary(
      wat: r'''
(module
  (func $f (import "" "f") (param i32 f32) (result f32 i32))

  (func $g (export "g") (param i32 f32) (result f32 i32)
    (call $f (local.get 0) (local.get 1))
  )

  (func $round_trip_many
    (export "round_trip_many")
    (param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
    (result i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)

    local.get 0
    local.get 1
    local.get 2
    local.get 3
    local.get 4
    local.get 5
    local.get 6
    local.get 7
    local.get 8
    local.get 9)
)''',
      base64Binary:
          'AGFzbQEAAAABHwJgAn99An1/YAp+fn5+fn5+fn5+Cn5+fn5+fn5+fn4CBgEAAWYAAAMDAgABBxcCAWcAAQ9yb3VuZF90cmlwX21hbnkAAgohAggAIAAgARAACxYAIAAgASACIAMgBCAFIAYgByAIIAkLAB8EbmFtZQEYAwABZgEBZwIPcm91bmRfdHJpcF9tYW55',
    );

    final module = compileWasmModuleSync(binary);

    final instance = await module
        .builder()
        .addImport(
          '',
          'f',
          WasmFunction(
            (int a, double b) {
              return [b, a];
            },
            params: [ValueTy.i32, ValueTy.f32],
            results: [ValueTy.f32, ValueTy.i32],
          ),
        )
        .build();

    final g = instance.getFunction('g')!;
    final roundTripMany = instance.getFunction('round_trip_many')!;

    expect(g([42, 409.32000732421875]), [(409.32000732421875), 42]);
    expect(g.inner(42, 3.240000009536743), [3.240000009536743, 42]);

    final params = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i64.fromInt).toList();
    expect(roundTripMany(params), params);
    if (!isLibrary) {
      // TODO: this throws in the browser:
      // NoSuchMethodError: method not found: 'call'
      // Receiver: Instance of 'JavaScriptFunction'
      // Arguments: [Instance of 'BigInt', Instan
      //
      // But you may use roundTripMany.call(params) instead
      expect(Function.apply(roundTripMany.inner, params), params);
    }
    final results = roundTripMany.inner(
      i64.fromInt(1),
      i64.fromInt(2),
      i64.fromInt(3),
      i64.fromInt(4),
      i64.fromInt(5),
      i64.fromInt(6),
      i64.fromInt(7),
      i64.fromInt(8),
      i64.fromInt(9),
      i64.fromInt(10),
    );
    expect(results, params);
  });

  /// SIMD v128 tests
  simdTests();

  test('Reference types', () async {
    final binary = await getBinary(
      wat: r'''
(module
  (table $table (export "table") 10 externref)
  (global $global (export "global") (mut externref) (ref.null extern))
  (func (export "func") (param externref) (result externref)
    local.get 0
  )
)
''',
      base64Binary:
          'AGFzbQEAAAABBgFgAW8BbwMCAQAEBAFvAAoGBgFvAdBvCwcZAwV0YWJsZQEABmdsb2JhbAMABGZ1bmMAAAoGAQQAIAALABoEbmFtZQUIAQAFdGFibGUHCQEABmdsb2JhbA==',
    );
    final module = compileWasmModuleSync(binary);
    final instance = module.builder().buildSync();

    final table = instance.getTable('table')!;
    final global = instance.getGlobal('global')!;
    final func = instance.getFunction('func')!;

    expect(table.length, 10);
    expect(table[0], isNull);
    table[0] = WasmValueRef.externRef(1);
    expect(table[0], 1);

    expect(global.get(), isNull);
    global.set(WasmValue.externRef(2));
    expect(global.get(), 2);

    expect(func.inner('3'), '3');

    final l = ['2'];
    table.set(1, WasmValueRef.externRef(l));
    expect(identical(l, table.get(1)), true);
  });

  /// https://github.com/bytecodealliance/wasmtime/blob/main/examples/fuel.rs
  /// TODO: print rust error in sync execution
  test('fueling instance execution limit', () async {
    final binary0 = await getBinary(
      wat: r'''
(module
  (func $fibonacci (param $n i32) (result i32)
    (if
      (i32.lt_s (local.get $n) (i32.const 2))
      (then (return (local.get $n)))
    )
    (i32.add
      (call $fibonacci (i32.sub (local.get $n) (i32.const 1)))
      (call $fibonacci (i32.sub (local.get $n) (i32.const 2)))
    )
  )
  (export "fibonacci" (func $fibonacci))
)
''',
      base64Binary:
          'AGFzbQEAAAABBgFgAX8BfwMCAQAHDQEJZmlib25hY2NpAAAKHgEcACAAQQJIBEAgAA8LIABBAWsQACAAQQJrEABqCwAbBG5hbWUBDAEACWZpYm9uYWNjaQIGAQABAAFu',
    );

    final module = compileWasmModuleSync(
      binary0,
      config: ModuleConfig(consumeFuel: true),
    );
    final builder = module.builder();

    final runtime = await wasmRuntimeFeatures();

    final WasmInstanceFuel? fuel = builder.fuel();
    if (!isLibrary) {
      expect(fuel, isNull);
      return;
    }
    expect(fuel, isNotNull);
    expect(fuel!.fuelConsumed(), 0);
    expect(fuel.consumeFuel(0), 0);
    expect(fuel.fuelAdded(), 0);
    fuel.addFuel(1);
    expect(fuel.fuelAdded(), 1);
    expect(fuel.consumeFuel(0), 1);
    expect(fuel.fuelConsumed(), 0);
    expect(fuel.consumeFuel(1), 0);
    expect(fuel.fuelConsumed(), 1);

    final instance = await builder.build();
    expect(fuel, instance.fuel());

    fuel.addFuel(10000);
    expect(fuel.fuelAdded(), 10001);

    final fibonacci = instance.getFunction('fibonacci')!;

    final List<int> values = [];
    final List<int> fuelConsumed = [];
    int n = 0;
    while (true) {
      try {
        fuelConsumed.add(fuel.fuelConsumed());
        values.add(fibonacci.inner(n) as int);
        n++;
      } catch (e) {
        expect(e.toString(), contains('fuel'));
        break;
      }
    }
    final isWasmtime = runtime.name == 'wasmtime';
    expect(values, [
      0,
      1,
      1,
      2,
      3,
      5,
      8,
      13,
      21,
      34,
      55,
      // TODO(fueling): try to make fueling similar between runtimes
      if (isWasmtime) 89
    ]);

    final runtimeFuel = isWasmtime
        ? [1, 7, 13, 39, 85, 171, 317, 563, 969, 1635, 2721, 4487, 7353]
        : [1, 19, 37, 88, 172, 322, 571, 985, 1663, 2770, 4570, 7492];
    expect(fuelConsumed, runtimeFuel);
    expect(fuel.fuelAdded(), 10001);
    fuel.addFuel(100);
    expect(fuel.consumeFuel(0), isWasmtime ? 94 : 104);
    expect(fuel.fuelAdded(), 10101);
  });

  print('CONFIGURED ALL TEST IN ${getRunnerIdentity()}');
}

// TODO: more tests
//   test('shared memory', () async {
//     final binary = await getBinary(
//       wat: r'''

// ''',
//       base64Binary: '',
//     );
//   });

// shared table
//   shared0.wat:

// (module
//   (import "js" "memory" (memory 1))
//   (import "js" "table" (table 1 funcref))
//   (elem (i32.const 0) $shared0func)
//   (func $shared0func (result i32)
//    i32.const 0
//    i32.load)
// )
// shared1.wat:

// (module
//   (import "js" "memory" (memory 1))
//   (import "js" "table" (table 1 funcref))
//   (type $void_to_i32 (func (result i32)))
//   (func (export "doIt") (result i32)
//    i32.const 0
//    i32.const 42
//    i32.store  ;; store 42 at address 0
//    i32.const 0
//    call_indirect (type $void_to_i32))
// )

// Shared memories (memory 1 2 shared)

//     test('memories', () async {
//       final binary = await getBinary(
// // https://github.com/bytecodealliance/wasmtime/blob/main/examples/multimemory.wat
//         wat: r'''
// (module
//   (memory (export "memory0") 2 3)
//   (memory (export "memory1") 2 4)

//   (func (export "size0") (result i32) (memory.size 0))
//   (func (export "load0") (param i32) (result i32)
//     local.get 0
//     i32.load8_s 0
//   )
//   (func (export "store0") (param i32 i32)
//     local.get 0
//     local.get 1
//     i32.store8 0
//   )
//   (func (export "size1") (result i32) (memory.size 1))
//   (func (export "load1") (param i32) (result i32)
//     local.get 0
//     i32.load8_s 1
//   )
//   (func (export "store1") (param i32 i32)
//     local.get 0
//     local.get 1
//     i32.store8 1
//   )

//   (data (memory 0) (i32.const 0x1000) "\01\02\03\04")
//   (data (memory 1) (i32.const 0x1000) "\04\03\02\01")
// )
//     ''',
//         base64Binary:
//             'AGFzbQEAAAABCAJgAX8AYAAAAg4BBGhvc3QFaGVsbG8AAAMCAQEHCQEFaGVsbG8AAQoIAQYAQQMQAAsAFARuYW1lAQ0BAApob3N0X2hlbGxv',
//       );
//     });
//   });

// https://github.com/bytecodealliance/wasmtime/blob/main/examples/memory.wat
// (module
//   (memory (export "memory") 2 3)

//   (func (export "size") (result i32) (memory.size))
//   (func (export "load") (param i32) (result i32)
//     (i32.load8_s (local.get 0))
//   )
//   (func (export "store") (param i32 i32)
//     (i32.store8 (local.get 0) (local.get 1))
//   )

//   (data (i32.const 0x1000) "\01\02\03\04")
// )

const endian = Endian.little;

class Parser {
  final Uint8List memView;
  final int initialMemOffset;
  final int viewDelta;
  int memOffset;
  ByteData get byteData => memView.buffer.asByteData();
  bool _isDealloc = false;

  /// Utilities for parsing data from a [memView].
  // TODO(test-improve): improve api, maybe pass WasmMemory
  // TODO(test-improve): improve api, only one method parses and it requires dealloc
  Parser(
    this.memView,
    this.initialMemOffset, {
    this.viewDelta = 0,
  }) : memOffset = initialMemOffset;

  void _dealloc(void Function(int offset, int bytes)? dealloc) {
    if (dealloc != null) {
      if (_isDealloc) throw StateError('Already deallocated');
      _isDealloc = true;
      dealloc(initialMemOffset + viewDelta, memOffset + viewDelta);
    }
  }

  static List<T> parseList<T>(
    Parser p,
    T Function(Parser) parse, {
    void Function(int offset, int bytes)? dealloc,
  }) {
    final length = parseLength(p);
    final result = List.generate(length, (_) => parse(p));

    p._dealloc(dealloc);
    return result;
  }

  static String parseUtf8(
    Parser p, {
    void Function(int offset, int bytes)? dealloc,
  }) {
    final strLength = parseLength(p);
    final bytes = Uint8List.view(p.memView.buffer, p.memOffset, strLength);
    p.memOffset += strLength;
    final str = utf8.decode(bytes);
    p._dealloc(dealloc);
    return str;
  }

  static int parseLength(
    Parser p, {
    void Function(int offset, int bytes)? dealloc,
  }) {
    // Uint32List.view(p.memView.buffer, p.memOffset, 1).first;
    final length = p.byteData.getUint32(p.memOffset, endian);
    p.memOffset += 4;

    p._dealloc(dealloc);
    return length;
  }

  T parse<T>(
    T Function(Parser) fn, {
    void Function(int offset, int bytes)? dealloc,
  }) {
    final result = fn(this);
    _dealloc(dealloc);
    return result;
  }

  bool parseBool() {
    final value = memView[memOffset];
    memOffset += 1;
    return value == 1;
  }

  int parseUint64() {
    final value = i64.getUint64(byteData, memOffset, endian);
    memOffset += 8;
    return i64.toInt(value);
  }

  T? parseNullable<T>(T Function() parse) {
    if (parseBool()) {
      return parse();
    } else {
      return null;
    }
  }
}

class FileData {
  final int size;
  final bool readOnly;
  final int? modified;
  final int? accessed;
  final int? created;

  ///
  FileData({
    required this.size,
    required this.readOnly,
    required this.modified,
    required this.accessed,
    required this.created,
  });

  factory FileData.fromParser(Parser p) {
    final size = p.parseUint64();
    final readOnly = p.parseBool();
    final modified = p.parseNullable(() => p.parseUint64());
    final accessed = p.parseNullable(() => p.parseUint64());
    final created = p.parseNullable(() => p.parseUint64());

    return FileData(
      size: size,
      readOnly: readOnly,
      modified: modified,
      accessed: accessed,
      created: created,
    );
  }

  @override
  String toString() {
    return 'FileData(size: $size, readOnly: $readOnly, modified: $modified,'
        ' accessed: $accessed, created: $created)';
  }
}
6
likes
120
pub points
51%
popularity

Publisher

unverified uploader

Web Assembly executor. Uses Rust's wasmtime optimizing runtime or wasmi interpreter.

Repository (GitHub)
View/report issues

Topics

#wasm #interop #runtime #interpreter #ffi

Documentation

API reference

License

MIT (LICENSE)

Dependencies

collection, ffi, flutter_rust_bridge, freezed_annotation, logging, meta, wasm_interop

More

Packages that depend on wasm_run