From 7d8cd032dcd9d665c2e9a48d32795f04c46ce09b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 15 Jan 2026 03:45:21 -0800 Subject: [PATCH] [dart2wasm] Interfaces for accessing memories This adds the `Memory` class to `dart:_wasm`, allowing Dart code to load and store numeric types in linear memory. Since `dart2wasm` doesn't generate a memory instance by default, there is no singleton instance of `Memory`. Instead, memories are defined as `external` top-level getters annotated with a pragma like `@pragma('wasm:memory-tyype', MemoryType(limits: Limits(1, 10)))` to declare their type. Interop happens in a static way: Methods on `Memory` cannot be torn-off and, since the target memory is encoded directly in the store/load instruction, there's also no polymorphism for memories in Dart. Attempting to call methods on a memory instance that isn't a direct reference to its definition is a compile-time error. Memories can also be imported and exported through the existing `wasm:import` and `wasm:export` pragmas. TEST=tests/web/wasm/memory_test.dart Change-Id: I726f33ac2ec04afab55c5a2b6bc09079d0193e02 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/470020 Commit-Queue: Martin Kustermann Reviewed-by: Slava Egorov Reviewed-by: Martin Kustermann --- pkg/dart2wasm/analysis_options.yaml | 3 + pkg/dart2wasm/lib/dynamic_modules.dart | 1 - pkg/dart2wasm/lib/functions.dart | 67 +++--- pkg/dart2wasm/lib/intrinsics.dart | 183 +++++++++++++++- pkg/dart2wasm/lib/kernel_nodes.dart | 10 +- pkg/dart2wasm/lib/modules.dart | 7 +- pkg/dart2wasm/lib/target.dart | 40 ++-- pkg/dart2wasm/lib/translator.dart | 61 +++++- pkg/dart2wasm/lib/util.dart | 49 +++++ pkg/dart2wasm/lib/wasm_annotations.dart | 54 +++++ pkg/dart2wasm/lib/wasm_library_checks.dart | 172 +++++++++++++++ .../deferred.constant.multi_module_use.wat | 4 +- ...rred.constant.multi_module_use_module1.wat | 6 +- ...rred.constant.multi_module_use_module2.wat | 4 +- ...rred.constant.multi_module_use_module3.wat | 6 +- .../ir_tests/deferred.constant.type_use.wat | 6 +- .../deferred.constant.type_use_module1.wat | 4 +- .../ir_tests/deferred.constant_module1.wat | 32 +-- .../ir_tests/deferred.constant_module2.wat | 62 +++--- .../deferred.fine_grained.devirtualized.wat | 14 +- ...red.fine_grained.devirtualized_module1.wat | 24 +-- .../test/ir_tests/deferred.fine_grained.wat | 18 +- .../deferred.fine_grained_module1.wat | 36 ++-- .../deferred.fine_grained_module2.wat | 18 +- .../deferred.fine_grained_module3.wat | 18 +- .../deferred.fine_grained_module4.wat | 18 +- .../deferred.fine_grained_module5.wat | 18 +- pkg/dart2wasm/test/ir_tests/dyn_closure.wat | 24 +-- .../ir_tests/dyn_closure_function_apply.wat | 24 +-- .../dyn_closure_function_apply_named.wat | 26 +-- pkg/dart2wasm/test/ir_tests/hello.wat | 4 +- .../test/ir_tests/import_name_module1.wat | 4 +- pkg/dart2wasm/test/ir_tests/interop.int.wat | 6 +- pkg/dart2wasm/test/ir_tests/memory_use.dart | 33 +++ pkg/dart2wasm/test/ir_tests/memory_use.wat | 48 +++++ .../lib/src/codes/cfe_codes_generated.dart | 29 +++ pkg/front_end/messages.status | 4 + pkg/front_end/messages.yaml | 17 ++ .../test/spell_checking_list_messages.txt | 3 + .../lib/src/builder/instructions.dart | 8 + pkg/wasm_builder/lib/src/ir/instruction.dart | 30 +++ pkg/wasm_builder/lib/src/ir/memory.dart | 47 ++++- pkg/wasm_builder/lib/src/ir/module.dart | 4 + .../lib/src/serialize/printer.dart | 61 +++++- sdk/lib/_wasm/memory.dart | 196 ++++++++++++++++++ sdk/lib/_wasm/wasm_sources.gni | 5 +- sdk/lib/_wasm/wasm_types.dart | 2 + tests/web/wasm/deferred_memory_test.dart | 30 +++ tests/web/wasm/externs_assert_error_test.dart | 45 ++++ tests/web/wasm/memory_test.dart | 60 ++++++ 50 files changed, 1365 insertions(+), 280 deletions(-) create mode 100644 pkg/dart2wasm/lib/wasm_annotations.dart create mode 100644 pkg/dart2wasm/lib/wasm_library_checks.dart create mode 100644 pkg/dart2wasm/test/ir_tests/memory_use.dart create mode 100644 pkg/dart2wasm/test/ir_tests/memory_use.wat create mode 100644 sdk/lib/_wasm/memory.dart create mode 100644 tests/web/wasm/deferred_memory_test.dart create mode 100644 tests/web/wasm/externs_assert_error_test.dart create mode 100644 tests/web/wasm/memory_test.dart diff --git a/pkg/dart2wasm/analysis_options.yaml b/pkg/dart2wasm/analysis_options.yaml index b2285e5fe11..080182cc63d 100644 --- a/pkg/dart2wasm/analysis_options.yaml +++ b/pkg/dart2wasm/analysis_options.yaml @@ -7,6 +7,9 @@ analyzer: implementation_imports: ignore library_prefixes: ignore + exclude: + - test/ir_tests/*.dart + linter: rules: - directives_ordering diff --git a/pkg/dart2wasm/lib/dynamic_modules.dart b/pkg/dart2wasm/lib/dynamic_modules.dart index 49cfcac535d..99c28b9442d 100644 --- a/pkg/dart2wasm/lib/dynamic_modules.dart +++ b/pkg/dart2wasm/lib/dynamic_modules.dart @@ -110,7 +110,6 @@ class DynamicSubmoduleOutputData extends ModuleOutputData { } class DynamicMainModuleStrategy extends ModuleStrategy with KernelNodes { - @override final Component component; @override final CoreTypes coreTypes; diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index c53d80de576..768ab79c169 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -12,6 +12,7 @@ import 'dispatch_table.dart'; import 'dynamic_modules.dart'; import 'reference_extensions.dart'; import 'translator.dart'; +import 'util.dart' as util; /// This class is responsible for collecting import and export annotations. /// It also creates Wasm functions for Dart members and manages the compilation @@ -49,33 +50,26 @@ class FunctionCollector { } void _importOrExport(Procedure member) { - String? importName = - translator.getPragma(member, "wasm:import", member.name.text); + final importName = util.getWasmImportPragma(translator.coreTypes, member); if (importName != null) { - int dot = importName.indexOf('.'); - if (dot != -1) { - assert(!member.isInstanceMember); - String module = importName.substring(0, dot); - String name = importName.substring(dot + 1); - final ftype = _makeFunctionType(translator, member.reference, null, - isImportOrExport: true); - _functions[member.reference] = translator - .moduleForReference(member.reference) - .functions - .import(module, name, ftype, "$importName (import)"); - } + final ftype = _makeFunctionType(translator, member.reference, null, + isImportOrExport: true); + _functions[member.reference] = translator + .moduleForReference(member.reference) + .functions + .import(importName.moduleName, importName.itemName, ftype, + "$importName (import)"); } // Ensure any procedures marked as exported are enqueued. - final text = member.name.text; - String? exportName = translator.getPragma(member, "wasm:export", text); + String? exportName = util.getWasmExportPragma(translator.coreTypes, member); if (exportName != null) { getFunction(member.reference); } // Whether a procedure is strongly or weakly exported, we must not use its // name as the export name of a different function. - exportName ??= translator.getPragma(member, "wasm:weak-export", text); + exportName ??= util.getWasmWeakExportPragma(translator.coreTypes, member); if (exportName != null) { translator.exporter.reserveName(exportName); } @@ -83,15 +77,7 @@ class FunctionCollector { /// If the member with the reference [target] is exported, get the export /// name. - String? getExportName(Reference target) { - final member = target.asMember; - if (member.reference == target) { - final text = member.name.text; - return translator.getPragma(member, "wasm:export", text) ?? - translator.getPragma(member, "wasm:weak-export", text); - } - return null; - } + String? getExportName(Reference target) => translator.getExportName(target); void initialize() { _collectImportsAndExports(); @@ -113,24 +99,20 @@ class FunctionCollector { return _functions.putIfAbsent(target, () { final member = target.asMember; - // If this function is a `@pragma('wasm:import', ':')` we + // If this function is a `@pragma('wasm:import', '.')` we // import the function and return it. if (member.reference == target && member.annotations.isNotEmpty) { final importName = - translator.getPragma(member, 'wasm:import', member.name.text); + util.getWasmImportPragma(translator.coreTypes, member); + if (importName != null) { - assert(!member.isInstanceMember); - int dot = importName.indexOf('.'); - if (dot != -1) { - final module = importName.substring(0, dot); - final name = importName.substring(dot + 1); - final ftype = _makeFunctionType(translator, member.reference, null, - isImportOrExport: true); - return _functions[member.reference] = translator - .moduleForReference(member.reference) - .functions - .import(module, name, ftype, "$importName (import)"); - } + final ftype = _makeFunctionType(translator, member.reference, null, + isImportOrExport: true); + return _functions[member.reference] = translator + .moduleForReference(member.reference) + .functions + .import(importName.moduleName, importName.itemName, ftype, + "$importName (import)"); } } @@ -145,9 +127,8 @@ class FunctionCollector { // we export it under the given `` String? exportName; if (member.reference == target && member.annotations.isNotEmpty) { - exportName = translator.getPragma( - member, 'wasm:export', member.name.text) ?? - translator.getPragma(member, 'wasm:weak-export', member.name.text); + exportName = util.getWasmExportPragma(translator.coreTypes, member) ?? + util.getWasmWeakExportPragma(translator.coreTypes, member); assert(exportName == null || member is Procedure && member.isStatic); } diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 2b727f416dd..9165922a470 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -194,7 +194,30 @@ enum StaticIntrinsic { wasmI31RefExtensionsExternalize( 'dart:_wasm', null, 'WasmI31RefExtensions|externalize'), wasmI31RefExtensionsGetS('dart:_wasm', null, 'WasmI31RefExtensions|get_s'), - wasmI31RefExtensionsGetU('dart:_wasm', null, 'WasmI31RefExtensions|get_u'); + wasmI31RefExtensionsGetU('dart:_wasm', null, 'WasmI31RefExtensions|get_u'), + wasmMemorySize('dart:_wasm', null, 'MemoryAccessExtension|get#size'), + wasmMemoryGrow('dart:_wasm', null, 'MemoryAccessExtension|grow'), + wasmMemoryFill('dart:_wasm', null, 'MemoryAccessExtension|fill'), + wasmMemoryLoadFloat32( + 'dart:_wasm', null, 'MemoryAccessExtension|loadFloat32'), + wasmMemoryLoadFloat64( + 'dart:_wasm', null, 'MemoryAccessExtension|loadFloat64'), + wasmMemoryLoadInt8('dart:_wasm', null, 'MemoryAccessExtension|loadInt8'), + wasmMemoryLoadInt16('dart:_wasm', null, 'MemoryAccessExtension|loadInt16'), + wasmMemoryLoadInt32('dart:_wasm', null, 'MemoryAccessExtension|loadInt32'), + wasmMemoryLoadInt64('dart:_wasm', null, 'MemoryAccessExtension|loadInt64'), + wasmMemoryLoadUint8('dart:_wasm', null, 'MemoryAccessExtension|loadUint8'), + wasmMemoryLoadUint16('dart:_wasm', null, 'MemoryAccessExtension|loadUint16'), + wasmMemoryLoadUint32('dart:_wasm', null, 'MemoryAccessExtension|loadUint32'), + wasmMemoryStoreFloat32( + 'dart:_wasm', null, 'MemoryAccessExtension|storeFloat32'), + wasmMemoryStoreFloat64( + 'dart:_wasm', null, 'MemoryAccessExtension|storeFloat64'), + wasmMemoryStoreInt8('dart:_wasm', null, 'MemoryAccessExtension|storeInt8'), + wasmMemoryStoreInt16('dart:_wasm', null, 'MemoryAccessExtension|storeInt16'), + wasmMemoryStoreInt32('dart:_wasm', null, 'MemoryAccessExtension|storeInt32'), + wasmMemoryStoreInt64('dart:_wasm', null, 'MemoryAccessExtension|storeInt64'), + ; final String library; final String? cls; @@ -307,7 +330,7 @@ class Intrinsifier { _inlineUnaryOperatorMap = { intType: { 'unary-': (c, receiver) { - final int? intValue = _extractIntValue(receiver); + final int? intValue = extractIntValue(receiver); if (intValue == null) { c.translateExpression(receiver, intType); c.b.i64_const(-1); @@ -317,7 +340,7 @@ class Intrinsifier { } }, '~': (c, receiver) { - final int? intValue = _extractIntValue(receiver); + final int? intValue = extractIntValue(receiver); if (intValue == null) { c.translateExpression(receiver, intType); c.b.i64_const(-1); @@ -327,7 +350,7 @@ class Intrinsifier { } }, 'toDouble': (c, receiver) { - final int? intValue = _extractIntValue(receiver); + final int? intValue = extractIntValue(receiver); if (intValue == null) { c.translateExpression(receiver, intType); c.b.f64_convert_i64_s(); @@ -1538,6 +1561,125 @@ class Intrinsifier { codeGen.translateExpression(value, w.RefType.i31(nullable: false)); b.i31_get_u(); return w.NumType.i32; + + case StaticIntrinsic.wasmMemorySize: + final memory = _extractMemoryFromCall(node, b); + b.memory_size(memory); + // Unsigned because memory sizes can't be negative. + b.i64_extend_i32_u(); + return w.NumType.i64; + case StaticIntrinsic.wasmMemoryGrow: + final memory = _extractMemoryFromCall(node, b); + codeGen.translateExpression( + node.arguments.positional[1], w.NumType.i64); + b.i32_wrap_i64(); + b.memory_grow(memory); + // Signed because memory.grow returns -1 on failure. + b.i64_extend_i32_s(); + return w.NumType.i64; + case StaticIntrinsic.wasmMemoryFill: + final memory = _extractMemoryFromCall(node, b); + // The positional arguments in Dart are value, startOffset, length. The + // stack for memory_fill needs to be [..., start, value, length] though. + final [_, value, start, length] = node.arguments.positional; + codeGen.translateExpression(value, w.NumType.i32); + codeGen.translateExpression(start, w.NumType.i64); + b.i32_wrap_i64(); + final startVar = b.addLocal(w.NumType.i32); + final valueVar = b.addLocal(w.NumType.i32); + + b + ..local_set(startVar) + ..local_set(valueVar) + ..local_get(startVar) + ..local_get(valueVar); + codeGen.translateExpression(length, w.NumType.i64); + b.i32_wrap_i64(); + b.memory_fill(memory); + return codeGen.voidMarker; + case StaticIntrinsic.wasmMemoryLoadFloat32: + case StaticIntrinsic.wasmMemoryLoadFloat64: + case StaticIntrinsic.wasmMemoryLoadInt8: + case StaticIntrinsic.wasmMemoryLoadInt16: + case StaticIntrinsic.wasmMemoryLoadInt32: + case StaticIntrinsic.wasmMemoryLoadInt64: + case StaticIntrinsic.wasmMemoryLoadUint8: + case StaticIntrinsic.wasmMemoryLoadUint16: + case StaticIntrinsic.wasmMemoryLoadUint32: + final (:memory, :align, :offset) = _extractMemoryOperands(node, b); + + codeGen.translateExpression( + node.arguments.positional[1], w.NumType.i64); + b.i32_wrap_i64(); + + switch (intrinsic) { + case StaticIntrinsic.wasmMemoryLoadFloat32: + b.f32_load(memory, offset, align); + return w.NumType.f32; + case StaticIntrinsic.wasmMemoryLoadFloat64: + b.f64_load(memory, offset, align); + return w.NumType.f64; + case StaticIntrinsic.wasmMemoryLoadInt8: + b.i32_load8_s(memory, offset, align); + return w.NumType.i32; + case StaticIntrinsic.wasmMemoryLoadInt16: + b.i32_load16_s(memory, offset, align); + return w.NumType.i32; + case StaticIntrinsic.wasmMemoryLoadInt32: + b.i32_load(memory, offset, align); + return w.NumType.i32; + case StaticIntrinsic.wasmMemoryLoadInt64: + b.i64_load(memory, offset, align); + return w.NumType.i64; + case StaticIntrinsic.wasmMemoryLoadUint8: + b.i32_load8_u(memory, offset, align); + return w.NumType.i32; + case StaticIntrinsic.wasmMemoryLoadUint16: + b.i32_load16_u(memory, offset, align); + return w.NumType.i32; + case StaticIntrinsic.wasmMemoryLoadUint32: + b.i32_load(memory, offset, align); + return w.NumType.i32; + default: + throw AssertionError('unreachable'); + } + case StaticIntrinsic.wasmMemoryStoreFloat32: + case StaticIntrinsic.wasmMemoryStoreFloat64: + case StaticIntrinsic.wasmMemoryStoreInt8: + case StaticIntrinsic.wasmMemoryStoreInt16: + case StaticIntrinsic.wasmMemoryStoreInt32: + case StaticIntrinsic.wasmMemoryStoreInt64: + final (:memory, :align, :offset) = _extractMemoryOperands(node, b); + + codeGen.translateExpression( + node.arguments.positional[1], w.NumType.i64); + b.i32_wrap_i64(); + final valueExpression = node.arguments.positional[2]; + + switch (intrinsic) { + case StaticIntrinsic.wasmMemoryStoreFloat32: + codeGen.translateExpression(valueExpression, w.NumType.f32); + b.f32_store(memory, offset, align); + case StaticIntrinsic.wasmMemoryStoreFloat64: + codeGen.translateExpression(valueExpression, w.NumType.f64); + b.f64_store(memory, offset, align); + case StaticIntrinsic.wasmMemoryStoreInt8: + codeGen.translateExpression(valueExpression, w.NumType.i32); + b.i32_store8(memory, offset, align); + case StaticIntrinsic.wasmMemoryStoreInt16: + codeGen.translateExpression(valueExpression, w.NumType.i32); + b.i32_store16(memory, offset, align); + case StaticIntrinsic.wasmMemoryStoreInt32: + codeGen.translateExpression(valueExpression, w.NumType.i32); + b.i32_store(memory, offset, align); + case StaticIntrinsic.wasmMemoryStoreInt64: + codeGen.translateExpression(valueExpression, w.NumType.i64); + b.i64_store(memory, offset, align); + default: + throw AssertionError('unreachable'); + } + + return codeGen.voidMarker; } } @@ -2155,9 +2297,40 @@ class Intrinsifier { } return wasmType.unpacked; } + + /// Extracts the memory instance for an intrinsic call on a memory extension. + w.Memory _extractMemoryFromCall( + Expression expr, w.InstructionsBuilder builder) { + // All validated memory calls look like MemoryAccessExtension|size(memory) + final memory = + (expr as StaticInvocation).arguments.positional[0] as StaticGet; + return translator.findMemory( + memory.target as Procedure, builder.moduleBuilder); + } + + ({w.Memory memory, int offset, int align}) _extractMemoryOperands( + StaticInvocation call, w.InstructionsBuilder builder) { + final memory = _extractMemoryFromCall(call, builder); + var align = 0; + var offset = 0; + + for (final NamedExpression(:name, :value) in call.arguments.named) { + // align and offset are verified to be compile-time constants in + // wasm_library_checks.dart + if (name == 'align') { + align = extractIntValue(value)!; + } else if (name == 'offset') { + offset = extractIntValue(value)!; + } else { + throw UnsupportedError('Unhandled named argument: $name'); + } + } + + return (memory: memory, align: align, offset: offset); + } } -int? _extractIntValue(Expression expr) { +int? extractIntValue(Expression expr) { if (expr is IntLiteral) { return expr.value; } diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index e8135e9671a..a568206bb46 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -9,8 +9,6 @@ import 'package:kernel/library_index.dart'; /// Kernel nodes for classes and members referenced specifically by the /// compiler. mixin KernelNodes { - Component get component; - LibraryIndex get index; CoreTypes get coreTypes; @@ -202,6 +200,14 @@ mixin KernelNodes { late final Field wasmI64ValueField = index.getField("dart:_wasm", "WasmI64", "_value"); + late final Class wasmMemoryClass = index.getClass('dart:_wasm', 'Memory'); + late final Class wasmMemoryTypeClass = + index.getClass('dart:_wasm', 'MemoryType'); + late final Field wasmLimitsMinimum = + index.getField('dart:_wasm', 'Limits', 'minimum'); + late final Field wasmLimitsMaximum = + index.getField('dart:_wasm', 'Limits', 'maximum'); + // dart:_js_helper procedures late final Procedure getInternalizedString = index.getTopLevelProcedure("dart:_js_helper", "getInternalizedString"); diff --git a/pkg/dart2wasm/lib/modules.dart b/pkg/dart2wasm/lib/modules.dart index c753176116a..64d3a8920d6 100644 --- a/pkg/dart2wasm/lib/modules.dart +++ b/pkg/dart2wasm/lib/modules.dart @@ -170,15 +170,12 @@ class DefaultModuleStrategy extends ModuleStrategy { DeferredModuleLoadingMap loadingMap) async {} } -bool _hasWasmExportPragma(CoreTypes coreTypes, Member m) => - hasPragma(coreTypes, m, 'wasm:export'); - bool containsWasmExport(CoreTypes coreTypes, Library lib) { - if (lib.members.any((m) => _hasWasmExportPragma(coreTypes, m))) { + if (lib.members.any((m) => hasWasmExportPragma(coreTypes, m))) { return true; } return lib.classes - .any((c) => c.members.any((m) => _hasWasmExportPragma(coreTypes, m))); + .any((c) => c.members.any((m) => hasWasmExportPragma(coreTypes, m))); } abstract class ModuleStrategy { diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart index 24cca0e3d57..da804fae028 100644 --- a/pkg/dart2wasm/lib/target.dart +++ b/pkg/dart2wasm/lib/target.dart @@ -36,6 +36,8 @@ import 'ffi_native_address_transformer.dart' as wasmFfiNativeAddressTrans; import 'ffi_native_transformer.dart' as wasmFfiNativeTrans; import 'records.dart' show RecordShape; import 'transformers.dart' as wasmTrans; +import 'util.dart' as util; +import 'wasm_library_checks.dart' as wasmChecks; enum Mode { regular, @@ -365,6 +367,8 @@ class WasmTarget extends Target { } wasmTrans.transformLibraries(libraries, coreTypes, hierarchy); + wasmChecks.checkDartWasmApiUseIfImported( + libraries, coreTypes, diagnosticReporter); awaitTrans.transformLibraries(libraries, hierarchy, coreTypes); } @@ -552,8 +556,6 @@ class WasmVerification extends Verification { } } -final _dartCoreUri = Uri.parse('dart:core'); - /// Check that `wasm:import` and `wasm:export` pragmas are only used in `dart:` /// libraries and in tests, with the exception of /// `reject_import_export_pragmas` test. @@ -569,31 +571,15 @@ void _checkWasmImportExportPragmas(List libraries, CoreTypes coreTypes, } for (Member member in library.members) { - for (Expression annotation in member.annotations) { - if (annotation is! ConstantExpression) { - continue; - } - final annotationConstant = annotation.constant; - if (annotationConstant is! InstanceConstant) { - continue; - } - final cls = annotationConstant.classNode; - if (cls.name == 'pragma' && - cls.enclosingLibrary.importUri == _dartCoreUri) { - final pragmaName = annotationConstant - .fieldValues[coreTypes.pragmaName.fieldReference]; - if (pragmaName is StringConstant) { - if (pragmaName.value == 'wasm:import' || - pragmaName.value == 'wasm:export') { - diagnosticReporter.report( - codeWasmImportOrExportInUserCode, - annotation.fileOffset, - 0, - library.fileUri, - ); - } - } - } + if (util.hasWasmImportPragma(coreTypes, member) || + util.hasWasmExportPragma(coreTypes, member) || + util.hasWasmWeakExportPragma(coreTypes, member)) { + diagnosticReporter.report( + codeWasmImportOrExportInUserCode, + member.fileOffset, + 0, + library.fileUri, + ); } } } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 1ad77076255..20819caf0df 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -40,6 +40,7 @@ import 'symbols.dart'; import 'tags.dart'; import 'types.dart'; import 'util.dart' as util; +import 'wasm_annotations.dart'; /// Options controlling the translation. class TranslatorOptions { @@ -149,7 +150,6 @@ class Translator with KernelNodes { late final Exporter exporter; // Kernel input and context. - @override final Component component; final List libraries; @override @@ -249,6 +249,7 @@ class Translator with KernelNodes { // Lazily import FFI memory if used. late final w.Memory ffiMemory = mainModule.memories.import("ffi", "memory", options.importSharedMemory, 0, options.sharedMemoryMaxPages); + final Map _memories = {}; /// Maps record shapes to the record class for the shape. Classes generated /// by `record_class_generator` library. @@ -762,6 +763,8 @@ class Translator with KernelNodes { late final WasmFunctionImporter _importedFunctions = WasmFunctionImporter(this, 'func'); + late final WasmMemoryImporter _importedMemories = + WasmMemoryImporter(this, 'memory'); /// Generates a set of instructions to call [function] adding indirection /// if the call crosses a module boundary. Calls the function directly if it @@ -2128,6 +2131,51 @@ class Translator with KernelNodes { _internalizedStringGlobals[(module, s)] = internalizedString; return internalizedString; } + + w.Memory findMemory( + Procedure topLevelExternalMemoryGetter, w.ModuleBuilder moduleBuilder) { + final inMain = _findMemoryForMainModule(topLevelExternalMemoryGetter); + if (moduleBuilder == mainModule) { + return inMain; + } + + return _importedMemories.get(inMain, moduleBuilder); + } + + w.Memory _findMemoryForMainModule(Procedure topLevelExternalMemoryGetter) { + return _memories.putIfAbsent(topLevelExternalMemoryGetter, () { + final limits = + MemoryLimits.readAnnotation(this, topLevelExternalMemoryGetter)!; + final exportName = getExportName(topLevelExternalMemoryGetter.reference); + final import = + util.getWasmImportPragma(coreTypes, topLevelExternalMemoryGetter); + + w.Memory memory; + if (import != null) { + memory = mainModule.memories.import(import.moduleName, import.itemName, + false, limits.minSize, limits.maxSize); + } else { + memory = + mainModule.memories.define(false, limits.minSize, limits.maxSize); + } + + if (exportName != null) { + mainModule.exports.export(exportName, memory); + } + return memory; + }); + } + + /// If the member with the reference [target] is exported, get the export + /// name. + String? getExportName(Reference target) { + final member = target.asMember; + if (member.reference == target) { + return util.getWasmExportPragma(coreTypes, member) ?? + util.getWasmWeakExportPragma(coreTypes, member); + } + return null; + } } class CompilationQueue { @@ -3290,6 +3338,17 @@ class WasmGlobalImporter extends _WasmImporter { } } +class WasmMemoryImporter extends _WasmImporter { + WasmMemoryImporter(super._translator, super._exportPrefix); + + @override + w.Memory _import(w.ModuleBuilder importingModule, w.Memory definition, + String moduleName, String importName) { + return importingModule.memories.import(moduleName, importName, + definition.shared, definition.minSize, definition.maxSize); + } +} + class WasmTableImporter extends _WasmImporter { WasmTableImporter(super._translator, super._exportPrefix); diff --git a/pkg/dart2wasm/lib/util.dart b/pkg/dart2wasm/lib/util.dart index 555ab6a1984..d87c9c2da0c 100644 --- a/pkg/dart2wasm/lib/util.dart +++ b/pkg/dart2wasm/lib/util.dart @@ -59,6 +59,55 @@ T? getPragma(CoreTypes coreTypes, Annotatable node, String name, return null; } +bool hasWasmImportPragma(CoreTypes coreTypes, Member member) { + return hasPragma(coreTypes, member, "wasm:import"); +} + +ImportName? getWasmImportPragma(CoreTypes coreTypes, Member member) { + String? importName = getPragma(coreTypes, member, "wasm:import"); + if (importName != null) { + int dot = importName.indexOf('.'); + if (dot != -1) { + assert(!member.isInstanceMember); + String module = importName.substring(0, dot); + String name = importName.substring(dot + 1); + return ImportName(module, name); + } + } + + return null; +} + +final class ImportName { + final String moduleName; + final String itemName; + + ImportName(this.moduleName, this.itemName); + + @override + String toString() { + return '$moduleName.$itemName'; + } +} + +bool hasWasmExportPragma(CoreTypes coreTypes, Member member) { + return hasPragma(coreTypes, member, "wasm:export"); +} + +bool hasWasmWeakExportPragma(CoreTypes coreTypes, Member member) { + return hasPragma(coreTypes, member, "wasm:weak-export"); +} + +String? getWasmExportPragma(CoreTypes coreTypes, Member member) { + return getPragma(coreTypes, member, 'wasm:export', + defaultValue: member.name.text); +} + +String? getWasmWeakExportPragma(CoreTypes coreTypes, Member member) { + return getPragma(coreTypes, member, 'wasm:weak-export', + defaultValue: member.name.text); +} + /// Add a `@pragma('wasm:entry-point')` annotation to an annotatable. T addWasmEntryPointPragma(T node, CoreTypes coreTypes) => addPragma(node, 'wasm:entry-point', coreTypes); diff --git a/pkg/dart2wasm/lib/wasm_annotations.dart b/pkg/dart2wasm/lib/wasm_annotations.dart new file mode 100644 index 00000000000..bd3cba1795a --- /dev/null +++ b/pkg/dart2wasm/lib/wasm_annotations.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2026, 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 'package:kernel/ast.dart'; + +import 'kernel_nodes.dart'; +import 'util.dart' as util; + +enum ExternType { memory } + +final class MemoryLimits { + final int minSize; + final int? maxSize; + + MemoryLimits({required this.minSize, this.maxSize}); + + /// Read the `MemoryType` annotation on a member. + static MemoryLimits? readAnnotation(KernelNodes nodes, Member member) { + final memoryType = util.getPragma( + nodes.coreTypes, member, 'wasm:memory-type'); + if (memoryType == null || + memoryType.classNode != nodes.wasmMemoryTypeClass) { + return null; + } + + final (minSize, maxSize) = _readMemoryType(nodes, memoryType); + + return MemoryLimits( + minSize: minSize, + maxSize: maxSize, + ); + } + + static (int, int?) _readMemoryType( + KernelNodes nodes, InstanceConstant constant) { + final limits = constant.fieldValues.values.single; + return _readLimits(nodes, limits as InstanceConstant); + } + + static (int, int?) _readLimits(KernelNodes nodes, InstanceConstant constant) { + final minimum = (constant + .fieldValues[nodes.wasmLimitsMinimum.fieldReference] as IntConstant) + .value; + final maximumConstant = + constant.fieldValues[nodes.wasmLimitsMaximum.fieldReference]; + final maximum = switch (maximumConstant) { + IntConstant(:final value) => value, + _ => null, + }; + + return (minimum, maximum); + } +} diff --git a/pkg/dart2wasm/lib/wasm_library_checks.dart b/pkg/dart2wasm/lib/wasm_library_checks.dart new file mode 100644 index 00000000000..2c771f0a5ed --- /dev/null +++ b/pkg/dart2wasm/lib/wasm_library_checks.dart @@ -0,0 +1,172 @@ +// Copyright (c) 2026, 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 'package:front_end/src/codes/cfe_codes.dart' + show + codeConstEvalNonConstantVariableGet, + codeWasmExternMemoryMissingAnnotation, + codeWasmExternInvalidLoad, + codeWasmExternInvalidTarget, + codeWasmIntrinsicTearOff; +import 'package:kernel/ast.dart'; +import 'package:kernel/core_types.dart'; +import 'package:kernel/library_index.dart'; +import 'package:kernel/target/targets.dart'; + +import 'intrinsics.dart'; +import 'kernel_nodes.dart'; +import 'wasm_annotations.dart'; + +/// Validates (a subset of) `dart:_wasm` usages. +/// +/// So far, we validate usages of: +/// * `Memory` and `MemoryAccessExtension`. +void checkDartWasmApiUseIfImported( + Iterable libraries, + CoreTypes coreTypes, + DiagnosticReporter diagnosticReporter, +) { + final checks = _DartWasmLibraryChecks(coreTypes, diagnosticReporter); + for (final library in libraries) { + // Skip the check if the library doesn't import dart:_wasm. + // TODO: This misses libraries importing dart:_wasm through an export. + for (final dependency in library.dependencies) { + if (!dependency.isImport) continue; + if (dependency.targetLibrary == checks.wasmLibrary) { + library.accept(checks); + continue; + } + } + } +} + +class _DartWasmLibraryChecks extends RecursiveVisitor with KernelNodes { + Member? _currentMember; + + final DiagnosticReporter _diagnosticReporter; + + @override + final CoreTypes coreTypes; + + @override + LibraryIndex get index => coreTypes.index; + + _DartWasmLibraryChecks(this.coreTypes, this._diagnosticReporter); + + @override + void visitLibrary(Library library) { + if (library == wasmLibrary) { + // The CFE generates getters to tear off extension methods, which look + // like illegal dynamic invocations to this visitor. We verify that + // tearoffs aren't used, but don't visit the source library to avoid + // false-positives here. + return; + } + + library.visitChildren(this); + } + + @override + void defaultMember(Member node) { + _currentMember = node; + node.visitChildren(this); + } + + @override + void visitProcedure(Procedure node) { + _currentMember = node; + + if (_categorizeWasmExtern(node) == ExternType.memory) { + final parsed = MemoryLimits.readAnnotation(this, node); + if (parsed == null) { + _diagnosticReporter.report(codeWasmExternMemoryMissingAnnotation, + node.fileOffset, 1, node.fileUri); + } + } + + node.visitChildren(this); + } + + @override + void visitStaticInvocation(StaticInvocation node) { + final target = node.target; + if (target.enclosingLibrary == wasmLibrary && + target.name.text.startsWith('MemoryAccessExtension|')) { + final args = node.arguments; + final memory = args.positional[0]; + final isTearOff = target.function.returnType is FunctionType; + + if (isTearOff) { + // Reference to a getter generated to implement tear offs, e.g. in + // memory.fill (as opposed to a direct memory.fill(a, b, c) call). + _diagnosticReporter.report(codeWasmIntrinsicTearOff, node.fileOffset, 1, + _currentMember!.fileUri); + } + + if (_isWasmMemoryRef(memory)) { + for (final positional in args.positional.skip(1)) { + positional.accept(this); + } + + for (final named in args.named) { + named.accept(this); + + // The parameter to the align and offset method should be a compile- + // time constant. + if (named.name case 'align' || 'offset') { + if (extractIntValue(named.value) == null) { + _diagnosticReporter.report( + codeConstEvalNonConstantVariableGet.withArguments( + nameOKEmpty: named.name), + named.value.fileOffset, + 1, + _currentMember!.fileUri); + } + } + } + + return; + } else { + _diagnosticReporter.report(codeWasmExternInvalidTarget, node.fileOffset, + 0, _currentMember!.fileUri); + } + } + + super.visitStaticInvocation(node); + } + + @override + void visitStaticGet(StaticGet node) { + if (_isWasmMemoryRef(node)) { + // The only valid use of a wasm element is to call an intrinsic extension + // method on it, in which case an outer visit method would have skipped + // this node. This get is invalid. + _diagnosticReporter.report(codeWasmExternInvalidLoad, node.fileOffset, 1, + _currentMember!.fileUri); + } + + super.visitStaticGet(node); + } + + /// Checks whether the getter defines an external WebAssembly member that can + /// only be used through intrinsics. + ExternType? _categorizeWasmExtern(Member getter) { + if (getter is Procedure && getter.isExternal) { + final type = getter.function.returnType; + + if (type is InterfaceType) { + if (type.classNode == wasmMemoryClass) { + return ExternType.memory; + } + } + } + + return null; + } + + bool _isWasmMemoryRef(Expression expr) { + return expr is StaticGet && + _categorizeWasmExtern(expr.target) == ExternType.memory; + } +} diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat index 349b940f56a..35d0d9fbf44 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use.wat @@ -19,7 +19,7 @@ (result (ref $MyConstClass)))) (table $static0-0 (export "static0-0") 2 (ref null $type0)) (table $static1-0 (export "static1-0") 1 (ref null $type2)) - (global $"C381 \"bad\"" (ref $JSStringImpl) <...>) + (global $"C383 \"bad\"" (ref $JSStringImpl) <...>) (func $"mainImpl " (param $var0 i32) (local $var1 (ref $MyConstClass)) i64.const 0 @@ -35,7 +35,7 @@ ref.eq i32.eqz if - global.get $"C381 \"bad\"" + global.get $"C383 \"bad\"" call $Error._throwWithCurrentStackTrace unreachable end diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat index a3c1ffb27d0..830418166e4 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module1.wat @@ -16,8 +16,8 @@ (result (ref $MyConstClass)))) (global $.h0-nonshared-const (import "" "h0-nonshared-const") (ref extern)) (table $module0.static1-0 (import "module0" "static1-0") 1 (ref null $type0)) - (global $"C503 MyConstClass" (ref $MyConstClass) - (i32.const 118) + (global $"C505 MyConstClass" (ref $MyConstClass) + (i32.const 120) (i32.const 0) (i32.const 4) (i32.const 0) @@ -27,7 +27,7 @@ (func $"modH0Use " (param $var0 i32) (result (ref $MyConstClass)) local.get $var0 if (result (ref $MyConstClass)) - global.get $"C503 MyConstClass" + global.get $"C505 MyConstClass" else i32.const 0 call_indirect $module0.static1-0 (result (ref $MyConstClass)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat index 44786426b4a..abe5e9b4153 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module2.wat @@ -13,8 +13,8 @@ (field $field0 i32) (field $field1 (mut i32))))) (global $.shared-const (import "" "shared-const") (ref extern)) - (global $"C501 MyConstClass" (ref $MyConstClass) - (i32.const 118) + (global $"C503 MyConstClass" (ref $MyConstClass) + (i32.const 120) (i32.const 0) (i32.const 4) (i32.const 0) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat index 67f2effb6d2..7dd4862d50a 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.multi_module_use_module3.wat @@ -16,8 +16,8 @@ (result (ref $MyConstClass)))) (global $.h1-nonshared-const (import "" "h1-nonshared-const") (ref extern)) (table $module0.static1-0 (import "module0" "static1-0") 1 (ref null $type0)) - (global $"C499 MyConstClass" (ref $MyConstClass) - (i32.const 118) + (global $"C501 MyConstClass" (ref $MyConstClass) + (i32.const 120) (i32.const 0) (i32.const 4) (i32.const 0) @@ -27,7 +27,7 @@ (func $"modH1Use " (param $var0 i32) (result (ref $MyConstClass)) local.get $var0 if (result (ref $MyConstClass)) - global.get $"C499 MyConstClass" + global.get $"C501 MyConstClass" else i32.const 0 call_indirect $module0.static1-0 (result (ref $MyConstClass)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat index 376965e9052..fca83a44e10 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat @@ -5,7 +5,7 @@ (type $_InterfaceType <...>) (type $type0 <...>) (table $static0-0 (export "static0-0") 1 (ref null $type0)) - (global $"C417 _InterfaceType" (ref $_InterfaceType) <...>) + (global $"C419 _InterfaceType" (ref $_InterfaceType) <...>) (func $_loaded implicit getter (result (ref $_DefaultSet&_HashFieldBase&SetMixin)) <...>) (func $"useFoo " call $"useFooAsType " @@ -13,7 +13,7 @@ call $_DefaultSet&_HashFieldBase&SetMixin&_HashBase&_OperatorEqualsAndHashCode&_LinkedHashSetMixin.contains i32.eqz if - i32.const 50 + i32.const 51 i32.const 0 ref.null none i64.const 0 @@ -26,7 +26,7 @@ drop ) (func $"useFooAsType " - global.get $"C417 _InterfaceType" + global.get $"C419 _InterfaceType" call $print drop ) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat index d7c341b0cf6..f7e769c848f 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use_module1.wat @@ -7,14 +7,14 @@ (type $JSStringImpl <...>) (type $Object <...>) (global $".Foo called " (import "" "Foo called ") (ref extern)) - (global $"C465 \"Foo called \"" (ref $JSStringImpl) + (global $"C467 \"Foo called \"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".Foo called ") (struct.new $JSStringImpl)) (func $"useFooAsObject " (result (ref null $#Top)) (local $var0 (ref $Foo)) - i32.const 116 + i32.const 118 i32.const 0 i64.const 0 struct.new $Foo diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat index edf5cb33082..29051842c8d 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat @@ -15,30 +15,30 @@ (type $_TopType <...>) (func $print (import "module0" "func3") (param (ref null $#Top)) (result (ref null $#Top))) (global $"C1 WasmArray<_Type>[0]" (import "module0" "global1") (ref $Array<_Type>)) - (global $"C317 WasmArray<_NamedParameter>[0]" (import "module0" "global4") (ref $Array<_NamedParameter>)) - (global $"C342 _TopType" (import "module0" "global2") (ref $_TopType)) - (global $"C64 WasmArray<_Type>[1]" (import "module0" "global3") (ref $Array<_Type>)) + (global $"C319 WasmArray<_NamedParameter>[0]" (import "module0" "global4") (ref $Array<_NamedParameter>)) + (global $"C344 _TopType" (import "module0" "global2") (ref $_TopType)) + (global $"C63 WasmArray<_Type>[1]" (import "module0" "global3") (ref $Array<_Type>)) (global $.globalH0Foo (import "" "globalH0Foo") (ref extern)) (table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $_FunctionType)) - (global $"C476 globalH0Foo tear-off" (mut (ref null $#Closure-0-1)) + (global $"C478 globalH0Foo tear-off" (mut (ref null $#Closure-0-1)) (ref.null none)) - (global $"C477 H0" (mut (ref null $H0)) + (global $"C479 H0" (mut (ref null $H0)) (ref.null none)) - (global $"C478 \"globalH0Foo\"" (ref $JSStringImpl) + (global $"C480 \"globalH0Foo\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $.globalH0Foo) (struct.new $JSStringImpl)) (global $global0 (ref $#DummyStruct) <...>) (global $global2 (ref $#Vtable-0-1) <...>) - (func $"C477 H0 (lazy initializer)}" (result (ref $H0)) + (func $"C479 H0 (lazy initializer)}" (result (ref $H0)) (local $var0 (ref $_FunctionType)) (local $var1 (ref $#Closure-0-1)) (local $var2 (ref $H0)) - i32.const 116 + i32.const 118 i32.const 0 block $label0 (result (ref $#Closure-0-1)) - global.get $"C476 globalH0Foo tear-off" + global.get $"C478 globalH0Foo tear-off" br_on_non_null $label0 i32.const 38 i32.const 0 @@ -55,10 +55,10 @@ i64.const 0 global.get $"C1 WasmArray<_Type>[0]" global.get $"C1 WasmArray<_Type>[0]" - global.get $"C342 _TopType" - global.get $"C64 WasmArray<_Type>[1]" + global.get $"C344 _TopType" + global.get $"C63 WasmArray<_Type>[1]" i64.const 1 - global.get $"C317 WasmArray<_NamedParameter>[0]" + global.get $"C319 WasmArray<_NamedParameter>[0]" struct.new $_FunctionType local.tee $var0 table.set $module0.constant-table0 @@ -66,20 +66,20 @@ end $label1 struct.new $#Closure-0-1 local.tee $var1 - global.set $"C476 globalH0Foo tear-off" + global.set $"C478 globalH0Foo tear-off" local.get $var1 end $label0 struct.new $H0 local.tee $var2 - global.set $"C477 H0" + global.set $"C479 H0" local.get $var2 ) (func $"globalH0Foo tear-off trampoline" (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C478 \"globalH0Foo\"" + global.get $"C480 \"globalH0Foo\"" call $print ) (func $globalH0Foo (param $var0 i64) (result (ref null $#Top)) - global.get $"C478 \"globalH0Foo\"" + global.get $"C480 \"globalH0Foo\"" call $print ) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat index d2222fae3a0..90226e4d08e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat @@ -24,55 +24,55 @@ (func $JSStringImpl._interpolate (import "module0" "func4") (param (ref $Array)) (result (ref $JSStringImpl))) (func $print (import "module0" "func3") (param (ref null $#Top)) (result (ref null $#Top))) (global $"C1 WasmArray<_Type>[0]" (import "module0" "global1") (ref $Array<_Type>)) - (global $"C15 _InterfaceType" (import "module0" "global7") (ref $_InterfaceType)) - (global $"C317 WasmArray<_NamedParameter>[0]" (import "module0" "global4") (ref $Array<_NamedParameter>)) - (global $"C342 _TopType" (import "module0" "global2") (ref $_TopType)) - (global $"C64 WasmArray<_Type>[1]" (import "module0" "global3") (ref $Array<_Type>)) - (global $"C8 \")\"" (import "module0" "global0") (ref $JSStringImpl)) + (global $"C21 \")\"" (import "module0" "global0") (ref $JSStringImpl)) + (global $"C28 _InterfaceType" (import "module0" "global7") (ref $_InterfaceType)) + (global $"C319 WasmArray<_NamedParameter>[0]" (import "module0" "global4") (ref $Array<_NamedParameter>)) + (global $"C344 _TopType" (import "module0" "global2") (ref $_TopType)) + (global $"C63 WasmArray<_Type>[1]" (import "module0" "global3") (ref $Array<_Type>)) (global $.globalH1Bar< (import "" "globalH1Bar<") (ref extern)) (table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $_FunctionType)) - (global $"C470 _FunctionType" (ref $_FunctionType) <...>) - (global $"C471 globalH1Foo tear-off" (mut (ref null $#Closure-1-1)) + (global $"C472 _FunctionType" (ref $_FunctionType) <...>) + (global $"C473 globalH1Foo tear-off" (mut (ref null $#Closure-1-1)) (ref.null none)) - (global $"C472 InstantiationConstant(globalH1Foo)" (mut (ref null $#Closure-0-1)) + (global $"C474 InstantiationConstant(globalH1Foo)" (mut (ref null $#Closure-0-1)) (ref.null none)) - (global $"C473 H1" (mut (ref null $H1)) + (global $"C475 H1" (mut (ref null $H1)) (ref.null none)) - (global $"C474 \"globalH1Bar<\"" (ref $JSStringImpl) + (global $"C476 \"globalH1Bar<\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $.globalH1Bar<) (struct.new $JSStringImpl)) - (global $"C475 \">(\"" (ref $JSStringImpl) <...>) + (global $"C477 \">(\"" (ref $JSStringImpl) <...>) (global $global0 (ref $#DummyStruct) <...>) (global $global2 (ref $#Vtable-1-1) <...>) (func $#dummy function (ref struct) -> (ref null #Top) (param $var0 (ref struct)) (result (ref null $#Top)) <...>) - (func $"C473 H1 (lazy initializer)}" (result (ref $H1)) + (func $"C475 H1 (lazy initializer)}" (result (ref $H1)) (local $var0 (ref $#Closure-1-1)) (local $var1 (ref $_FunctionType)) (local $var2 (ref $#Closure-0-1)) (local $var3 (ref $H1)) - i32.const 117 + i32.const 119 i32.const 0 block $label0 (result (ref $#Closure-0-1)) - global.get $"C472 InstantiationConstant(globalH1Foo)" + global.get $"C474 InstantiationConstant(globalH1Foo)" br_on_non_null $label0 i32.const 38 i32.const 0 block $label1 (result (ref $#Closure-1-1)) - global.get $"C471 globalH1Foo tear-off" + global.get $"C473 globalH1Foo tear-off" br_on_non_null $label1 i32.const 38 i32.const 0 global.get $global0 global.get $global2 - global.get $"C470 _FunctionType" + global.get $"C472 _FunctionType" struct.new $#Closure-1-1 local.tee $var0 - global.set $"C471 globalH1Foo tear-off" + global.set $"C473 globalH1Foo tear-off" local.get $var0 end $label1 - global.get $"C15 _InterfaceType" + global.get $"C28 _InterfaceType" struct.new $#InstantiationContext-1-1 ref.func $"#dummy function (ref struct) -> (ref null #Top)" ref.func $"instantiation constant trampoline" @@ -88,10 +88,10 @@ i64.const 0 global.get $"C1 WasmArray<_Type>[0]" global.get $"C1 WasmArray<_Type>[0]" - global.get $"C342 _TopType" - global.get $"C64 WasmArray<_Type>[1]" + global.get $"C344 _TopType" + global.get $"C63 WasmArray<_Type>[1]" i64.const 1 - global.get $"C317 WasmArray<_NamedParameter>[0]" + global.get $"C319 WasmArray<_NamedParameter>[0]" struct.new $_FunctionType local.tee $var1 table.set $module0.constant-table0 @@ -99,20 +99,20 @@ end $label2 struct.new $#Closure-0-1 local.tee $var2 - global.set $"C472 InstantiationConstant(globalH1Foo)" + global.set $"C474 InstantiationConstant(globalH1Foo)" local.get $var2 end $label0 struct.new $H1 local.tee $var3 - global.set $"C473 H1" + global.set $"C475 H1" local.get $var3 ) (func $"globalH1Foo tear-off trampoline" (param $var0 (ref struct)) (param $var1 (ref $_Type)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C474 \"globalH1Bar<\"" + global.get $"C476 \"globalH1Bar<\"" local.get $var1 - global.get $"C475 \">(\"" + global.get $"C477 \">(\"" local.get $var2 - global.get $"C8 \")\"" + global.get $"C21 \")\"" array.new_fixed $Array 5 call $JSStringImpl._interpolate call $print @@ -121,21 +121,21 @@ (func $"modH1UseH1 " (result (ref null $#Top)) (local $var0 (ref $#Closure-0-1)) block $label0 (result (ref $H1)) - global.get $"C473 H1" + global.get $"C475 H1" br_on_non_null $label0 - call $"C473 H1 (lazy initializer)}" + call $"C475 H1 (lazy initializer)}" end $label0 call $print drop block $label1 (result (ref $H1)) - global.get $"C473 H1" + global.get $"C475 H1" br_on_non_null $label1 - call $"C473 H1 (lazy initializer)}" + call $"C475 H1 (lazy initializer)}" end $label1 struct.get $H1 $fun local.tee $var0 struct.get $#Closure-0-1 $context - i32.const 86 + i32.const 69 i64.const 1 struct.new $BoxedInt local.get $var0 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized.wat index c76d746e573..f3d9635ee6d 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized.wat @@ -7,17 +7,17 @@ (global $".Foo1.doitDispatch(" (import "" "Foo1.doitDispatch(") (ref extern)) (global $".FooBase(" (import "" "FooBase(") (ref extern)) (table $static0-0 (export "static0-0") 1 (ref null $type0)) - (global $"C383 \"FooBase(\"" (ref $JSStringImpl) + (global $"C385 \"FooBase(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooBase(") (struct.new $JSStringImpl)) - (global $"C384 \"Foo1.doitDispatch(\"" (ref $JSStringImpl) + (global $"C386 \"Foo1.doitDispatch(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".Foo1.doitDispatch(") (struct.new $JSStringImpl)) - (global $"C385 \"Foo0.doitDispatch(\"" (ref $JSStringImpl) + (global $"C387 \"Foo0.doitDispatch(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".Foo0.doitDispatch(") @@ -28,7 +28,7 @@ (func $"foo0 " call $"runtimeTrue implicit getter" if (result (ref $Object)) - i32.const 118 + i32.const 120 i32.const 0 struct.new $Object else @@ -46,7 +46,7 @@ ) (func $runtimeTrue implicit getter (result i32) <...>) (func $Foo0.doitDispatch (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C385 \"Foo0.doitDispatch(\"" + global.get $"C387 \"Foo0.doitDispatch(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 @@ -58,7 +58,7 @@ ) (func $Foo1 (result (ref $Object)) <...>) (func $Foo1.doitDispatch (export "func1") (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C384 \"Foo1.doitDispatch(\"" + global.get $"C386 \"Foo1.doitDispatch(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 @@ -69,7 +69,7 @@ ref.null none ) (func $FooBase.doitDispatch (param $var0 (ref null $#Top)) - global.get $"C383 \"FooBase(\"" + global.get $"C385 \"FooBase(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat index abb48d467b4..0e45d2ce028 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat @@ -8,14 +8,14 @@ (func $JSStringImpl._interpolate3 (import "module0" "func2") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl))) (func $print (import "module0" "func3") (param (ref null $#Top)) (result (ref null $#Top))) (global $".Foo1.doitDevirt(" (import "" "Foo1.doitDevirt(") (ref extern)) - (global $"C315 1" (import "module0" "global1") (ref $BoxedInt)) - (global $"C344 2" (import "module0" "global3") (ref $BoxedInt)) - (global $"C383 \"FooBase(\"" (import "module0" "global5") (ref $JSStringImpl)) + (global $"C317 1" (import "module0" "global1") (ref $BoxedInt)) + (global $"C346 2" (import "module0" "global3") (ref $BoxedInt)) + (global $"C385 \"FooBase(\"" (import "module0" "global5") (ref $JSStringImpl)) (global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl)) (global $baseObj (import "module0" "global0") (ref null $Object)) (global $foo1Obj (import "module0" "global2") (ref null $Object)) - (table $module0.dispatch0 (import "module0" "dispatch0") 760 funcref) - (global $"C502 \"Foo1.doitDevirt(\"" (ref $JSStringImpl) + (table $module0.dispatch0 (import "module0" "dispatch0") 773 funcref) + (global $"C504 \"Foo1.doitDevirt(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".Foo1.doitDevirt(") @@ -29,10 +29,10 @@ br $label0 end $label1 local.tee $var0 - global.get $"C315 1" + global.get $"C317 1" local.get $var0 struct.get $Object $field0 - i32.const 508 + i32.const 444 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) (result (ref null $#Top)) drop @@ -41,7 +41,7 @@ br_on_non_null $label2 br $label0 end $label2 - global.get $"C344 2" + global.get $"C346 2" call $Foo1.doitDispatch drop block $label3 (result (ref $Object)) @@ -63,14 +63,14 @@ unreachable ) (func $Foo1.doitDevirt (param $var0 (ref $Object)) - global.get $"C502 \"Foo1.doitDevirt(\"" - global.get $"C315 1" + global.get $"C504 \"Foo1.doitDevirt(\"" + global.get $"C317 1" global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print drop - global.get $"C383 \"FooBase(\"" - global.get $"C315 1" + global.get $"C385 \"FooBase(\"" + global.get $"C317 1" global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat index 201b4512725..a144bf60778 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat @@ -14,29 +14,29 @@ (table $static2-0 (export "static2-0") 4 (ref null $type4)) (table $static3-0 (export "static3-0") 4 (ref null $type6)) (global $"C12 0" (ref $BoxedInt) <...>) - (global $"C390 \"FooConstBase(\"" (ref $JSStringImpl) + (global $"C392 \"FooConstBase(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConstBase(") (struct.new $JSStringImpl)) - (global $"C391 FooConst0" (ref $Object) - (i32.const 118) + (global $"C393 FooConst0" (ref $Object) + (i32.const 120) (i32.const 0) (struct.new $Object)) - (global $"C392 \"FooConst0(\"" (ref $JSStringImpl) + (global $"C394 \"FooConst0(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConst0(") (struct.new $JSStringImpl)) - (global $"C508 \"foo0Code(\"" (ref $JSStringImpl) <...>) + (global $"C510 \"foo0Code(\"" (ref $JSStringImpl) <...>) (global $"C8 \")\"" (ref $JSStringImpl) <...>) (global $fooGlobal0 (mut (ref null $#Top)) (ref.null none)) (func $"foo0Code " (export "func12") (param $var0 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C391 FooConst0" + global.get $"C393 FooConst0" call $print drop - global.get $"C508 \"foo0Code(\"" + global.get $"C510 \"foo0Code(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 @@ -47,7 +47,7 @@ ref.null none ) (func $FooConst0.doit (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C392 \"FooConst0(\"" + global.get $"C394 \"FooConst0(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 @@ -60,7 +60,7 @@ ref.null none ) (func $FooConstBase.doit (export "func14") (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C390 \"FooConstBase(\"" + global.get $"C392 \"FooConstBase(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat index 22d3dc72937..2a1d43b6db0 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module1.wat @@ -18,25 +18,25 @@ (func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl))) (func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top))) (global $".FooConst5(" (import "" "FooConst5(") (ref extern)) - (global $"C313 \"[]\"" (import "module0" "global6") (ref $JSStringImpl)) - (global $"C389 5" (import "module0" "global5") (ref $BoxedInt)) - (global $"C391 FooConst0" (import "module0" "global7") (ref $Object)) + (global $"C315 \"[]\"" (import "module0" "global6") (ref $JSStringImpl)) + (global $"C391 5" (import "module0" "global5") (ref $BoxedInt)) + (global $"C393 FooConst0" (import "module0" "global7") (ref $Object)) (global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl)) - (table $module0.dispatch0 (import "module0" "dispatch0") 778 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 789 funcref) (table $module0.static1-0 (import "module0" "static1-0") 4 (ref null $type2)) (table $module0.static2-0 (import "module0" "static2-0") 4 (ref null $type0)) (table $module0.static3-0 (import "module0" "static3-0") 4 (ref null $type4)) - (global $"C510 FooConst5" (ref $Object) - (i32.const 123) + (global $"C512 FooConst5" (ref $Object) + (i32.const 125) (i32.const 0) (struct.new $Object)) - (global $"C511 \"foo5Code(\"" (ref $JSStringImpl) <...>) - (global $"C512 \"FooConst5(\"" (ref $JSStringImpl) + (global $"C513 \"foo5Code(\"" (ref $JSStringImpl) <...>) + (global $"C514 \"FooConst5(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConst5(") (struct.new $JSStringImpl)) - (global $"C513 _InterfaceType" (ref $_InterfaceType) <...>) + (global $"C515 _InterfaceType" (ref $_InterfaceType) <...>) (global $allFooConstants (mut (ref null $WasmListBase)) (ref.null none)) (global $fooGlobal5 (mut (ref null $#Top)) @@ -46,16 +46,16 @@ (local $var1 (ref $WasmListBase)) (local $var2 (ref $Object)) (local $var3 i64) - global.get $"C510 FooConst5" + global.get $"C512 FooConst5" call $print drop - global.get $"C511 \"foo5Code(\"" + global.get $"C513 \"foo5Code(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print drop - global.get $"C389 5" + global.get $"C391 5" global.set $fooGlobal5 call $"fooGlobal0 implicit getter" call $"foo0Code " @@ -83,8 +83,8 @@ block $label0 (result (ref $WasmListBase)) global.get $allFooConstants br_on_non_null $label0 - global.get $"C513 _InterfaceType" - global.get $"C391 FooConst0" + global.get $"C515 _InterfaceType" + global.get $"C393 FooConst0" i32.const 0 call_indirect $module0.static3-0 (result (ref $Object)) i32.const 1 @@ -93,7 +93,7 @@ call_indirect $module0.static3-0 (result (ref $Object)) i32.const 3 call_indirect $module0.static3-0 (result (ref $Object)) - global.get $"C510 FooConst5" + global.get $"C512 FooConst5" array.new_fixed $Array 6 call $GrowableList._withData global.set $allFooConstants @@ -107,7 +107,7 @@ if i64.const 0 local.get $var3 - global.get $"C313 \"[]\"" + global.get $"C315 \"[]\"" call $"_throwIndexError " unreachable end @@ -120,14 +120,14 @@ call $"fooGlobal5 implicit getter" local.get $var2 struct.get $Object $field0 - i32.const 400 + i32.const 406 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) (result (ref null $#Top)) drop ) (func $fooGlobal5 implicit getter (result (ref $#Top)) <...>) (func $FooConst5.doit (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C512 \"FooConst5(\"" + global.get $"C514 \"FooConst5(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat index 9c37c978085..e96a06451e6 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module2.wat @@ -7,36 +7,36 @@ (func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl))) (func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top))) (global $".FooConst1(" (import "" "FooConst1(") (ref extern)) - (global $"C319 1" (import "module0" "global8") (ref $BoxedInt)) + (global $"C321 1" (import "module0" "global8") (ref $BoxedInt)) (global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl)) - (global $"C514 FooConst1" (ref $Object) - (i32.const 119) + (global $"C516 FooConst1" (ref $Object) + (i32.const 121) (i32.const 0) (struct.new $Object)) - (global $"C521 \"FooConst1(\"" (ref $JSStringImpl) + (global $"C523 \"FooConst1(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConst1(") (struct.new $JSStringImpl)) - (global $"C529 \"foo1Code(\"" (ref $JSStringImpl) <...>) + (global $"C531 \"foo1Code(\"" (ref $JSStringImpl) <...>) (global $fooGlobal1 (mut (ref null $#Top)) (ref.null none)) (func $"foo1Code " (param $var0 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C514 FooConst1" + global.get $"C516 FooConst1" call $print drop - global.get $"C529 \"foo1Code(\"" + global.get $"C531 \"foo1Code(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print drop - global.get $"C319 1" + global.get $"C321 1" global.set $fooGlobal1 ref.null none ) (func $FooConst1.doit (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C521 \"FooConst1(\"" + global.get $"C523 \"FooConst1(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat index 661ab82e817..1826b789116 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module3.wat @@ -7,36 +7,36 @@ (func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl))) (func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top))) (global $".FooConst2(" (import "" "FooConst2(") (ref extern)) - (global $"C348 2" (import "module0" "global12") (ref $BoxedInt)) + (global $"C350 2" (import "module0" "global12") (ref $BoxedInt)) (global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl)) - (global $"C515 FooConst2" (ref $Object) - (i32.const 120) + (global $"C517 FooConst2" (ref $Object) + (i32.const 122) (i32.const 0) (struct.new $Object)) - (global $"C520 \"FooConst2(\"" (ref $JSStringImpl) + (global $"C522 \"FooConst2(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConst2(") (struct.new $JSStringImpl)) - (global $"C528 \"foo2Code(\"" (ref $JSStringImpl) <...>) + (global $"C530 \"foo2Code(\"" (ref $JSStringImpl) <...>) (global $fooGlobal2 (mut (ref null $#Top)) (ref.null none)) (func $"foo2Code " (param $var0 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C515 FooConst2" + global.get $"C517 FooConst2" call $print drop - global.get $"C528 \"foo2Code(\"" + global.get $"C530 \"foo2Code(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print drop - global.get $"C348 2" + global.get $"C350 2" global.set $fooGlobal2 ref.null none ) (func $FooConst2.doit (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C520 \"FooConst2(\"" + global.get $"C522 \"FooConst2(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat index 0083b9880c4..5001a31632e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module4.wat @@ -7,36 +7,36 @@ (func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl))) (func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top))) (global $".FooConst3(" (import "" "FooConst3(") (ref extern)) - (global $"C427 3" (import "module0" "global11") (ref $BoxedInt)) + (global $"C429 3" (import "module0" "global11") (ref $BoxedInt)) (global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl)) - (global $"C516 FooConst3" (ref $Object) - (i32.const 121) + (global $"C518 FooConst3" (ref $Object) + (i32.const 123) (i32.const 0) (struct.new $Object)) - (global $"C519 \"FooConst3(\"" (ref $JSStringImpl) + (global $"C521 \"FooConst3(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConst3(") (struct.new $JSStringImpl)) - (global $"C527 \"foo3Code(\"" (ref $JSStringImpl) <...>) + (global $"C529 \"foo3Code(\"" (ref $JSStringImpl) <...>) (global $fooGlobal3 (mut (ref null $#Top)) (ref.null none)) (func $"foo3Code " (param $var0 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C516 FooConst3" + global.get $"C518 FooConst3" call $print drop - global.get $"C527 \"foo3Code(\"" + global.get $"C529 \"foo3Code(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print drop - global.get $"C427 3" + global.get $"C429 3" global.set $fooGlobal3 ref.null none ) (func $FooConst3.doit (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C519 \"FooConst3(\"" + global.get $"C521 \"FooConst3(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat index 5670f361c32..2488b7f2699 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat @@ -7,36 +7,36 @@ (func $JSStringImpl._interpolate3 (import "module0" "func10") (param (ref null $#Top) (ref null $#Top) (ref null $#Top)) (result (ref $JSStringImpl))) (func $print (import "module0" "func9") (param (ref null $#Top)) (result (ref null $#Top))) (global $".FooConst4(" (import "" "FooConst4(") (ref extern)) - (global $"C367 4" (import "module0" "global10") (ref $BoxedInt)) + (global $"C369 4" (import "module0" "global10") (ref $BoxedInt)) (global $"C8 \")\"" (import "module0" "global4") (ref $JSStringImpl)) - (global $"C517 FooConst4" (ref $Object) - (i32.const 122) + (global $"C519 FooConst4" (ref $Object) + (i32.const 124) (i32.const 0) (struct.new $Object)) - (global $"C518 \"FooConst4(\"" (ref $JSStringImpl) + (global $"C520 \"FooConst4(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".FooConst4(") (struct.new $JSStringImpl)) - (global $"C526 \"foo4Code(\"" (ref $JSStringImpl) <...>) + (global $"C528 \"foo4Code(\"" (ref $JSStringImpl) <...>) (global $fooGlobal4 (mut (ref null $#Top)) (ref.null none)) (func $"foo4Code " (param $var0 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C517 FooConst4" + global.get $"C519 FooConst4" call $print drop - global.get $"C526 \"foo4Code(\"" + global.get $"C528 \"foo4Code(\"" local.get $var0 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 call $print drop - global.get $"C367 4" + global.get $"C369 4" global.set $fooGlobal4 ref.null none ) (func $FooConst4.doit (param $var0 (ref $Object)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - global.get $"C518 \"FooConst4(\"" + global.get $"C520 \"FooConst4(\"" local.get $var1 global.get $"C8 \")\"" call $JSStringImpl._interpolate3 diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat index 59871c5d448..5009545a2a3 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat @@ -20,10 +20,10 @@ (type $type2 <...>) (global $"C1 WasmArray<_Type>[0]" (ref $Array<_Type>) <...>) (global $"C28 _InterfaceType" (ref $_InterfaceType) <...>) - (global $"C314 WasmArray<_NamedParameter>[0]" (ref $Array<_NamedParameter>) <...>) - (global $"C340 _TopType" (ref $_TopType) <...>) - (global $"C344 foo tear-off" (ref $#Closure-0-2) - (i32.const 32) + (global $"C316 WasmArray<_NamedParameter>[0]" (ref $Array<_NamedParameter>) <...>) + (global $"C342 _TopType" (ref $_TopType) <...>) + (global $"C346 foo tear-off" (ref $#Closure-0-2) + (i32.const 56) (i32.const 0) (global.get $global0) (ref.func $"foo tear-off trampoline") @@ -34,21 +34,21 @@ (i32.const 0) (global.get $"C1 WasmArray<_Type>[0]") (global.get $"C1 WasmArray<_Type>[0]") - (global.get $"C340 _TopType") + (global.get $"C342 _TopType") (global.get $"C28 _InterfaceType") (i32.const 10) (i32.const 0) (i32.const 1) - (i32.const 152) + (i32.const 154) (global.get $"C1 WasmArray<_Type>[0]") (struct.new $_InterfaceType) (array.new_fixed $Array<_Type> 2) (i64.const 1) - (global.get $"C314 WasmArray<_NamedParameter>[0]") + (global.get $"C316 WasmArray<_NamedParameter>[0]") (struct.new $_FunctionType) (struct.new $#Closure-0-2)) - (global $"C348 bar tear-off" (ref $#Closure-0-2) - (i32.const 32) + (global $"C350 bar tear-off" (ref $#Closure-0-2) + (i32.const 56) (i32.const 0) (global.get $global0) (ref.func $"bar tear-off trampoline") @@ -59,17 +59,17 @@ (i32.const 0) (global.get $"C1 WasmArray<_Type>[0]") (global.get $"C1 WasmArray<_Type>[0]") - (global.get $"C340 _TopType") + (global.get $"C342 _TopType") (global.get $"C28 _InterfaceType") (i32.const 10) (i32.const 0) (i32.const 1) - (i32.const 135) + (i32.const 137) (global.get $"C1 WasmArray<_Type>[0]") (struct.new $_InterfaceType) (array.new_fixed $Array<_Type> 2) (i64.const 1) - (global.get $"C314 WasmArray<_NamedParameter>[0]") + (global.get $"C316 WasmArray<_NamedParameter>[0]") (struct.new $_FunctionType) (struct.new $#Closure-0-2)) (global $global0 (ref $#DummyStruct) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat index f8a728a8594..90c7aae5324 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat @@ -20,10 +20,10 @@ (type $type2 <...>) (global $"C1 WasmArray<_Type>[0]" (ref $Array<_Type>) <...>) (global $"C28 _InterfaceType" (ref $_InterfaceType) <...>) - (global $"C314 WasmArray<_NamedParameter>[0]" (ref $Array<_NamedParameter>) <...>) - (global $"C340 _TopType" (ref $_TopType) <...>) - (global $"C344 foo tear-off" (ref $#Closure-0-2) - (i32.const 32) + (global $"C316 WasmArray<_NamedParameter>[0]" (ref $Array<_NamedParameter>) <...>) + (global $"C342 _TopType" (ref $_TopType) <...>) + (global $"C346 foo tear-off" (ref $#Closure-0-2) + (i32.const 56) (i32.const 0) (global.get $global0) (ref.func $"foo tear-off trampoline") @@ -34,21 +34,21 @@ (i32.const 0) (global.get $"C1 WasmArray<_Type>[0]") (global.get $"C1 WasmArray<_Type>[0]") - (global.get $"C340 _TopType") + (global.get $"C342 _TopType") (global.get $"C28 _InterfaceType") (i32.const 10) (i32.const 0) (i32.const 1) - (i32.const 152) + (i32.const 154) (global.get $"C1 WasmArray<_Type>[0]") (struct.new $_InterfaceType) (array.new_fixed $Array<_Type> 2) (i64.const 1) - (global.get $"C314 WasmArray<_NamedParameter>[0]") + (global.get $"C316 WasmArray<_NamedParameter>[0]") (struct.new $_FunctionType) (struct.new $#Closure-0-2)) - (global $"C349 bar tear-off" (ref $#Closure-0-2) - (i32.const 32) + (global $"C351 bar tear-off" (ref $#Closure-0-2) + (i32.const 56) (i32.const 0) (global.get $global0) (ref.func $"bar tear-off trampoline") @@ -59,17 +59,17 @@ (i32.const 0) (global.get $"C1 WasmArray<_Type>[0]") (global.get $"C1 WasmArray<_Type>[0]") - (global.get $"C340 _TopType") + (global.get $"C342 _TopType") (global.get $"C28 _InterfaceType") (i32.const 10) (i32.const 0) (i32.const 1) - (i32.const 135) + (i32.const 137) (global.get $"C1 WasmArray<_Type>[0]") (struct.new $_InterfaceType) (array.new_fixed $Array<_Type> 2) (i64.const 1) - (global.get $"C314 WasmArray<_NamedParameter>[0]") + (global.get $"C316 WasmArray<_NamedParameter>[0]") (struct.new $_FunctionType) (struct.new $#Closure-0-2)) (global $global0 (ref $#DummyStruct) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat index 93022508fba..edcc5027d78 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat @@ -24,10 +24,10 @@ (type $type2 <...>) (global $"C1 WasmArray<_Type>[0]" (ref $Array<_Type>) <...>) (global $"C28 _InterfaceType" (ref $_InterfaceType) <...>) - (global $"C314 WasmArray<_NamedParameter>[0]" (ref $Array<_NamedParameter>) <...>) - (global $"C340 _TopType" (ref $_TopType) <...>) - (global $"C344 foo tear-off" (ref $#Closure-0-2) - (i32.const 32) + (global $"C316 WasmArray<_NamedParameter>[0]" (ref $Array<_NamedParameter>) <...>) + (global $"C342 _TopType" (ref $_TopType) <...>) + (global $"C346 foo tear-off" (ref $#Closure-0-2) + (i32.const 56) (i32.const 0) (global.get $global0) (ref.func $"foo tear-off dynamic call entry") @@ -38,22 +38,22 @@ (i32.const 0) (global.get $"C1 WasmArray<_Type>[0]") (global.get $"C1 WasmArray<_Type>[0]") - (global.get $"C340 _TopType") + (global.get $"C342 _TopType") (global.get $"C28 _InterfaceType") (i32.const 10) (i32.const 0) (i32.const 1) - (i32.const 152) + (i32.const 154) (global.get $"C1 WasmArray<_Type>[0]") (struct.new $_InterfaceType) (array.new_fixed $Array<_Type> 2) (i64.const 1) - (global.get $"C314 WasmArray<_NamedParameter>[0]") + (global.get $"C316 WasmArray<_NamedParameter>[0]") (struct.new $_FunctionType) (struct.new $#Closure-0-2)) - (global $"C346 _InterfaceType" (ref $_InterfaceType) <...>) - (global $"C349 bar tear-off" (ref $#Closure-0-2) - (i32.const 32) + (global $"C348 _InterfaceType" (ref $_InterfaceType) <...>) + (global $"C351 bar tear-off" (ref $#Closure-0-2) + (i32.const 56) (i32.const 0) (global.get $global0) (ref.func $"bar tear-off dynamic call entry") @@ -64,12 +64,12 @@ (i32.const 0) (global.get $"C1 WasmArray<_Type>[0]") (global.get $"C1 WasmArray<_Type>[0]") - (global.get $"C340 _TopType") + (global.get $"C342 _TopType") (global.get $"C28 _InterfaceType") - (global.get $"C346 _InterfaceType") + (global.get $"C348 _InterfaceType") (array.new_fixed $Array<_Type> 2) (i64.const 1) - (global.get $"C314 WasmArray<_NamedParameter>[0]") + (global.get $"C316 WasmArray<_NamedParameter>[0]") (struct.new $_FunctionType) (struct.new $#Closure-0-2)) (global $global0 (ref $#DummyStruct) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/hello.wat b/pkg/dart2wasm/test/ir_tests/hello.wat index e3295964633..aa293a5cbd5 100644 --- a/pkg/dart2wasm/test/ir_tests/hello.wat +++ b/pkg/dart2wasm/test/ir_tests/hello.wat @@ -5,12 +5,12 @@ (field $field0 i32) (field $_ref externref)))) (global $".hello world" (import "" "hello world") (ref extern)) - (global $"C338 \"hello world\"" (ref $JSStringImpl) + (global $"C340 \"hello world\"" (ref $JSStringImpl) (i32.const 4) (global.get $".hello world") (struct.new $JSStringImpl)) (func $"main " - global.get $"C338 \"hello world\"" + global.get $"C340 \"hello world\"" call $print ) (func $print (param $var0 (ref $#Top)) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat index fad0779cd18..2fe1a1fea01 100644 --- a/pkg/dart2wasm/test/ir_tests/import_name_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/import_name_module1.wat @@ -10,7 +10,7 @@ (field $field1 (mut i32))))) (func $print (import "module0" "func0") (param (ref null $#Top)) (result (ref null $#Top))) (global $".hello world" (import "" "hello world") (ref extern)) - (global $"C463 \"hello world\"" (ref $JSStringImpl) + (global $"C465 \"hello world\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) (global.get $".hello world") @@ -20,7 +20,7 @@ ref.null none ) (func $"mainFoo " - global.get $"C463 \"hello world\"" + global.get $"C465 \"hello world\"" call $print drop ) diff --git a/pkg/dart2wasm/test/ir_tests/interop.int.wat b/pkg/dart2wasm/test/ir_tests/interop.int.wat index fea0a0ac887..d00b5b435f1 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.int.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.int.wat @@ -38,7 +38,7 @@ if (result (ref null $BoxedInt)) ref.null none else - i32.const 68 + i32.const 69 local.get $var0 call $dartifyInt struct.new $BoxedInt @@ -69,7 +69,7 @@ if call $"ktrue implicit getter" if (result (ref null $BoxedInt)) - i32.const 68 + i32.const 69 call $"intValue implicit getter" struct.new $BoxedInt else @@ -94,7 +94,7 @@ if (result (ref null $BoxedInt)) ref.null none else - i32.const 68 + i32.const 69 local.get $var1 call $dartifyInt struct.new $BoxedInt diff --git a/pkg/dart2wasm/test/ir_tests/memory_use.dart b/pkg/dart2wasm/test/ir_tests/memory_use.dart new file mode 100644 index 00000000000..64b16a001c6 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/memory_use.dart @@ -0,0 +1,33 @@ +// Copyright (c) 2026, 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. + +// functionFilter=main +// compilerOption=--no-minify +// compilerOption=--enable-experimental-wasm-interop + +import 'dart:_wasm'; + +@pragma('wasm:import', 'foo.mem') +@pragma('wasm:memory-type', MemoryType(limits: Limits(1))) +external Memory get memory; + +@pragma('wasm:never-inline') +void main() { + memory.size; + memory.grow(1); + print(memory.loadFloat32(0, align: 2).toDouble()); + print(memory.loadFloat32(0, align: 2).toDouble()); + print(memory.loadFloat64(0, align: 3).toDouble()); + + print(memory.loadFloat32(0, offset: 1, align: 2).toDouble()); + print(memory + .loadFloat32( + 0, + align: 2, + offset: 1, + ) + .toDouble()); + + memory.storeInt32(memory.size, WasmI32.fromInt(32), offset: 10); +} diff --git a/pkg/dart2wasm/test/ir_tests/memory_use.wat b/pkg/dart2wasm/test/ir_tests/memory_use.wat new file mode 100644 index 00000000000..f032f1934c8 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/memory_use.wat @@ -0,0 +1,48 @@ +(module $module0 + (type $#Top (struct + (field $field0 i32))) + (type $BoxedDouble (sub final $#Top (struct + (field $field0 i32) + (field $value f64)))) + (memory $foo.mem (import "foo" "mem") 1) + (func $"main " + memory.size $foo.mem + drop + i32.const 1 + memory.grow $foo.mem + drop + i32.const 88 + i32.const 0 + f32.load align=4 + f64.promote_f32 + struct.new $BoxedDouble + call $print + i32.const 88 + i32.const 0 + f32.load align=4 + f64.promote_f32 + struct.new $BoxedDouble + call $print + i32.const 88 + i32.const 0 + f64.load align=8 + struct.new $BoxedDouble + call $print + i32.const 88 + i32.const 1 + f32.load align=4 + f64.promote_f32 + struct.new $BoxedDouble + call $print + i32.const 88 + i32.const 1 + f32.load align=4 + f64.promote_f32 + struct.new $BoxedDouble + call $print + memory.size $foo.mem + i32.const 32 + i32.store offset=10 + ) + (func $print (param $var0 (ref $#Top)) <...>) +) \ No newline at end of file diff --git a/pkg/front_end/lib/src/codes/cfe_codes_generated.dart b/pkg/front_end/lib/src/codes/cfe_codes_generated.dart index 64e96bcea18..65ee97a8b43 100644 --- a/pkg/front_end/lib/src/codes/cfe_codes_generated.dart +++ b/pkg/front_end/lib/src/codes/cfe_codes_generated.dart @@ -16298,6 +16298,29 @@ const MessageCode codeVoidExpression = const MessageCode( problemMessage: """This expression has type 'void' and can't be used.""", ); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode codeWasmExternInvalidLoad = const MessageCode( + "WasmExternInvalidLoad", + problemMessage: + """WebAssembly elements may only be referenced to directly call a method on them.""", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode codeWasmExternInvalidTarget = const MessageCode( + "WasmExternInvalidTarget", + problemMessage: + """The receiver of this call must be a top-level variable describing the WebAssembly element.""", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode codeWasmExternMemoryMissingAnnotation = const MessageCode( + "WasmExternMemoryMissingAnnotation", + problemMessage: + """This external getter returns a memory instance, but no annotation describing it was found""", + correctionMessage: + """Try adding a `@MemoryType()` or `@Import.memory()` annotation.""", +); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const MessageCode codeWasmImportOrExportInUserCode = const MessageCode( "WasmImportOrExportInUserCode", @@ -16305,6 +16328,12 @@ const MessageCode codeWasmImportOrExportInUserCode = const MessageCode( """Pragmas `wasm:import` and `wasm:export` are for internal use only and cannot be used by user code.""", ); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode codeWasmIntrinsicTearOff = const MessageCode( + "WasmIntrinsicTearOff", + problemMessage: """This intrinsic extension member may not be torn off.""", +); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const MessageCode codeWeakReferenceMismatchReturnAndArgumentTypes = const MessageCode( diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index ec0bd7b730f..384d0c0308c 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -162,6 +162,10 @@ front_end/UnsoundSwitchStatementError/example: missingExample # Used in throw in front_end/Unspecified/example: missingExample # This should generally not be used. front_end/UnterminatedToken/example: missingExample # This is a fall-back message that shouldn't happen. front_end/WasmImportOrExportInUserCode/example: missingExample # only issued by wasm build +front_end/WasmExternInvalidLoad/example: missingExample # only issued by wasm build +front_end/WasmExternInvalidTarget/example: missingExample # only issued by wasm build +front_end/WasmIntrinsicTearOff/example: missingExample # only issued by wasm build +front_end/WasmExternMemoryMissingAnnotation/example: missingExample # only issued by wasm build front_end/WebLiteralCannotBeRepresentedExactly/example: missingExample # only issued on web build # Can we do better? diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 5cd06d4201b..8d9330462b3 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -7182,6 +7182,23 @@ wasmImportOrExportInUserCode: parameters: none problemMessage: "Pragmas `wasm:import` and `wasm:export` are for internal use only and cannot be used by user code." +wasmExternMemoryMissingAnnotation: + parameters: none + problemMessage: "This external getter returns a memory instance, but no annotation describing it was found" + correctionMessage: "Try adding a `@MemoryType()` or `@Import.memory()` annotation." + +wasmExternInvalidLoad: + parameters: none + problemMessage: "WebAssembly elements may only be referenced to directly call a method on them." + +wasmExternInvalidTarget: + parameters: none + problemMessage: "The receiver of this call must be a top-level variable describing the WebAssembly element." + +wasmIntrinsicTearOff: + parameters: none + problemMessage: This intrinsic extension member may not be torn off. + weakReferenceNotStatic: parameters: none problemMessage: "Weak reference pragma can be used on a static method only." diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt index ca3fc3e1d89..d7a28e3ad75 100644 --- a/pkg/front_end/test/spell_checking_list_messages.txt +++ b/pkg/front_end/test/spell_checking_list_messages.txt @@ -73,6 +73,7 @@ guarded guides h https +import.memory int32x interact interop @@ -89,6 +90,7 @@ list.filled loadlibrary macro member(s) +memorytype migrate mocking modifier @@ -158,6 +160,7 @@ unavailable unsound unsupportederror v +webassembly wasm:export wasm:import x diff --git a/pkg/wasm_builder/lib/src/builder/instructions.dart b/pkg/wasm_builder/lib/src/builder/instructions.dart index 44fb965996a..faf024efd6c 100644 --- a/pkg/wasm_builder/lib/src/builder/instructions.dart +++ b/pkg/wasm_builder/lib/src/builder/instructions.dart @@ -1159,6 +1159,14 @@ class InstructionsBuilder with Builder { _add(ir.MemoryGrow(memory)); } + /// Emit a `memory.fill` instruction. + void memory_fill(ir.Memory memory) { + assert(_verifyTypes( + const [ir.NumType.i32, ir.NumType.i32, ir.NumType.i32], const [])); + assert(memory.enclosingModule == module); + _add(ir.MemoryFill(memory)); + } + // Reference instructions /// Emit a `ref.null` instruction. diff --git a/pkg/wasm_builder/lib/src/ir/instruction.dart b/pkg/wasm_builder/lib/src/ir/instruction.dart index d0d68562a61..e6d88d7773e 100644 --- a/pkg/wasm_builder/lib/src/ir/instruction.dart +++ b/pkg/wasm_builder/lib/src/ir/instruction.dart @@ -552,6 +552,8 @@ abstract class Instruction implements Serializable { return I64TruncSatF64S.deserialize(d); case 0x07: return I64TruncSatF64U.deserialize(d); + case 0x0B: + return MemoryFill.deserialize(d, memories); case 0x10: return TableSize.deserialize(d, tables); case 0x11: @@ -2004,6 +2006,7 @@ class MemorySize extends Instruction { @override void printTo(IrPrinter p) { p.write(name); + p.write(' '); p.writeMemoryReference(memory); } } @@ -2026,6 +2029,33 @@ class MemoryGrow extends Instruction { @override String get name => 'memory.grow'; + @override + void printTo(IrPrinter p) { + p.write(name); + p.write(' '); + p.writeMemoryReference(memory); + } +} + +class MemoryFill extends Instruction { + final Memory memory; + + MemoryFill(this.memory); + + static MemoryFill deserialize(Deserializer d, Memories memories) { + return MemoryFill(memories[d.readUnsigned()]); + } + + @override + void serialize(Serializer s) { + s.writeByte(0xFC); + s.writeUnsigned(0x0B); + s.writeUnsigned(memory.index); + } + + @override + String get name => 'memory.fill'; + @override void printTo(IrPrinter p) { p.write(name); diff --git a/pkg/wasm_builder/lib/src/ir/memory.dart b/pkg/wasm_builder/lib/src/ir/memory.dart index c8a458ef17d..f84b000a6da 100644 --- a/pkg/wasm_builder/lib/src/ir/memory.dart +++ b/pkg/wasm_builder/lib/src/ir/memory.dart @@ -2,11 +2,12 @@ // 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 '../serialize/printer.dart'; import '../serialize/serialize.dart'; import 'ir.dart'; /// An (imported or defined) memory. -class Memory with Indexable, Exportable { +abstract class Memory with Indexable, Exportable { @override final FinalizableIndex finalizableIndex; final bool shared; @@ -41,6 +42,18 @@ class Memory with Indexable, Exportable { /// Export a memory from the module. @override Export buildExport(String name) => MemoryExport(name, this); + + void printTo(IrPrinter p); + + void _printType(IrPrinter p) { + // We don't encode the optional address type in our representation because + // it defaults to i32 and we don't support 64-bit addressing yet. + + p.write('$minSize'); + if (maxSize case final max?) { + p.write(' $max'); + } + } } /// A memory defined in a module. @@ -50,6 +63,27 @@ class DefinedMemory extends Memory implements Serializable { @override void serialize(Serializer s) => _serializeLimits(s); + + @override + void printTo(IrPrinter p) { + p.write('(memory '); + p.writeMemoryReference(this); + String? exportName; + for (final e in enclosingModule.exports.exported) { + if (e is MemoryExport && e.memory == this) { + exportName = e.name; + break; + } + } + if (exportName != null) { + p.write(' '); + p.writeExport(exportName); + } + + p.write(' '); + _printType(p); + p.write(')'); + } } /// An imported memory. @@ -69,6 +103,17 @@ class ImportedMemory extends Memory implements Import { s.writeByte(0x02); _serializeLimits(s); } + + @override + void printTo(IrPrinter p) { + p.write('(memory '); + p.writeMemoryReference(this); + p.write(' '); + p.writeImport(module, name); + p.write(' '); + _printType(p); + p.write(')'); + } } class MemoryExport extends Export { diff --git a/pkg/wasm_builder/lib/src/ir/module.dart b/pkg/wasm_builder/lib/src/ir/module.dart index 12ea82064a6..5edc7ab0627 100644 --- a/pkg/wasm_builder/lib/src/ir/module.dart +++ b/pkg/wasm_builder/lib/src/ir/module.dart @@ -289,6 +289,10 @@ class Module implements Serializable { } } + for (final memory in [...memories.imported, ...memories.defined]) { + mp.enqueueMemory(memory); + } + for (final table in [...tables.imported, ...tables.defined]) { mp.enqueueTable(table); } diff --git a/pkg/wasm_builder/lib/src/serialize/printer.dart b/pkg/wasm_builder/lib/src/serialize/printer.dart index da5197ce4fa..b1cac77cc53 100644 --- a/pkg/wasm_builder/lib/src/serialize/printer.dart +++ b/pkg/wasm_builder/lib/src/serialize/printer.dart @@ -22,6 +22,8 @@ class ModulePrinter { TableNamer(settings.scrubAbsoluteUris, _module, enqueueTable); late final dataNamer = DataNamer(settings.scrubAbsoluteUris, _module, enqueueDataSegment); + late final memoryNamer = + MemoryNamer(settings.scrubAbsoluteUris, _module, enqueueMemory); final _types = {}; final _tags = {}; @@ -32,6 +34,7 @@ class ModulePrinter { final _globals = {}; final _functions = {}; final _dataSegments = {}; + final _memories = {}; final _typeQueue = Queue(); final _functionsQueue = Queue(); @@ -42,8 +45,16 @@ class ModulePrinter { ModulePrinter(this._module, {this.settings = const ModulePrintSettings()}); - IrPrinter newIrPrinter() => IrPrinter._(settings.preferMultiline, _module, - typeNamer, globalNamer, functionNamer, tagNamer, tableNamer, dataNamer); + IrPrinter newIrPrinter() => IrPrinter._( + settings.preferMultiline, + _module, + typeNamer, + globalNamer, + functionNamer, + tagNamer, + tableNamer, + dataNamer, + memoryNamer); void enqueueType(ir.DefType type) { if (!_types.containsKey(type)) { @@ -81,6 +92,13 @@ class ModulePrinter { } } + void enqueueMemory(ir.Memory memory) { + if (!_memories.containsKey(memory)) { + _memories[memory] = ''; + _generateMemory(memory); + } + } + void enqueueTable(ir.Table table) { if (!_tables.containsKey(table)) { _tables[table] = ''; @@ -242,8 +260,10 @@ class ModulePrinter { } } + printOrdered(_module.memories.imported, memoryNamer, _memories); printOrdered(_module.functions.imported, functionNamer, _functions); printOrdered(_module.globals.imported, globalNamer, _globals); + printOrdered(_module.memories.defined, memoryNamer, _memories); printOrdered(_module.tables.imported, tableNamer, _tables); printOrdered(_module.tables.defined, tableNamer, _tables); printOrdered(_module.tags.defined, tagNamer, _tags); @@ -286,6 +306,12 @@ class ModulePrinter { _tags[tag] = p.getText(); } + void _generateMemory(ir.Memory memory) { + final p = newIrPrinter(); + memory.printTo(p); + _memories[memory] = p.getText(); + } + void _generateTable(ir.Table table) { final p = newIrPrinter(); if (table is ir.DefinedTable) { @@ -456,6 +482,7 @@ class IrPrinter extends IndentPrinter { final TagNamer _tagNamer; final TableNamer _tableNamer; final DataNamer _dataNamer; + final MemoryNamer _memoryNamer; _LocalNamer? _localNamer; final _labelNamer = _LabelNamer(); @@ -468,12 +495,21 @@ class IrPrinter extends IndentPrinter { this._functionNamer, this._tagNamer, this._tableNamer, - this._dataNamer); + this._dataNamer, + this._memoryNamer); /// Returns a new [IrPrinter] with same settings, but empty indentation, /// empty text content and no local namer. - IrPrinter dup() => IrPrinter._(preferMultiline, module, _typeNamer, - _globalNamer, _functionNamer, _tagNamer, _tableNamer, _dataNamer); + IrPrinter dup() => IrPrinter._( + preferMultiline, + module, + _typeNamer, + _globalNamer, + _functionNamer, + _tagNamer, + _tableNamer, + _dataNamer, + _memoryNamer); void beginLabeledBlock(ir.Instruction? instruction) { _labelNamer.stack.add(LabelInfo(instruction)); @@ -604,7 +640,7 @@ class IrPrinter extends IndentPrinter { } void writeMemoryReference(ir.Memory memory) { - throw UnimplementedError(); + write(_memoryNamer.name(memory)); } } @@ -759,6 +795,19 @@ class DataNamer extends Namer { } } +class MemoryNamer extends Namer { + MemoryNamer(super.scubUris, super.module, super.onReference); + + @override + String name(ir.Memory memory, {bool activateOnReferenceCallback = true}) { + return super._name( + memory, + memory is ir.ImportedMemory ? '${memory.module}.${memory.name}' : null, + 'memory', + activateOnReferenceCallback); + } +} + class _LabelNamer { int _nextId = 0; final stack = []; diff --git a/sdk/lib/_wasm/memory.dart b/sdk/lib/_wasm/memory.dart new file mode 100644 index 00000000000..5302c9bd39c --- /dev/null +++ b/sdk/lib/_wasm/memory.dart @@ -0,0 +1,196 @@ +// Copyright (c) 2026, 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. + +part of 'dart:_wasm'; + +/// A [memory type] in WebAssembly, describing the address range and [Limits] +/// for memory instances. +/// +/// Dart currently only supports 32-bit memory instances. +/// +/// [memory type]: https://webassembly.github.io/spec/core/syntax/types.html#memory-types +@pragma("wasm:entry-point") +final class MemoryType { + /// Minimum and optional maximum size for the memory. + final Limits limits; + + const MemoryType({required this.limits}); +} + +/// Limits for the size of memories or tables in WebAssembly. +final class Limits { + /// The minimum size for the memory instance (in units of WebAssembly pages). + final int minimum; + + /// An optional maximum size for the instance (in units of WebAsembly pages). + final int? maximum; + + const Limits(this.minimum, [this.maximum]); +} + +/// An instance of linear memory available to this WebAssembly module. +/// +/// ## Using memories +/// +/// By default, compiling Dart to WebAssembly does not create a memory instance +/// (since Dart uses garbage collected types for everything instead). Especially +/// when interacting with other modules written in languages based on linear +/// memory though, it is necessary to access a linear memory instance from Dart. +/// +/// [MemoryAccessExtension] provides methods to load and store values at certain +/// positions in a linear memory instance (e.g. [MemoryAccessExtension.loadInt8] +/// or [MemoryAccessExtension.storeInt8]), to inspect its +/// [MemoryAccessExtension.size] of memory or to [MemoryAccessExtension.grow] +/// it (if supported by the memory instance). +/// +/// In WebAssembly, instructions can't be polymorphic with regards to the memory +/// instance they operate on: Each `i8.load` instruction has the target memory +/// instance encoded into it. +/// This also restricts how memory instances can be used in Dart: Every call +/// must use a top-level getter defining the memory as a receiver. It is not +/// allowed to call methods on other instances of [Memory]: +/// +/// ``` +/// @pragma('wasm:memory-type', MemoryType(limits: Limits(1, 10))) +/// external Memory get additionalMemory; +/// +/// void main() { +/// // Allowed: Direct access to memory instance +/// print(additionalMemory.size); +/// +/// // Not allowed: Loading a reference to the memory instance. +/// useMemory(additionalMemory); +/// } +/// +/// void useMemory(Memory memory) { +/// // Not allowed: Dynamic memory instance. +/// memory.loadInt32(0, 1337); +/// } +/// ``` +/// +/// Further, tearing-off methods from [MemoryAccessExtension] is a compile-time +/// error. +/// +/// ## Obtaining memory instances +/// +/// To access linear memory, a [Memory] instance needs to be defined or +/// imported. +/// +/// To define a memory instance, use a top-level getter defined as `external` +/// and with a [MemoryType] annotation: +/// +/// ``` +/// @pragma('wasm:memory-type', MemoryType(limits: Limits(1, 10))) +/// external Memory get additionalMemory; +/// ``` +/// +/// Memory instances can also be imported from the host environment by +/// annotating such getter with the `wasm:import` pragma: +/// +/// ``` +/// @pragma('wasm:memory-type', MemoryType(limits: Limits(1, 10))) +/// @pragma('wasm:import', 'module.name') +/// external Memory get mySecondMemory; +/// ``` +/// +/// ## Restrictions +/// +/// Note that only 32-bit [Memory] instances are supported by Dart at the +/// moment. +@pragma("wasm:entry-point") +final class Memory { + Memory._(); + + /// The size of a page in WebAssembly memory. + static const pageSize = 65536; +} + +/// Operators accessing [Memory] instances in WebAssembly. +extension MemoryAccessExtension on Memory { + /// Returns the size of this memory instance in units of [Memory.pageSize] + /// (65536 bytes). + @pragma("wasm:intrinsic") + external int get size; + + /// Grows the size of this memory instance by the amount of [pages]. + /// + /// This returns the old size (also in units of [Memory.pageSize]) if growing + /// this memory instance was successful, or `-1` otherwise (e.g. due to an + /// out-of-memory error). + @pragma("wasm:intrinsic") + external int grow(int pages); + + /// Copies the byte [value] to the memory region from [startOffset] to + /// [startOffset] plus [length] (exclusive). + /// + /// This causes a WebAssembly trap if the target region is out-of-bounds for + /// this memory. + @pragma("wasm:intrinsic") + external void fill(WasmI32 value, int startOffset, int length); + + @pragma("wasm:intrinsic") + external WasmF32 loadFloat32(int address, {int align = 0, int offset = 0}); + @pragma("wasm:intrinsic") + external WasmF64 loadFloat64(int address, {int align = 0, int offset = 0}); + + @pragma("wasm:intrinsic") + external WasmI32 loadInt8(int address, {int align = 0, int offset = 0}); + @pragma("wasm:intrinsic") + external WasmI32 loadInt16(int address, {int align = 0, int offset = 0}); + @pragma("wasm:intrinsic") + external WasmI32 loadInt32(int address, {int align = 0, int offset = 0}); + @pragma("wasm:intrinsic") + external WasmI64 loadInt64(int address, {int align = 0, int offset = 0}); + + @pragma("wasm:intrinsic") + external WasmI32 loadUint8(int address, {int align = 0, int offset = 0}); + @pragma("wasm:intrinsic") + external WasmI32 loadUint16(int address, {int align = 0, int offset = 0}); + @pragma("wasm:intrinsic") + external WasmI32 loadUint32(int address, {int align = 0, int offset = 0}); + + @pragma("wasm:intrinsic") + external void storeFloat32( + int address, + WasmF32 value, { + int align = 0, + int offset = 0, + }); + @pragma("wasm:intrinsic") + external void storeFloat64( + int address, + WasmF64 value, { + int align = 0, + int offset = 0, + }); + + @pragma("wasm:intrinsic") + external void storeInt8( + int address, + WasmI32 value, { + int align = 0, + int offset = 0, + }); + @pragma("wasm:intrinsic") + external void storeInt16( + int address, + WasmI32 value, { + int align = 0, + int offset = 0, + }); + @pragma("wasm:intrinsic") + external void storeInt32( + int address, + WasmI32 value, { + int align = 0, + int offset = 0, + }); + @pragma("wasm:intrinsic") + external void storeInt64( + int address, + WasmI64 value, { + int align = 0, + int offset = 0, + }); +} diff --git a/sdk/lib/_wasm/wasm_sources.gni b/sdk/lib/_wasm/wasm_sources.gni index 8530e23a9c9..423604609b1 100644 --- a/sdk/lib/_wasm/wasm_sources.gni +++ b/sdk/lib/_wasm/wasm_sources.gni @@ -2,4 +2,7 @@ # 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. -wasm_sdk_sources = [ "wasm_types.dart" ] +wasm_sdk_sources = [ + "memory.dart", + "wasm_types.dart", +] diff --git a/sdk/lib/_wasm/wasm_types.dart b/sdk/lib/_wasm/wasm_types.dart index 9d62cef11e1..2e289f7a5db 100644 --- a/sdk/lib/_wasm/wasm_types.dart +++ b/sdk/lib/_wasm/wasm_types.dart @@ -6,6 +6,8 @@ library dart._wasm; import 'dart:js_interop'; +part 'memory.dart'; + // A collection a special Dart types that are mapped directly to Wasm types // by the dart2wasm compiler. These types have a number of constraints: // diff --git a/tests/web/wasm/deferred_memory_test.dart b/tests/web/wasm/deferred_memory_test.dart new file mode 100644 index 00000000000..d9ac8a4686a --- /dev/null +++ b/tests/web/wasm/deferred_memory_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2026, 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. +// +// dart2wasmOptions=--enable-deferred-loading --extra-compiler-option=--enable-experimental-wasm-interop + +import 'dart:_wasm'; + +import '' deferred as M; + +import 'package:expect/expect.dart'; + +void main() async { + await M.loadLibrary(); + + write(); + Expect.equals(42, M.read()); +} + +void write() { + M.memory.storeInt64(0, WasmI64.fromInt(42)); +} + +@pragma('wasm:memory-type', MemoryType(limits: Limits(1))) +external Memory get memory; + +@pragma('wasm:never-inline') +int read() { + return memory.loadInt64(0).toInt(); +} diff --git a/tests/web/wasm/externs_assert_error_test.dart b/tests/web/wasm/externs_assert_error_test.dart new file mode 100644 index 00000000000..eeffe752f60 --- /dev/null +++ b/tests/web/wasm/externs_assert_error_test.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2026, 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. + +// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop + +import 'dart:_wasm'; + +external Memory get missingAnnotation; +// ^ +// [web] This external getter returns a memory instance, but no annotation describing it was found + +@pragma('wasm:memory-type', MemoryType(limits: Limits(1, 10))) +external Memory get validDefinition; + +void main() { + validDefinition.loadUint8(10); + validDefinition.size; + + print(validDefinition); + // ^ + // [web] WebAssembly elements may only be referenced to directly call a method on them. + + print(validDefinition.fill); + // ^ + // [web] This intrinsic extension member may not be torn off. +} + +void invalidDynamicMemory(Memory memory) { + memory.loadUint8(10); + // ^ + // [web] The receiver of this call must be a top-level variable describing the WebAssembly element. +} + +int get notAConstant => 3; + +void invalidNonConstantArguments() { + validDefinition.loadUint8(0, offset: 12, align: 1); + validDefinition.loadUint8(0, offset: notAConstant); + // ^ + // [web] The variable 'offset' is not a constant, only constant expressions are allowed. + validDefinition.loadUint8(0, align: notAConstant); + // ^ + // [web] The variable 'align' is not a constant, only constant expressions are allowed. +} diff --git a/tests/web/wasm/memory_test.dart b/tests/web/wasm/memory_test.dart new file mode 100644 index 00000000000..1bd84b82f3b --- /dev/null +++ b/tests/web/wasm/memory_test.dart @@ -0,0 +1,60 @@ +// Copyright (c) 2026, 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. + +// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop + +import 'dart:_wasm'; + +import 'package:expect/expect.dart'; + +@pragma('wasm:memory-type', MemoryType(limits: Limits(1, 10))) +external Memory get _memory; + +void main() { + _testSizeAndGrow(); + _testFill(); + + _testFloat(); + _testInt(); + _testOffset(); +} + +void _testSizeAndGrow() { + Expect.equals(1, _memory.size); + Expect.equals(1, _memory.grow(1)); + Expect.equals(2, _memory.size); + + Expect.equals(0, _memory.loadInt32(Memory.pageSize + 1).toIntSigned()); + + Expect.equals(-1, _memory.grow(20)); +} + +void _testFill() { + _memory.fill(WasmI32.fromInt(42), 0, 1024); + for (var i = 0; i < 1024; i++) { + Expect.equals(42, _memory.loadUint8(i).toIntSigned()); + } +} + +void _testFloat() { + _memory.storeFloat64(0, WasmF64.fromDouble(1.5)); + Expect.equals(1.5, _memory.loadFloat64(0).toDouble()); + + _memory.storeFloat32(0, WasmF32.fromDouble(1.5)); + Expect.equals(1.5, _memory.loadFloat32(0).toDouble()); +} + +void _testInt() { + _memory.storeInt8(0, WasmI32.fromInt(-1)); + Expect.equals(-1, _memory.loadInt8(0).toIntSigned()); + Expect.equals(255, _memory.loadUint8(0).toIntUnsigned()); +} + +void _testOffset() { + _memory.storeInt32(0, WasmI32.fromInt(0x01020304)); + Expect.equals(0x01, _memory.loadInt8(0, offset: 3).toIntSigned()); + Expect.equals(0x02, _memory.loadInt8(0, offset: 2).toIntSigned()); + Expect.equals(0x03, _memory.loadInt8(0, offset: 1).toIntSigned()); + Expect.equals(0x04, _memory.loadInt8(0, offset: 0).toIntSigned()); +}