Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dart2wasm] Compiling acx results in massive duplication of imports #60384

Closed
mkustermann opened this issue Mar 24, 2025 · 1 comment
Closed
Assignees
Labels
area-dart2wasm Issues for the dart2wasm compiler.

Comments

@mkustermann
Copy link
Member

When compiling ACX gallery it seems to result in massive duplication of imports. What I see is

The acx.mjs file has many

    // Imports
    const dart2wasm = {
      ...
      _89: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _90: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _91: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _92: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _93: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _94: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _95: (x0,x1,x2) => x0.setAttribute(x1,x2),
      _96: (x0,x1,x2) => x0.setAttribute(x1,x2),
     ...
  }

similarly the wasm file has corresponding imports

...
  (func $dart2wasm._88 (import) (;12855;) (import "dart2wasm" "_88") (param externref externref externref) (result externref))
  (func $dart2wasm._89 (import) (;12856;) (import "dart2wasm" "_89") (param externref externref externref) (result externref))
  (func $dart2wasm._90 (import) (;12857;) (import "dart2wasm" "_90") (param externref externref externref) (result externref))
  (func $dart2wasm._91 (import) (;12858;) (import "dart2wasm" "_91") (param externref externref externref) (result externref))
  (func $dart2wasm._92 (import) (;12859;) (import "dart2wasm" "_92") (param externref externref externref) (result externref))
  (func $dart2wasm._93 (import) (;12860;) (import "dart2wasm" "_93") (param externref externref externref) (result externref))
  (func $dart2wasm._94 (import) (;12861;) (import "dart2wasm" "_94") (param externref externref externref) (result externref))
  (func $dart2wasm._95 (import) (;12862;) (import "dart2wasm" "_95") (param externref externref externref) (result externref))
  (func $dart2wasm._96 (import) (;12863;) (import "dart2wasm" "_96") (param externref externref externref) (result externref))
...

It's not very clear why this is the case, the source code only contains one function

 extension type Element._(JSObject _) implements Node, JSObject {
  ...
  external void setAttribute(
    String qualifiedName,
    String value,
  );
}

It's almost as every call site to setAttribute will get it's own entry in the mjs file and corresponding wasm import.

Though this is bad for code size, startup and performance (as JS would need to compile all of those closures - even though they perform the same work)

/cc @srujzs @osa1

@mkustermann mkustermann added the area-dart2wasm Issues for the dart2wasm compiler. label Mar 24, 2025
@osa1 osa1 self-assigned this Mar 24, 2025
@osa1
Copy link
Member

osa1 commented Mar 24, 2025

I think I found the issue but run out of time before fixing it.

The transformer that transforms interop functions creates specializers for members here:

_Specializer? _getSpecializerForMember(Procedure node, String jsString,
[StaticInvocation? invocation]) {
if (invocation == null) {
if (_extensionIndex.isGetter(node)) {
return _GetterSpecializer(this, node, jsString);
} else if (_extensionIndex.isSetter(node)) {
return _SetterSpecializer(this, node, jsString);
} else if (_extensionIndex.isOperator(node)) {
return _OperatorSpecializer(this, node, jsString);
} else if (_extensionIndex.isMethod(node)) {
return _MethodSpecializer(this, node, jsString);
}
} else {
if (_extensionIndex.isMethod(node)) {
return _MethodInvocationSpecializer(this, node, jsString, invocation);
}
}
return null;
}

These specializer are created per invocation (the else branch):

Expression visitStaticInvocation(StaticInvocation node) {
node = super.visitStaticInvocation(node) as StaticInvocation;
Procedure target = node.target;
if (target == _util.allowInteropTarget) {
return _callbackSpecializer.allowInterop(node);
} else if (target == _util.functionToJSTarget) {
return _callbackSpecializer.functionToJS(node);
} else if (target == _util.functionToJSCaptureThisTarget) {
return _callbackSpecializer.functionToJS(node, captureThis: true);
} else if (target == _util.inlineJSTarget) {
return _inlineExpander.expand(node);
} else {
return _interopSpecializerFactory.maybeSpecializeInvocation(
target, node) ??
node;
}
}

So if I see invocation of a @JS member 100 times I create 100 specializers.

Each of these specializers creates an import:

Procedure _getRawInteropProcedure() {
// Initialize variable declarations.
List<String> jsParameterStrings = [];
List<VariableDeclaration> dartPositionalParameters = [];
for (int j = 0; j < parameters.length; j++) {
String parameterString = 'x$j';
dartPositionalParameters.add(VariableDeclaration(parameterString,
type: _util.nullableWasmExternRefType, isSynthesized: true));
jsParameterStrings.add(parameterString);
}
// Create Dart procedure stub for JS method.
String jsMethodName = _methodCollector.generateMethodName();
final dartProcedure = _methodCollector.addInteropProcedure(
'|$jsMethodName',
'dart2wasm.$jsMethodName',
FunctionNode(null,
positionalParameters: dartPositionalParameters,
returnType: _util.nullableWasmExternRefType),
fileUri,
AnnotationType.import,
isExternal: true);
_methodCollector.addMethod(
dartProcedure, jsMethodName, generateJS(jsParameterStrings));
return dartProcedure;
}

The addMethod here caches the procedures:

void addMethod(Procedure dartProcedure, String methodName, String code) {
jsMethods[dartProcedure] = (importName: methodName, jsCode: code);
}

But we create a procedure for each specializer, so we still create a procedure for each use site. I think this cache is never hit currently.

I think we need to be caching the specializers, there should be one per called procedure.

I'll try to fix this tomorrow.


Fix in https://dart-review.googlesource.com/c/sdk/+/417741.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-dart2wasm Issues for the dart2wasm compiler.
Projects
None yet
Development

No branches or pull requests

2 participants