From 034b90af5d8192973b53269f746f645a7d6278dd Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Wed, 10 Jun 2026 02:34:44 -0700 Subject: [PATCH] [dart2wasm] Allow using experimental `dart:ffi` in deferred modules The experimental `dart:ffi` support predates deferred loading implementation. Any use of FFI features would lazily import `ffi.memory` into the (fixed coded) main module. This CL now lazily imports the `ffi.memory` module into all modules that operate on the memory. => This will now allow a flutter app that is compiled with dart2wasm in deferred loading mode to possibly defer parts of flutter engine code that uses FFI into deferred modules. Issue https://github.com/dart-lang/sdk/issues/56952 Change-Id: Idf821ce2dd92c36ac1be9dae24307516a1ae86d9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510760 Reviewed-by: Slava Egorov Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/intrinsics.dart | 39 ++++++++++--------- pkg/dart2wasm/lib/translator.dart | 27 +++++++------ .../wasm/ffi/ffi_native_deferred_test.dart | 13 +++++++ 3 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 tests/web/wasm/ffi/ffi_native_deferred_test.dart diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 47424bcd982..5212239734e 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -1610,42 +1610,43 @@ class Intrinsifier { b.i32_add(); offset = 0; } + final memory = translator.ffiMemory(b.moduleBuilder); switch (intrinsic) { case StaticIntrinsic.loadInt8: - b.i64_load8_s(translator.ffiMemory, offset); + b.i64_load8_s(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadUint8: - b.i64_load8_u(translator.ffiMemory, offset); + b.i64_load8_u(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadInt16: - b.i64_load16_s(translator.ffiMemory, offset); + b.i64_load16_s(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadUint16: - b.i64_load16_u(translator.ffiMemory, offset); + b.i64_load16_u(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadInt32: - b.i64_load32_s(translator.ffiMemory, offset); + b.i64_load32_s(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadUint32: - b.i64_load32_u(translator.ffiMemory, offset); + b.i64_load32_u(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadInt64: case StaticIntrinsic.loadUint64: - b.i64_load(translator.ffiMemory, offset); + b.i64_load(memory, offset); return w.NumType.i64; case StaticIntrinsic.loadFloat: - b.f32_load(translator.ffiMemory, offset); + b.f32_load(memory, offset); b.f64_promote_f32(); return w.NumType.f64; case StaticIntrinsic.loadFloatUnaligned: - b.f32_load(translator.ffiMemory, offset, 0); + b.f32_load(memory, offset, 0); b.f64_promote_f32(); return w.NumType.f64; case StaticIntrinsic.loadDouble: - b.f64_load(translator.ffiMemory, offset); + b.f64_load(memory, offset); return w.NumType.f64; case StaticIntrinsic.loadDoubleUnaligned: - b.f64_load(translator.ffiMemory, offset, 0); + b.f64_load(memory, offset, 0); return w.NumType.f64; case StaticIntrinsic.storeInt8: case StaticIntrinsic.storeUint8: @@ -1653,7 +1654,7 @@ class Intrinsifier { node.arguments.positional[2], w.NumType.i64, ); - b.i64_store8(translator.ffiMemory, offset); + b.i64_store8(memory, offset); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeInt16: @@ -1662,7 +1663,7 @@ class Intrinsifier { node.arguments.positional[2], w.NumType.i64, ); - b.i64_store16(translator.ffiMemory, offset); + b.i64_store16(memory, offset); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeInt32: @@ -1671,7 +1672,7 @@ class Intrinsifier { node.arguments.positional[2], w.NumType.i64, ); - b.i64_store32(translator.ffiMemory, offset); + b.i64_store32(memory, offset); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeInt64: @@ -1680,7 +1681,7 @@ class Intrinsifier { node.arguments.positional[2], w.NumType.i64, ); - b.i64_store(translator.ffiMemory, offset); + b.i64_store(memory, offset); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeFloat: @@ -1689,7 +1690,7 @@ class Intrinsifier { w.NumType.f64, ); b.f32_demote_f64(); - b.f32_store(translator.ffiMemory, offset); + b.f32_store(memory, offset); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeFloatUnaligned: @@ -1698,7 +1699,7 @@ class Intrinsifier { w.NumType.f64, ); b.f32_demote_f64(); - b.f32_store(translator.ffiMemory, offset, 0); + b.f32_store(memory, offset, 0); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeDouble: @@ -1706,7 +1707,7 @@ class Intrinsifier { node.arguments.positional[2], w.NumType.f64, ); - b.f64_store(translator.ffiMemory, offset); + b.f64_store(memory, offset); b.ref_null(w.HeapType.none); return translator.topType; case StaticIntrinsic.storeDoubleUnaligned: @@ -1714,7 +1715,7 @@ class Intrinsifier { node.arguments.positional[2], w.NumType.f64, ); - b.f64_store(translator.ffiMemory, offset, 0); + b.f64_store(memory, offset, 0); b.ref_null(w.HeapType.none); return translator.topType; default: diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 7e608198c28..446aa33ea58 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -204,16 +204,22 @@ class Translator with KernelNodes { w.StructType("void"), nullable: true, ); - // Lazily import FFI memory if used. - late final w.Memory ffiMemory = mainModule.memories.import( - "ffi", - "memory", - options.importSharedMemory, - 0, - options.sharedMemoryMaxPages, - ); final Map _memories = {}; + // Lazily import FFI memory if used. + final _ffiMemoryImports = {}; + w.Memory ffiMemory(w.ModuleBuilder usingModule) { + return _ffiMemoryImports.putIfAbsent(usingModule, () { + return usingModule.memories.import( + "ffi", + "memory", + options.importSharedMemory, + 0, + options.sharedMemoryMaxPages, + ); + }); + } + /// Maps record shapes to the record class for the shape. Classes generated /// by `record_class_generator` library. final Map recordClasses; @@ -2192,10 +2198,7 @@ class Translator with KernelNodes { return _shouldInlineProcedureCall(target, signature, member as Procedure); } - InliningDecision _shouldInlineFieldAccessor( - Reference target, - Field field, - ) { + InliningDecision _shouldInlineFieldAccessor(Reference target, Field field) { if (field.isInstanceMember) { // Implicit instance getters are just loads. if (target.isImplicitGetter) { diff --git a/tests/web/wasm/ffi/ffi_native_deferred_test.dart b/tests/web/wasm/ffi/ffi_native_deferred_test.dart new file mode 100644 index 00000000000..42db2b4ecb3 --- /dev/null +++ b/tests/web/wasm/ffi/ffi_native_deferred_test.dart @@ -0,0 +1,13 @@ +// 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-ffi --enable-deferred-loading +// SharedObjects=ffi_native_test_module + +import 'ffi_native_test.dart' deferred as D; + +main() async { + await D.loadLibrary(); + D.main(); +}