Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 58fd524

Browse files
Use dart:_wasm constructs to avoid dependence on WebAssembly.Function (#46388)
Use the newly exposed functionality in `dart:_wasm` to fix up two different hacks we have: 1) When creating an image from an image source, use `wasm:import` instead of `@Native` and pass the image source directly as an externref. (Direct wasm binding instead of a JS interop shim, yay). 2) When binding the surface callback, previously we were wrapping the callback in a JS function, and then using `WebAssembly.Function` to create a wasm function wrapper around that. Now, we can create a `WasmFuncRef` that is a direct reference to a dart function and pass that over. Now there are no intermediary JavaScript layers when skwasm calls back to us, and we no longer are dependent on the type reflection flag in Chrome. This fixes flutter/flutter#134556
1 parent 845dd9d commit 58fd524

6 files changed

Lines changed: 74 additions & 97 deletions

File tree

lib/web_ui/dev/test_dart2wasm.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ window.onload = async function () {
6464
const skwasmInstance = await skwasm();
6565
window._flutter_skwasmInstance = skwasmInstance;
6666
resolve({
67-
"skwasm": skwasmInstance.asm ?? skwasmInstance.wasmExports,
67+
"skwasm": skwasmInstance.wasmExports,
68+
"skwasmWrapper": skwasmInstance,
6869
"ffi": {
6970
"memory": skwasmInstance.wasmMemory,
7071
}

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@DefaultAsset('skwasm')
66
library skwasm_impl;
77

8+
import 'dart:_wasm';
89
import 'dart:ffi';
910
import 'dart:js_interop';
1011

@@ -39,45 +40,27 @@ external ImageHandle imageCreateFromPixels(
3940
int rowByteCount,
4041
);
4142

42-
// We actually want this function to look something like this:
43-
//
44-
// @Native<ImageHandle Function(
45-
// WasmExternRef,
46-
// Int,
47-
// Int,
48-
// SurfaceHandle,
49-
// )>(symbol: 'image_createFromTextureSource', isLeaf: true)
50-
// external ImageHandle imageCreateFromTextureSource(
51-
// JSAny textureSource,
52-
// int width,
53-
// int height,
54-
// SurfaceHandle handle,
55-
// );
56-
//
57-
// However, this doesn't work because we cannot use extern refs as part of @Native
58-
// annotations currently. For now, we can use JS interop to expose this function
59-
// instead.
60-
extension SkwasmImageExtension on SkwasmInstance {
61-
@JS('wasmExports.image_createFromTextureSource')
62-
external JSNumber imageCreateFromTextureSource(
63-
JSAny textureSource,
64-
JSNumber width,
65-
JSNumber height,
66-
JSNumber surfaceHandle,
67-
);
68-
}
43+
// We use a wasm import directly here instead of @Native since this uses an externref
44+
// in the function signature.
6945
ImageHandle imageCreateFromTextureSource(
7046
JSAny frame,
7147
int width,
7248
int height,
7349
SurfaceHandle handle
7450
) => ImageHandle.fromAddress(
75-
skwasmInstance.imageCreateFromTextureSource(
76-
frame,
77-
width.toJS,
78-
height.toJS,
79-
handle.address.toJS,
80-
).toDartInt
51+
imageCreateFromTextureSourceImpl(
52+
externRefForJSAny(frame),
53+
width.toWasmI32(),
54+
height.toWasmI32(),
55+
handle.address.toWasmI32(),
56+
).toIntUnsigned()
57+
);
58+
@pragma('wasm:import', 'skwasm.image_createFromTextureSource')
59+
external WasmI32 imageCreateFromTextureSourceImpl(
60+
WasmExternRef? frame,
61+
WasmI32 width,
62+
WasmI32 height,
63+
WasmI32 surfaceHandle,
8164
);
8265

8366
@Native<Void Function(ImageHandle)>(symbol:'image_ref', isLeaf: true)

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:_wasm';
56
import 'dart:js_interop';
67

78
@JS()
@@ -17,37 +18,11 @@ extension WebAssemblyMemoryExtension on WebAssemblyMemory {
1718
class SkwasmInstance {}
1819

1920
extension SkwasmInstanceExtension on SkwasmInstance {
20-
external JSNumber getEmptyTableSlot();
21-
22-
// The function here *must* be a directly exported wasm function, not a
23-
// JavaScript function. If you actually need to add a JavaScript function,
24-
// use `addFunction` instead.
25-
external void setWasmTableEntry(JSNumber index, JSAny function);
26-
27-
external JSNumber addFunction(WebAssemblyFunction function);
28-
external void removeFunction(JSNumber functionPointer);
29-
3021
external WebAssemblyMemory get wasmMemory;
3122
}
3223

3324
@JS('window._flutter_skwasmInstance')
3425
external SkwasmInstance get skwasmInstance;
3526

36-
@JS()
37-
@staticInterop
38-
@anonymous
39-
class WebAssemblyFunctionType {
40-
external factory WebAssemblyFunctionType({
41-
required JSArray parameters,
42-
required JSArray results,
43-
});
44-
}
45-
46-
@JS('WebAssembly.Function')
47-
@staticInterop
48-
class WebAssemblyFunction {
49-
external factory WebAssemblyFunction(
50-
WebAssemblyFunctionType functionType,
51-
JSFunction function
52-
);
53-
}
27+
@pragma('wasm:import', 'skwasmWrapper.addFunction')
28+
external WasmI32 addFunction(WasmFuncRef function);

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:_wasm';
56
import 'dart:async';
67
import 'dart:ffi';
78
import 'dart:js_interop';
@@ -11,7 +12,52 @@ import 'package:ui/src/engine.dart';
1112
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
1213
import 'package:ui/ui.dart' as ui;
1314

15+
@pragma('wasm:export')
16+
WasmVoid callbackHandler(WasmI32 callbackId, WasmI32 context, WasmExternRef? jsContext) {
17+
// Actually hide this call behind whether skwasm is enabled. Otherwise, the SkwasmCallbackHandler
18+
// won't actually be tree-shaken, and we end up with skwasm imports in non-skwasm builds.
19+
if (FlutterConfiguration.flutterWebUseSkwasm) {
20+
SkwasmCallbackHandler.instance.handleCallback(callbackId, context, jsContext);
21+
}
22+
return WasmVoid();
23+
}
24+
25+
// This class handles callbacks coming from Skwasm by keeping a map of callback IDs to Completers
26+
class SkwasmCallbackHandler {
27+
SkwasmCallbackHandler._withCallbackPointer(this.callbackPointer);
28+
29+
factory SkwasmCallbackHandler._() {
30+
final WasmFuncRef wasmFunction = WasmFunction<WasmVoid Function(WasmI32, WasmI32, WasmExternRef?)>.fromFunction(callbackHandler);
31+
final int functionIndex = addFunction(wasmFunction).toIntUnsigned();
32+
return SkwasmCallbackHandler._withCallbackPointer(
33+
OnRenderCallbackHandle.fromAddress(functionIndex)
34+
);
35+
}
36+
static SkwasmCallbackHandler instance = SkwasmCallbackHandler._();
1437

38+
final OnRenderCallbackHandle callbackPointer;
39+
final Map<CallbackId, Completer<JSAny>> _pendingCallbacks = <int, Completer<JSAny>>{};
40+
41+
// Returns a future that will resolve when Skwasm calls back with the given callbackID
42+
Future<JSAny> registerCallback(int callbackId) {
43+
final Completer<JSAny> completer = Completer<JSAny>();
44+
_pendingCallbacks[callbackId] = completer;
45+
return completer.future;
46+
}
47+
48+
void handleCallback(WasmI32 callbackId, WasmI32 context, WasmExternRef? jsContext) {
49+
// Skwasm can either callback with a JS object (an externref) or it can call back
50+
// with a simple integer, which usually refers to a pointer on its heap. In order
51+
// to coerce these into a single type, we just make the completers take a JSAny
52+
// that either contains the JS object or a JSNumber that contains the integer value.
53+
final Completer<JSAny> completer = _pendingCallbacks.remove(callbackId.toIntUnsigned())!;
54+
if (!jsContext.isNull) {
55+
completer.complete(jsContext!.toJS);
56+
} else {
57+
completer.complete(context.toIntUnsigned().toJS);
58+
}
59+
}
60+
}
1561

1662
class SkwasmSurface {
1763
factory SkwasmSurface() {
@@ -25,32 +71,16 @@ class SkwasmSurface {
2571

2672
SkwasmSurface._fromHandle(this.handle) : threadId = surfaceGetThreadId(handle);
2773
final SurfaceHandle handle;
28-
OnRenderCallbackHandle _callbackHandle = nullptr;
29-
final Map<CallbackId, Completer<JSAny>> _pendingCallbacks = <int, Completer<JSAny>>{};
3074

3175
final int threadId;
3276

3377
void _initialize() {
34-
final WebAssemblyFunction wasmFunction = WebAssemblyFunction(
35-
WebAssemblyFunctionType(
36-
parameters: <JSString>[
37-
'i32'.toJS,
38-
'i32'.toJS,
39-
'externref'.toJS
40-
].toJS,
41-
results: <JSString>[].toJS
42-
),
43-
_callbackHandler.toJS,
44-
);
45-
_callbackHandle = OnRenderCallbackHandle.fromAddress(
46-
skwasmInstance.addFunction(wasmFunction).toDartInt,
47-
);
48-
surfaceSetCallbackHandler(handle, _callbackHandle);
78+
surfaceSetCallbackHandler(handle, SkwasmCallbackHandler.instance.callbackPointer);
4979
}
5080

5181
Future<DomImageBitmap> renderPicture(SkwasmPicture picture) async {
5282
final int callbackId = surfaceRenderPicture(handle, picture.handle);
53-
final DomImageBitmap bitmap = (await _registerCallback(callbackId)) as DomImageBitmap;
83+
final DomImageBitmap bitmap = (await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as DomImageBitmap;
5484
return bitmap;
5585
}
5686

@@ -60,7 +90,7 @@ class SkwasmSurface {
6090
image.handle,
6191
format.index,
6292
);
63-
final int context = (await _registerCallback(callbackId) as JSNumber).toDartInt;
93+
final int context = (await SkwasmCallbackHandler.instance.registerCallback(callbackId) as JSNumber).toDartInt;
6494
final SkDataHandle dataHandle = SkDataHandle.fromAddress(context);
6595
final int byteCount = skDataGetSize(dataHandle);
6696
final Pointer<Uint8> dataPointer = skDataGetConstPointer(dataHandle).cast<Uint8>();
@@ -72,23 +102,7 @@ class SkwasmSurface {
72102
return ByteData.sublistView(output);
73103
}
74104

75-
Future<JSAny> _registerCallback(int callbackId) {
76-
final Completer<JSAny> completer = Completer<JSAny>();
77-
_pendingCallbacks[callbackId] = completer;
78-
return completer.future;
79-
}
80-
81-
void _callbackHandler(JSNumber callbackId, JSNumber context, JSAny? jsContext) {
82-
final Completer<JSAny> completer = _pendingCallbacks.remove(callbackId.toDartInt)!;
83-
if (jsContext.isUndefinedOrNull) {
84-
completer.complete(context);
85-
} else {
86-
completer.complete(jsContext);
87-
}
88-
}
89-
90105
void dispose() {
91106
surfaceDestroy(handle);
92-
skwasmInstance.removeFunction(_callbackHandle.address.toJS);
93107
}
94108
}

web_sdk/sdk_rewriter.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ List<String> getExtraImportsForLibrary(String libraryName) {
178178
extraImports.add(entry.value);
179179
}
180180
}
181+
if (libraryName == 'skwasm_impl') {
182+
extraImports.add("import 'dart:_wasm';");
183+
}
181184
return extraImports;
182185
}
183186

web_sdk/test/sdk_rewriter_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ void printSomething() {
184184
"import 'dart:_web_unicode';",
185185
"import 'dart:_web_test_fonts';",
186186
"import 'dart:_web_locale_keymap' as locale_keymap;",
187+
"import 'dart:_wasm';",
187188
]);
188189

189190
// Other libraries (should not have extra imports).

0 commit comments

Comments
 (0)