From 2c8f4300d0824f6018724d6e0fc2c59ec5cf9cd6 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Thu, 5 Mar 2026 11:10:13 -0800 Subject: [PATCH] [dart2wasm] Fix `@pragma('wasm:initialize-at-startup')` in deferred loading scenario The module owning the storage for the static field has to emit the initialization code in it's start function. Change-Id: I9089e99d6e12ea275343cebe3e456658a7c7a540 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/485480 Reviewed-by: Nate Biggs Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/dynamic_modules.dart | 5 ++- pkg/dart2wasm/lib/globals.dart | 14 +++---- pkg/dart2wasm/lib/translator.dart | 5 +-- pkg/dart2wasm/test/ir_test.dart | 1 + .../ir_tests/deferred.init_at_startup.dart | 29 +++++++++++++++ .../ir_tests/deferred.init_at_startup.wat | 34 +++++++++++++++++ .../deferred.init_at_startup_module1.wat | 37 +++++++++++++++++++ .../wasm/deferred_init_at_startup_test.dart | 24 ++++++++++++ 8 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.dart create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat create mode 100644 pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat create mode 100644 tests/web/wasm/deferred_init_at_startup_test.dart diff --git a/pkg/dart2wasm/lib/dynamic_modules.dart b/pkg/dart2wasm/lib/dynamic_modules.dart index c48590190b5..812564f7a9e 100644 --- a/pkg/dart2wasm/lib/dynamic_modules.dart +++ b/pkg/dart2wasm/lib/dynamic_modules.dart @@ -650,8 +650,9 @@ class DynamicModuleInfo { } void finishDynamicModule() { - _registerModuleRefs( - isSubmodule ? initFunction.body : translator.initFunction.body); + _registerModuleRefs(isSubmodule + ? initFunction.body + : translator.mainModule.startFunction.body); } void _registerModuleRefs(w.InstructionsBuilder b) { diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart index d7a6f4f6345..f0fc11f983b 100644 --- a/pkg/dart2wasm/lib/globals.dart +++ b/pkg/dart2wasm/lib/globals.dart @@ -160,15 +160,11 @@ class DartGlobals { final definition = _defineGlobalBasedField(field, fieldType, module, true, init, null); - if (module.module == translator.initFunction.enclosingModule) { - // We have to initialize the global field in the same module as where - // the field value is defined in. - // TODO: Once dynamic modules only compile code for the submodule and - // not the main module, we should turn this into an assert. - EagerStaticFieldInitializerCodeGenerator( - translator, field, definition.global) - .generate(translator.initFunction.body, [], null); - } + // We have to initialize the global field in the same module as where + // the field value is defined in. + EagerStaticFieldInitializerCodeGenerator( + translator, field, definition.global) + .generate(module.startFunction.body, [], null); return definition; } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index c2bb7ff71b5..9f196f0c99a 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -247,7 +247,6 @@ class Translator with KernelNodes { final Set membersContainingInnerFunctions = {}; final Set membersBeingGenerated = {}; final Map constructorClosures = {}; - late final w.FunctionBuilder initFunction; late final w.ValueType voidMarker = w.RefType.def(w.StructType("void"), nullable: true); // Lazily import FFI memory if used. @@ -595,8 +594,6 @@ class Translator with KernelNodes { Map translate( Uri Function(String moduleName)? sourceMapUrlGenerator) { _initModules(sourceMapUrlGenerator); - initFunction = mainModule.startFunction; - closureLayouter.collect(); classInfoCollector.collect(); @@ -637,7 +634,7 @@ class Translator with KernelNodes { } }); } - _printFunction(initFunction, "init"); + _printFunction(mainModule.startFunction, "init"); // Remove empty modules. _outputToBuilder.removeWhere((outputModule, moduleBuilder) { diff --git a/pkg/dart2wasm/test/ir_test.dart b/pkg/dart2wasm/test/ir_test.dart index 5321f22ad24..326f5bfd784 100644 --- a/pkg/dart2wasm/test/ir_test.dart +++ b/pkg/dart2wasm/test/ir_test.dart @@ -64,6 +64,7 @@ void main(List args) async { 'pkg/dart2wasm/tool/compile_benchmark', for (final option in compilerOptions) '--extra-compiler-option=$option', '--extra-compiler-option=--no-unique-constant-names', + '--extra-compiler-option=--enable-experimental-wasm-interop', if (runFromSource) '--src', '--no-strip-wasm', '-o', diff --git a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.dart b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.dart new file mode 100644 index 00000000000..47b57df62ec --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.dart @@ -0,0 +1,29 @@ +// 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=write|read|#init +// tableFilter=cross-module-funcs +// globalFilter=array +// typeFilter=NoMatch +// compilerOption=--enable-deferred-loading +// compilerOption=--no-minify + +import 'dart:_wasm'; + +import '' deferred as D; + +import 'package:expect/expect.dart'; + +void main() async { + await D.loadLibrary(); + + D.write(); + Expect.equals('hello', D.read()); +} + +void write() => array[0] = 'hello'; +String read() => array[0]!; + +@pragma("wasm:initialize-at-startup") +final WasmArray array = WasmArray(1); diff --git a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat new file mode 100644 index 00000000000..7bb73d1e0ab --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat @@ -0,0 +1,34 @@ +(module $module0 + (type $#Top <...>) + (type $Array <...>) + (type $Array <...>) + (type $Array <...>) + (type $JSExternWrapper <...>) + (type $Object <...>) + (table $cross-module-funcs-0 (export "cross-module-funcs-0") 3 funcref) + (global $"\"1.0\"" (ref $JSExternWrapper) <...>) + (global $BoxedDouble._cacheKeys (mut (ref $Array)) <...>) + (global $BoxedDouble._cacheValues (mut (ref $Array)) <...>) + (global $JSStringImpl._stringFromCodePointBuffer (mut (ref $Array)) <...>) + (global $_deletedDataMarker (mut (ref $#Top)) <...>) + (elem $cross-module-funcs-0 + (set 2 (ref.func $"_TypeError._throwNullCheckErrorWithCurrentStack "))) + (func $_TypeError._throwNullCheckErrorWithCurrentStack (result (ref none)) <...>) + (func $#init + global.get $"\"1.0\"" + i32.const 16 + array.new $Array + global.set $BoxedDouble._cacheValues + i64.const 4607182418800017408 + i32.const 16 + array.new $Array + global.set $BoxedDouble._cacheKeys + i32.const 2 + array.new_default $Array + global.set $JSStringImpl._stringFromCodePointBuffer + i32.const 1 + i32.const 0 + struct.new $Object + global.set $_deletedDataMarker + ) +) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat new file mode 100644 index 00000000000..f1773e583e1 --- /dev/null +++ b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat @@ -0,0 +1,37 @@ +(module $module1 + (type $#Top <...>) + (type $Array <...>) + (type $JSExternWrapper <...>) + (global $"\"hello\"" (import "module0" "global0") (ref $JSExternWrapper)) + (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 3 funcref) + (global $array (mut (ref $Array)) + (array.new_fixed $Array 0)) + (elem $module0.cross-module-funcs-0 + (set 0 (ref.func $write)) + (set 1 (ref.func $read))) + (func $#init + i32.const 1 + array.new_default $Array + global.set $array + ) + (func $read (result (ref $JSExternWrapper)) + block $label0 (result (ref $JSExternWrapper)) + global.get $array + i32.const 0 + array.get $Array + br_on_non_null $label0 + i32.const 2 + call_indirect (result (ref none)) + unreachable + end $label0 + ) + (func $write (result (ref null $#Top)) + (local $var0 (ref $JSExternWrapper)) + global.get $array + i32.const 0 + global.get $"\"hello\"" + local.tee $var0 + array.set $Array + local.get $var0 + ) +) \ No newline at end of file diff --git a/tests/web/wasm/deferred_init_at_startup_test.dart b/tests/web/wasm/deferred_init_at_startup_test.dart new file mode 100644 index 00000000000..5db571b37b8 --- /dev/null +++ b/tests/web/wasm/deferred_init_at_startup_test.dart @@ -0,0 +1,24 @@ +// 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 D; + +import 'package:expect/expect.dart'; + +void main() async { + await D.loadLibrary(); + + D.write(); + Expect.equals('hello', D.read()); +} + +void write() => array[0] = 'hello'; +String read() => array[0]!; + +@pragma("wasm:initialize-at-startup") +final WasmArray array = WasmArray(1);