Skip to content

Commit

Permalink
Version 3.6.0-104.0.dev
Browse files Browse the repository at this point in the history
Merge ae3dbcd into dev
  • Loading branch information
Dart CI committed Aug 1, 2024
2 parents b56fcd8 + ae3dbcd commit 0486e48
Show file tree
Hide file tree
Showing 16 changed files with 457 additions and 70 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
- Added constructors for `JSArrayBuffer`, `JSDataView`, and concrete typed array
types e.g. `JSInt8Array`.
- Added `length` and `[]`/`[]=` operators to `JSArray`.
- Added `toJSCaptureThis` so `this` is passed in from JavaScript to the
callback as the first parameter.

## 3.5.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,37 @@ typedef _InvocationBuilder = Expression Function(
/// Replaces js_util methods with inline calls to foreign_helper JS which
/// emits the code as a JavaScript code fragment.
class JsUtilOptimizer extends Transformer {
final Procedure _allowInteropTarget;
final Iterable<Procedure> _allowedInteropJsUtilTargets;
final Procedure _callMethodTarget;
final Procedure _callMethodTrustTypeTarget;
final List<Procedure> _callMethodUncheckedTargets;
final List<Procedure> _callMethodUncheckedTrustTypeTargets;
final Procedure _callConstructorTarget;
final List<Procedure> _callConstructorUncheckedTargets;
final CloneVisitorNotMembers _cloner = CloneVisitorWithMembers();
final Map<Member, _InvocationBuilder?> _externalInvocationBuilders = {};
final Procedure _functionToJSTarget;
final Procedure _functionToJSCaptureThisTarget;
final List<Procedure> _functionToJSTargets;
final List<Procedure>? _functionToJSCaptureThisTargets;
final Procedure _functionToJSNTarget;
final Procedure _functionToJSCaptureThisNTarget;
final Procedure _getPropertyTarget;
final Procedure _getPropertyTrustTypeTarget;
final Procedure _globalContextTarget;
final Procedure _jsExportedDartFunctionToDartTarget;
final Procedure _jsFunctionToDart;
final Procedure _jsTarget;
final Procedure _listEmptyFactory;
final InterfaceType _objectType;
final Procedure _setPropertyTarget;
final Procedure _setPropertyUncheckedTarget;

final CoreTypes _coreTypes;
final CloneVisitorNotMembers _cloner = CloneVisitorWithMembers();
final ExtensionIndex _extensionIndex;
final Map<Member, _InvocationBuilder?> _externalInvocationBuilders = {};
final StatefulStaticTypeContext _staticTypeContext;

/// Dynamic members in js_util that interop allowed.
static const List<String> _allowedInteropJsUtilMembers = [
'callConstructor',
Expand All @@ -63,18 +74,9 @@ class JsUtilOptimizer extends Transformer {
'setProperty'
];

final Procedure _allowInteropTarget;
final Iterable<Procedure> _allowedInteropJsUtilTargets;
final Procedure _jsTarget;
final Procedure _listEmptyFactory;

final CoreTypes _coreTypes;
final StatefulStaticTypeContext _staticTypeContext;

final ExtensionIndex _extensionIndex;

JsUtilOptimizer(
this._coreTypes, ClassHierarchy hierarchy, this._extensionIndex)
this._coreTypes, ClassHierarchy hierarchy, this._extensionIndex,
{required bool isDart2JS})
: _callMethodTarget =
_coreTypes.index.getTopLevelProcedure('dart:js_util', 'callMethod'),
_callMethodTrustTypeTarget = _coreTypes.index
Expand All @@ -95,12 +97,23 @@ class JsUtilOptimizer extends Transformer {
'dart:js_util', '_callConstructorUnchecked$i')),
_functionToJSTarget = _coreTypes.index.getTopLevelProcedure(
'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'),
_functionToJSCaptureThisTarget = _coreTypes.index.getTopLevelProcedure(
'dart:js_interop',
'FunctionToJSExportedDartFunction|get#toJSCaptureThis'),
_functionToJSTargets = List<Procedure>.generate(
6,
(i) => _coreTypes.index
.getTopLevelProcedure('dart:js_util', '_functionToJS$i')),
_functionToJSCaptureThisTargets = isDart2JS
? List<Procedure>.generate(
5,
(i) => _coreTypes.index.getTopLevelProcedure(
'dart:js_util', '_functionToJSCaptureThis$i'))
: null,
_functionToJSNTarget = _coreTypes.index
.getTopLevelProcedure('dart:js_util', '_functionToJSN'),
_functionToJSCaptureThisNTarget = _coreTypes.index
.getTopLevelProcedure('dart:js_util', '_functionToJSCaptureThisN'),
_getPropertyTarget = _coreTypes.index
.getTopLevelProcedure('dart:js_util', 'getProperty'),
_getPropertyTrustTypeTarget = _coreTypes.index
Expand Down Expand Up @@ -500,6 +513,8 @@ class JsUtilOptimizer extends Transformer {
// https://github.com/dart-lang/sdk/issues/53367 is resolved.
} else if (target == _functionToJSTarget) {
invocation = _lowerFunctionToJS(node);
} else if (target == _functionToJSCaptureThisTarget) {
invocation = _lowerFunctionToJS(node, captureThis: true);
} else if (target == _jsExportedDartFunctionToDartTarget) {
invocation = _lowerJSExportedDartFunctionToDart(node);
} else if (target.isExternal && !JsInteropChecks.isPatchedMember(target)) {
Expand Down Expand Up @@ -684,27 +699,47 @@ class JsUtilOptimizer extends Transformer {
..parent = nodeParent;
}

/// For the given `dart:js_interop` `Function.toJS` invocation [node], returns
/// an invocation of `_functionToJSX` with the given `Function` argument,
/// where X is the number of the positional arguments.
/// For the given `dart:js_interop` `Function.toJS` or
/// `Function.toJSCaptureThis` invocation [node], returns an invocation of the
/// corresponding private stub in `js_util` with the invocation's [Function]
/// argument and the number of positional parameters of that [Function].
///
/// If the number of the positional arguments is larger than 5, returns an
/// invocation of `_functionToJSN` instead.
StaticInvocation _lowerFunctionToJS(StaticInvocation node) {
/// If [captureThis] is false, the node target is assumed to be
/// `Function.toJS` and otherwise `Function.toJSCaptureThis`.
///
/// There are specialized stubs up to a certain positional parameter length,
/// and after that, either an invocation of `_functionToJSN` or
/// `_functionToJSCaptureThisN` is returned.
StaticInvocation _lowerFunctionToJS(StaticInvocation node,
{bool captureThis = false}) {
// JS interop checks assert that the static type is available, and that
// there are no named arguments or type arguments.
final function = node.arguments.positional.single;
final functionType =
function.getStaticType(_staticTypeContext) as FunctionType;
final argumentsLength = functionType.positionalParameters.length;
final parametersLength = functionType.positionalParameters.length;
List<Procedure>? specializedStubs;
int stubIndex;
Procedure genericStub;
if (captureThis) {
specializedStubs = _functionToJSCaptureThisTargets;
stubIndex = parametersLength - 1; // Account for `this`.
genericStub = _functionToJSCaptureThisNTarget;
} else {
specializedStubs = _functionToJSTargets;
stubIndex = parametersLength;
genericStub = _functionToJSNTarget;
}
Procedure target;
Arguments arguments;
if (argumentsLength < _functionToJSTargets.length) {
target = _functionToJSTargets[argumentsLength];
if (specializedStubs != null &&
stubIndex >= 0 &&
stubIndex < specializedStubs.length) {
target = specializedStubs[stubIndex];
arguments = Arguments([function]);
} else {
target = _functionToJSNTarget;
arguments = Arguments([function, IntLiteral(argumentsLength)]);
target = genericStub;
arguments = Arguments([function, IntLiteral(parametersLength)]);
}
return StaticInvocation(
target, arguments..fileOffset = node.arguments.fileOffset)
Expand Down
5 changes: 3 additions & 2 deletions pkg/compiler/lib/src/kernel/dart2js_target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,9 @@ class Dart2jsTarget extends Target {
jsInteropReporter,
jsInteropChecks.exportChecker,
jsInteropChecks.extensionIndex);
var jsUtilOptimizer =
JsUtilOptimizer(coreTypes, hierarchy, jsInteropChecks.extensionIndex);
var jsUtilOptimizer = JsUtilOptimizer(
coreTypes, hierarchy, jsInteropChecks.extensionIndex,
isDart2JS: true);
for (var library in libraries) {
// Shared transformer has static checks, so we still visit even if there
// are errors.
Expand Down
41 changes: 29 additions & 12 deletions pkg/dart2wasm/lib/js/callback_specializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,12 @@ class CallbackSpecializer {
/// Create a [Procedure] that will wrap a Dart callback in a JS wrapper.
///
/// [node] is the conversion function that is called by the user (either
/// `allowInterop` or `Function.toJS`). [type] is the static type of the
/// callback. [boxExternRef] determines if the trampoline should box the
/// arguments and return value or convert every value. [needsCastClosure]
/// determines if a cast closure is needed in order to validate the types of
/// some arguments.
/// `allowInterop`, `Function.toJS`, or `Function.toJSCaptureThis`). [type] is
/// the static type of the callback. [boxExternRef] determines if the
/// trampoline should box the arguments and return value or convert every
/// value. [needsCastClosure] determines if a cast closure is needed in order
/// to validate the types of some arguments. [captureThis] determines if
/// `this` needs to be passed into the trampoline from the JS wrapper.
///
/// The procedure will call a JS method that will create a wrapper, cache the
/// callback, and call the trampoline function with the callback, the JS
Expand All @@ -190,20 +191,30 @@ class CallbackSpecializer {
///
/// Returns the created [Procedure].
Procedure _getJSWrapperFunction(Procedure node, FunctionType type,
{required bool boxExternRef, required bool needsCastClosure}) {
{required bool boxExternRef,
required bool needsCastClosure,
required bool captureThis}) {
final functionTrampolineName =
_createFunctionTrampoline(node, type, boxExternRef: boxExternRef);
List<String> jsParameters = [];
for (int i = 0; i < type.positionalParameters.length; i++) {
var jsParametersLength = type.positionalParameters.length;
if (captureThis) jsParametersLength--;
for (int i = 0; i < jsParametersLength; i++) {
jsParameters.add('x$i');
}
String jsWrapperParams = jsParameters.join(',');
String dartArguments = 'f,arguments.length';
// We could avoid incrementing the arguments length in the case of
// `captureThis` and have the function trampoline account for the extra
// argument, but there's no benefit in doing that.
String argumentsLength =
captureThis ? 'arguments.length + 1' : 'arguments.length';
String dartArguments = 'f,$argumentsLength';
String jsMethodParams = 'f';
if (needsCastClosure) {
dartArguments = '$dartArguments,castClosure';
jsMethodParams = '($jsMethodParams,castClosure)';
}
if (captureThis) dartArguments = '$dartArguments,this';
if (jsParameters.isNotEmpty) {
dartArguments = '$dartArguments,$jsWrapperParams';
}
Expand Down Expand Up @@ -263,7 +274,7 @@ class CallbackSpecializer {
final type = argument.getStaticType(_staticTypeContext) as FunctionType;
final jsWrapperFunction = _getJSWrapperFunction(
staticInvocation.target, type,
boxExternRef: false, needsCastClosure: false);
boxExternRef: false, needsCastClosure: false, captureThis: false);
final v = VariableDeclaration('#var',
initializer: argument, type: type, isSynthesized: true);
return Let(
Expand Down Expand Up @@ -332,21 +343,27 @@ class CallbackSpecializer {
returnType: VoidType()));
}

/// Given an invocation of `<Function>.toJS`, returns an [Expression]
/// Given an invocation of `Function.toJS`, returns an [Expression]
/// representing:
///
/// JSValue(jsWrapperFunction(<Function>))
///
/// or if a cast closure is needed:
///
/// JSValue(jsWrapperFunction(<Function>, <CastClosure>))
Expression functionToJS(StaticInvocation staticInvocation) {
///
/// If [captureThis] is true, this is assumed to be an invocation of
/// `Function.toJSCaptureThis`.
Expression functionToJS(StaticInvocation staticInvocation,
{bool captureThis = false}) {
final argument = staticInvocation.arguments.positional.single;
final type = argument.getStaticType(_staticTypeContext) as FunctionType;
final castClosure = _createCastClosure(type);
final jsWrapperFunction = _getJSWrapperFunction(
staticInvocation.target, type,
boxExternRef: true, needsCastClosure: castClosure != null);
boxExternRef: true,
needsCastClosure: castClosure != null,
captureThis: captureThis);
return _createJSValue(StaticInvocation(
jsWrapperFunction,
Arguments([
Expand Down
2 changes: 2 additions & 0 deletions pkg/dart2wasm/lib/js/interop_transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class InteropTransformer extends Transformer {
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 {
Expand Down
4 changes: 4 additions & 0 deletions pkg/dart2wasm/lib/js/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class CoreTypesUtil {
final Procedure allowInteropTarget;
final Procedure dartifyRawTarget;
final Procedure functionToJSTarget;
final Procedure functionToJSCaptureThisTarget;
final Procedure greaterThanOrEqualToTarget;
final Procedure inlineJSTarget;
final Procedure isDartFunctionWrappedTarget;
Expand All @@ -35,6 +36,9 @@ class CoreTypesUtil {
.getTopLevelProcedure('dart:_js_helper', 'dartifyRaw'),
functionToJSTarget = coreTypes.index.getTopLevelProcedure(
'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'),
functionToJSCaptureThisTarget = coreTypes.index.getTopLevelProcedure(
'dart:js_interop',
'FunctionToJSExportedDartFunction|get#toJSCaptureThis'),
greaterThanOrEqualToTarget =
coreTypes.index.getProcedure('dart:core', 'num', '>='),
inlineJSTarget =
Expand Down
5 changes: 3 additions & 2 deletions pkg/dev_compiler/lib/src/kernel/target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,9 @@ class DevCompilerTarget extends Target {
jsInteropReporter,
jsInteropChecks.exportChecker,
jsInteropChecks.extensionIndex);
final jsUtilOptimizer =
JsUtilOptimizer(coreTypes, hierarchy, jsInteropChecks.extensionIndex);
final jsUtilOptimizer = JsUtilOptimizer(
coreTypes, hierarchy, jsInteropChecks.extensionIndex,
isDart2JS: false);
for (var node in nodes) {
_CovarianceTransformer(node).transform();
// Shared interop transformer has static checks, so we still visit.
Expand Down
24 changes: 24 additions & 0 deletions sdk/lib/_internal/js_dev_runtime/patch/js_allow_interop_patch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,30 @@ JavaScriptFunction _functionToJSN(Function f, int maxLength) {
return ret;
}

// TODO(srujzs): We could add specific arity stubs for this like we do with
// `Function.toJS`, but unlike dart2js, unused ones aren't tree-shaken away.
// Considering this isn't a very commonly used API, that seems wasteful.
JavaScriptFunction _functionToJSCaptureThisN(Function f, int maxLength) {
if (!dart.isDartFunction(f)) {
throw ArgumentError('Attempting to rewrap a JS function.');
}
final ret = JS<JavaScriptFunction>(
'!',
'''
function (...arguments) {
let args = [this];
args.push.apply(args, arguments);
return #(#, Array.prototype.slice.call(args, 0,
Math.min(args.length, #)));
}
''',
dart.dcall,
f,
maxLength);
JS('', '#[#] = #', ret, _functionToJSProperty, f);
return ret;
}

_callDartFunctionFast0(callback) => JS('', '#()', callback);

_callDartFunctionFast1(callback, arg1, int length) {
Expand Down
Loading

0 comments on commit 0486e48

Please sign in to comment.