2d78883f27
Right now a JS interop callback works like this: * Each wasm module that gets instantiated will be given it's module instance (JS calls Dart to set it) via `setThisModule` * When Dart code calls JS and gives it a callback to invoke, it gave it this module instance. It will also make the callback wasm function weakly exported. * The JS trampoline code, when invoked, would then call the weakly exported wasm function from the module instance. We simplify this now by making the Dart code simply give the wasm function reference to JS, then JS can later on invoke it. No need to weakly export a function and call back via `module.exports.<weaklyExportedCallback>` To ensure binaryen is aware that the wasm function may be called from JS, we annotate it via the `(@binaryen.js.called)` annotation. Change-Id: I828dd0cf8d3b36db338792c4e277a4bb94c76faf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/511080 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
176 lines
5.9 KiB
Dart
176 lines
5.9 KiB
Dart
// 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 'package:kernel/core_types.dart';
|
|
import 'js/util.dart' show JsInteropMemberData;
|
|
import 'translator.dart' show TranslatorOptions;
|
|
import 'util.dart';
|
|
|
|
/// Provides wasm import/export names for a module.
|
|
///
|
|
/// Each instance of [Namer] defines a namespace. Uniqueness is only guaranteed
|
|
/// within a namespace. Names passed to [getName] must themselves be unique for
|
|
/// a given namespace.
|
|
///
|
|
/// If [jsSafeNames] is true, the returned names will be safe to use as a
|
|
/// JavaScript identifier. This is useful if the name will be used as a key in a
|
|
/// JS object. Names only referenced from wasm modules can be any ASCII string.
|
|
///
|
|
/// Each name should appear exactly once in the wasm module (defining the
|
|
/// import/export) and JS code so we don't consider frequency when naming.
|
|
class Namer {
|
|
final bool minify;
|
|
final Set<String> _usedNames = {};
|
|
int _nextId = 0;
|
|
|
|
Namer({this.minify = false});
|
|
|
|
/// Mark a name as reserved in case the name might collide with a
|
|
/// pre-specified name such as an export.
|
|
void reserveName(String name) {
|
|
if (!minify) return;
|
|
final added = _usedNames.add(name);
|
|
assert(added, "Name '$name' is already reserved");
|
|
}
|
|
|
|
static String _jsSanitizeName(String name) {
|
|
return name.replaceAll(RegExp(r'[^a-zA-Z0-9_$]'), '_');
|
|
}
|
|
|
|
/// Get an assigned name for the given name.
|
|
///
|
|
|
|
String getName(String name, {bool jsSafeName = false}) {
|
|
if (!minify) {
|
|
String basename = name = jsSafeName ? _jsSanitizeName(name) : name;
|
|
int counter = 0;
|
|
while (!_usedNames.add(name)) {
|
|
name = '${basename}_${counter++}';
|
|
}
|
|
return name;
|
|
}
|
|
do {
|
|
name = jsSafeName
|
|
? intToMinJsSafeString(_nextId++)
|
|
: intToMinString(_nextId++);
|
|
} while (!_usedNames.add(name));
|
|
return name;
|
|
}
|
|
}
|
|
|
|
/// Manages naming for external interop members.
|
|
///
|
|
/// Members annotated with `@pragma('wasm:import', '...')` or
|
|
/// `@pragma('wasm:export', '...')` or `@pragma('wasm:weak-export', '...')`
|
|
/// are considered external members. These members are visible from outside the
|
|
/// Dart program and are therefore not minified. We do make sure that other
|
|
/// names do not collide with these names.
|
|
class _ExternalMemberNamer {
|
|
final Namer _exportNamer;
|
|
final CoreTypes coreTypes;
|
|
|
|
_ExternalMemberNamer(this.coreTypes, this._exportNamer);
|
|
ImportName? getImportName(Member member) {
|
|
return getWasmImportPragma(coreTypes, member);
|
|
}
|
|
|
|
String? getExportName(Member member) {
|
|
// External export names should be registered already so we don't have to
|
|
// reserve them with the namer here.
|
|
return getWasmExportPragma(coreTypes, member) ??
|
|
getWasmWeakExportPragma(coreTypes, member);
|
|
}
|
|
|
|
bool registerExportName(Member member) {
|
|
final name = getWasmExportPragma(coreTypes, member);
|
|
if (name != null) {
|
|
_exportNamer.reserveName(name);
|
|
return true;
|
|
}
|
|
final weakName = getWasmWeakExportPragma(coreTypes, member);
|
|
if (weakName != null) {
|
|
_exportNamer.reserveName(weakName);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Manages naming for internal interop helper members.
|
|
///
|
|
/// These members may import code from the support JS modules but are not
|
|
/// visible from outside the Dart program and are only used by the Dart runtime.
|
|
/// Their names can therefore be minified.
|
|
class _InteropHelperMemberNamer {
|
|
final Namer _interopHelperNamer;
|
|
final CoreTypes coreTypes;
|
|
final String interopModuleName;
|
|
final Map<Member, String> interopMemberNames = {};
|
|
|
|
_InteropHelperMemberNamer(this.coreTypes, TranslatorOptions options)
|
|
: interopModuleName = options.minify ? '_' : 'dart2wasm',
|
|
_interopHelperNamer = Namer(
|
|
minify: options.minify || options.minifyInteropNames,
|
|
);
|
|
|
|
ImportName? getImportName(Member member) {
|
|
final annotationInfo = JsInteropMemberData.fromMember(member, coreTypes);
|
|
if (annotationInfo == null) return null;
|
|
if (annotationInfo.isImport) {
|
|
return ImportName(
|
|
interopModuleName,
|
|
interopMemberNames[member] ??= _interopHelperNamer.getName(
|
|
member.name.text,
|
|
jsSafeName: true,
|
|
),
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Manages naming for [Member]s associated with JS interop.
|
|
///
|
|
/// Some of these members are visible from JS outside of the Dart program and
|
|
/// are therefore not minified. Others are only helpers used by the Dart runtime
|
|
/// and can be minified.
|
|
class InteropMemberNamer {
|
|
final _ExternalMemberNamer _externalMemberNamer;
|
|
final _InteropHelperMemberNamer _interopHelperMemberNamer;
|
|
|
|
InteropMemberNamer(
|
|
CoreTypes coreTypes,
|
|
Namer exportNamer,
|
|
TranslatorOptions options,
|
|
) : _externalMemberNamer = _ExternalMemberNamer(coreTypes, exportNamer),
|
|
_interopHelperMemberNamer = _InteropHelperMemberNamer(coreTypes, options);
|
|
|
|
String get interopHelperModuleName =>
|
|
_interopHelperMemberNamer.interopModuleName;
|
|
|
|
/// Returns the import name for the given member.
|
|
///
|
|
/// Returns null if the member is not an import. Checks both external and
|
|
/// interop helper imports.
|
|
ImportName? getImportName(Member member) {
|
|
return _externalMemberNamer.getImportName(member) ??
|
|
_interopHelperMemberNamer.getImportName(member);
|
|
}
|
|
|
|
/// Returns the export name for the given member.
|
|
///
|
|
/// Returns null if the member is not an export. Checks both external and
|
|
/// interop helper exports.
|
|
String? getExportName(Member member) {
|
|
return _externalMemberNamer.getExportName(member);
|
|
}
|
|
|
|
/// Registers the export name for the given member with the [Namer].
|
|
///
|
|
/// Returns true if the member is a strong external export.
|
|
bool registerExternalExportName(Member member) {
|
|
return _externalMemberNamer.registerExportName(member);
|
|
}
|
|
}
|