blob: 089d8d7805b3988db26a6df2b875ab58cf65cefd [file] [log] [blame] [edit]
// Copyright (c) 2023, 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/type_environment.dart';
import 'method_collector.dart';
import 'util.dart';
/// Expands inline JS calls to trampolines that call functions in the JS
/// runtime.
class InlineExpander {
final StatefulStaticTypeContext _staticTypeContext;
final CoreTypesUtil _util;
final MethodCollector _methodCollector;
late String _inlineJSImportName;
bool _replaceProcedureWithInlineJS = false;
InlineExpander(this._staticTypeContext, this._util, this._methodCollector);
void enterProcedure() {
// Under very restricted circumstances, we will make a procedure
// external and clear it's body. See the description on
// [expand] for more details.
_replaceProcedureWithInlineJS = false;
}
void exitProcedure(Procedure node) {
if (_replaceProcedureWithInlineJS) {
node.isStatic = true;
node.isExternal = true;
node.function.body = null;
_util.annotateProcedure(node, _inlineJSImportName, AnnotationType.import);
_replaceProcedureWithInlineJS = false;
}
}
Procedure? _tryGetEnclosingProcedure(TreeNode? node) {
while (node is! Procedure) {
node = node?.parent;
if (node == null) {
return null;
}
}
return node;
}
/// We will replace the enclosing procedure if:
/// 1) The enclosing procedure is static.
/// 2) The enclosing procedure has a body with a single statement, and that
/// statement is just a [StaticInvocation] of the inline JS helper.
/// 3) All of the arguments to `inlineJS` are [VariableGet]s. (this is
/// checked by [_expandInlineJS]).
bool _shouldReplaceEnclosingProcedure(StaticInvocation node) {
Procedure? enclosingProcedure = _tryGetEnclosingProcedure(node);
if (enclosingProcedure == null) {
return false;
}
Statement enclosingBody = enclosingProcedure.function.body!;
Expression? expression;
if (enclosingBody is ReturnStatement) {
expression = enclosingBody.expression;
} else if (enclosingBody is Block && enclosingBody.statements.length == 1) {
Statement statement = enclosingBody.statements.single;
if (statement is ExpressionStatement) {
expression = statement.expression;
} else if (statement is ReturnStatement) {
expression = statement.expression;
}
}
return expression == node;
}
/// Calls to the `JS` helper are replaced in one of two ways:
/// 1) By a static invocation to an external stub method that imports
/// the JS function.
/// 2) Under restricted circumstances the entire enclosing procedure will be
/// replaced by an external stub method that imports the JS function. See
/// [_shouldReplaceEnclosingProcedure] for more details.
Expression expand(StaticInvocation node) {
Arguments arguments = node.arguments;
List<Expression> originalArguments = arguments.positional.sublist(1);
List<VariableDeclaration> dartPositionalParameters = [];
bool allArgumentsAreGet = true;
for (int j = 0; j < originalArguments.length; j++) {
Expression originalArgument = originalArguments[j];
String parameterString = 'x$j';
DartType type = originalArgument.getStaticType(_staticTypeContext);
dartPositionalParameters.add(VariableDeclaration(parameterString,
type: _toExternalType(type), isSynthesized: true));
if (originalArgument is! VariableGet) {
allArgumentsAreGet = false;
}
}
assert(arguments.positional[0] is StringLiteral,
"Code template must be a StringLiteral");
String codeTemplate = (arguments.positional[0] as StringLiteral).value;
String jsMethodName = _methodCollector.generateMethodName();
_inlineJSImportName = 'dart2wasm.$jsMethodName';
_replaceProcedureWithInlineJS =
allArgumentsAreGet && _shouldReplaceEnclosingProcedure(node);
Procedure dartProcedure;
Expression result;
if (_replaceProcedureWithInlineJS) {
dartProcedure = _tryGetEnclosingProcedure(node)!;
result = InvalidExpression("Unreachable");
} else {
dartProcedure = _methodCollector.addInteropProcedure(
'|$jsMethodName',
_inlineJSImportName,
FunctionNode(null,
positionalParameters: dartPositionalParameters,
returnType: arguments.types.single),
_util.inlineJSTarget.fileUri,
AnnotationType.import,
isExternal: true);
result = StaticInvocation(dartProcedure, Arguments(originalArguments));
}
_methodCollector.addMethod(dartProcedure, jsMethodName, codeTemplate);
return result;
}
// The `JS<foo>("...some js code ...", arg0, arg1)` expressions will produce
// wasm imports. We want to only use types in those wasm imports that binaryen
// allows under closed world assumptions (and not blindly use `arg<N>`s static
// type).
//
// For now we special case `WasmArray<>` which we turn into a generic `array`
// type (super type of all wasm arrays).
DartType _toExternalType(DartType type) {
if (type is InterfaceType && type.classNode == _util.wasmArrayClass) {
return InterfaceType(_util.wasmArrayRefClass, type.declaredNullability);
}
return type;
}
}