blob: 6c034b68f6ea5a0b5596b9e3a99f4eaaf0a2f479 [file] [log] [blame]
// Copyright (c) 2022, 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:_fe_analyzer_shared/src/messages/codes.dart'
show Message, LocatedMessage;
import 'package:_js_interop_checks/js_interop_checks.dart';
import 'package:_js_interop_checks/src/js_interop.dart' as jsInteropHelper;
import 'package:_js_interop_checks/src/transformations/export_creator.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/clone.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/changed_structure_notifier.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/type_environment.dart';
import 'package:kernel/verifier.dart';
import 'package:vm/transformations/mixin_full_resolution.dart'
as transformMixins show transformLibraries;
import 'package:vm/transformations/ffi/common.dart' as ffiHelper
show calculateTransitiveImportsOfDartFfiIfUsed;
import 'package:vm/transformations/ffi/definitions.dart'
as transformFfiDefinitions show transformLibraries;
import 'package:vm/transformations/ffi/use_sites.dart' as transformFfiUseSites
show transformLibraries;
import 'package:front_end/src/api_prototype/constant_evaluator.dart'
as constantEvaluator show ConstantEvaluator, EvaluationMode;
import 'package:front_end/src/api_prototype/const_conditional_simplifier.dart'
show ConstConditionalSimplifier;
import 'package:dart2wasm/await_transformer.dart' as awaitTrans;
import 'package:dart2wasm/ffi_native_transformer.dart' as wasmFfiNativeTrans;
import 'package:dart2wasm/records.dart' show RecordShape;
import 'package:dart2wasm/transformers.dart' as wasmTrans;
enum Mode {
regular,
stringref,
jsCompatibility,
}
class Dart2WasmConstantsBackend extends ConstantsBackend {
const Dart2WasmConstantsBackend();
@override
bool get supportsUnevaluatedConstants => true;
}
class ConstantResolver extends Transformer {
ConstantResolver(this.evaluator);
final constantEvaluator.ConstantEvaluator evaluator;
StaticTypeContext? _context;
@override
TreeNode visitLibrary(Library library) {
final oldContext = _context;
_context =
StaticTypeContext.forAnnotations(library, evaluator.typeEnvironment);
final result = super.visitLibrary(library);
_context = oldContext;
return result;
}
@override
TreeNode defaultMember(Member member) {
final oldContext = _context;
_context = StaticTypeContext(member, evaluator.typeEnvironment);
final result = super.defaultMember(member);
_context = oldContext;
return result;
}
@override
TreeNode visitConstantExpression(ConstantExpression node) {
final constant = node.constant;
if (constant is UnevaluatedConstant) {
final expression = constant.expression;
final newConstant = evaluator.evaluate(_context!, expression);
ConstantExpression result =
new ConstantExpression(newConstant, node.getStaticType(_context!))
..fileOffset = node.fileOffset;
return result;
}
return node;
}
}
class WasmTarget extends Target {
WasmTarget({this.removeAsserts = true, this.mode = Mode.regular});
bool removeAsserts;
Mode mode;
Class? _growableList;
Class? _immutableList;
Class? _wasmDefaultMap;
Class? _wasmDefaultSet;
Class? _wasmImmutableMap;
Class? _wasmImmutableSet;
Class? _oneByteString;
Class? _twoByteString;
Class? _jsString;
Class? _closure;
Map<String, Class>? _nativeClasses;
@override
bool get enableNoSuchMethodForwarders => true;
@override
ConstantsBackend get constantsBackend => const Dart2WasmConstantsBackend();
@override
Verification get verification => const WasmVerification();
@override
String get name {
switch (mode) {
case Mode.regular:
return 'wasm';
case Mode.stringref:
return 'wasm_stringref';
case Mode.jsCompatibility:
return 'wasm_js_compatibility';
}
}
String get platformFile {
switch (mode) {
case Mode.regular:
return 'dart2wasm_platform.dill';
case Mode.stringref:
return 'dart2wasm_stringref_platform.dill';
case Mode.jsCompatibility:
return 'dart2wasm_js_compatibility_platform.dill';
}
}
@override
TargetFlags get flags => TargetFlags();
@override
List<String> get extraRequiredLibraries => [
'dart:_http',
'dart:_internal',
'dart:_js_helper',
'dart:_js_types',
'dart:_wasm',
'dart:async',
'dart:developer',
'dart:ffi',
'dart:io',
'dart:js',
'dart:js_interop',
'dart:js_interop_unsafe',
'dart:js_util',
'dart:nativewrappers',
'dart:typed_data',
if (mode != Mode.jsCompatibility) 'dart:_string',
];
@override
List<String> get extraIndexedLibraries => [
'dart:_js_helper',
'dart:_js_types',
'dart:_wasm',
'dart:collection',
'dart:js_interop',
'dart:js_interop_unsafe',
'dart:js_util',
'dart:typed_data',
if (mode != Mode.jsCompatibility) 'dart:_string',
];
@override
bool mayDefineRestrictedType(Uri uri) => uri.isScheme('dart');
@override
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>
super.allowPlatformPrivateLibraryAccess(importer, imported) ||
importer.path.contains('tests/web/wasm') ||
importer.isScheme('package') &&
(importer.path == 'js/js.dart' ||
importer.path.startsWith('ui/') &&
imported.toString() == 'dart:_wasm');
void _patchHostEndian(CoreTypes coreTypes) {
// Fix Endian.host to be a const field equal to Endian.little instead of
// a final field. Wasm is a little-endian platform.
// Can't use normal patching process for this because CFE does not
// support patching fields.
// See http://dartbug.com/32836 for the background.
final Field host =
coreTypes.index.getField('dart:typed_data', 'Endian', 'host');
final Field little =
coreTypes.index.getField('dart:typed_data', 'Endian', 'little');
host.isConst = true;
host.initializer = new CloneVisitorNotMembers().clone(little.initializer!)
..parent = host;
}
void _performJSInteropTransformations(
Component component,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
Set<Library> interopDependentLibraries,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex) {
_nativeClasses ??= JsInteropChecks.getNativeClasses(component);
final jsInteropReporter = JsInteropDiagnosticReporter(
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>);
final jsInteropChecks = JsInteropChecks(
coreTypes, hierarchy, jsInteropReporter, _nativeClasses!,
isDart2Wasm: true);
// Process and validate first before doing anything with exports.
for (Library library in interopDependentLibraries) {
jsInteropChecks.visitLibrary(library);
}
final exportCreator = ExportCreator(TypeEnvironment(coreTypes, hierarchy),
jsInteropReporter, jsInteropChecks.exportChecker);
for (Library library in interopDependentLibraries) {
exportCreator.visitLibrary(library);
}
}
@override
void performPreConstantEvaluationTransformations(
Component component,
CoreTypes coreTypes,
List<Library> libraries,
DiagnosticReporter diagnosticReporter,
{void Function(String msg)? logger,
ChangedStructureNotifier? changedStructureNotifier}) {
_patchHostEndian(coreTypes);
}
@override
void performModularTransformationsOnLibraries(
Component component,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
List<Library> libraries,
Map<String, String>? environmentDefines,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex,
{void logger(String msg)?,
ChangedStructureNotifier? changedStructureNotifier}) {
Set<Library> transitiveImportingJSInterop = {
...?jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component, Uri.parse("package:js/js.dart")),
...?jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component, Uri.parse("dart:_js_annotations")),
...?jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component, Uri.parse("dart:js_interop")),
};
if (transitiveImportingJSInterop.isEmpty) {
logger?.call("Skipped JS interop transformations");
} else {
_performJSInteropTransformations(component, coreTypes, hierarchy,
transitiveImportingJSInterop, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed JS interop classes");
}
// If we are compiling with a null environment, skip constant resolution
// and simplification.
if (environmentDefines != null) {
final reportError =
(LocatedMessage message, [List<LocatedMessage>? context]) {
diagnosticReporter.report(message.messageObject, message.charOffset,
message.length, message.uri);
if (context != null) {
for (final m in context) {
diagnosticReporter.report(
m.messageObject, m.charOffset, m.length, m.uri);
}
}
};
final simplifier = ConstConditionalSimplifier(
dartLibrarySupport,
constantsBackend,
component,
reportError,
environmentDefines: environmentDefines,
evaluationMode: constantEvaluator.EvaluationMode.strong,
coreTypes: coreTypes,
classHierarchy: hierarchy,
removeAsserts: removeAsserts,
);
final evaluator = simplifier.constantEvaluator;
ConstantResolver(evaluator).transform(component);
simplifier.run();
}
transformMixins.transformLibraries(
this, coreTypes, hierarchy, libraries, referenceFromIndex);
logger?.call("Transformed mixin applications");
List<Library>? transitiveImportingDartFfi = ffiHelper
.calculateTransitiveImportsOfDartFfiIfUsed(component, libraries);
if (transitiveImportingDartFfi == null) {
logger?.call("Skipped ffi transformation");
} else {
wasmFfiNativeTrans.transformLibraries(component, coreTypes, hierarchy,
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
transformFfiDefinitions.transformLibraries(
component,
coreTypes,
hierarchy,
transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
changedStructureNotifier);
transformFfiUseSites.transformLibraries(component, coreTypes, hierarchy,
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed ffi annotations");
}
wasmTrans.transformLibraries(
libraries, coreTypes, hierarchy, diagnosticReporter);
awaitTrans.transformLibraries(libraries, hierarchy, coreTypes);
}
@override
void performTransformationsOnProcedure(
CoreTypes coreTypes,
ClassHierarchy hierarchy,
Procedure procedure,
Map<String, String>? environmentDefines,
{void logger(String msg)?}) {
wasmTrans.transformProcedure(procedure, coreTypes, hierarchy);
}
Expression _instantiateInvocation(
CoreTypes coreTypes, String name, Arguments arguments) {
if (name.startsWith("set:")) {
name = name.substring(4);
Procedure invocationSetter = coreTypes.invocationClass.procedures
.firstWhere((c) => c.name.text == "setter");
return StaticInvocation(invocationSetter,
Arguments([SymbolLiteral(name), arguments.positional.single]));
} else if (name.startsWith("get:")) {
name = name.substring(4);
Procedure invocationGetter = coreTypes.invocationClass.procedures
.firstWhere((c) => c.name.text == "getter");
return StaticInvocation(
invocationGetter, Arguments([SymbolLiteral(name)]));
} else if (arguments.types.isEmpty) {
Procedure invocationMethod = coreTypes.invocationClass.procedures
.firstWhere((c) => c.name.text == "method");
return StaticInvocation(
invocationMethod,
Arguments([
SymbolLiteral(name),
ListLiteral(arguments.positional),
MapLiteral(List<MapLiteralEntry>.from(
arguments.named.map((NamedExpression arg) {
return MapLiteralEntry(SymbolLiteral(arg.name), arg.value);
})), keyType: coreTypes.symbolNonNullableRawType)
..isConst = (arguments.named.isEmpty)
]));
} else {
Procedure invocationGenericMethod = coreTypes.invocationClass.procedures
.firstWhere((c) => c.name.text == "genericMethod");
return StaticInvocation(
invocationGenericMethod,
Arguments([
SymbolLiteral(name),
ListLiteral(arguments.types.map((t) => TypeLiteral(t)).toList()),
ListLiteral(arguments.positional),
MapLiteral(List<MapLiteralEntry>.from(
arguments.named.map((NamedExpression arg) {
return new MapLiteralEntry(SymbolLiteral(arg.name), arg.value);
})), keyType: coreTypes.symbolNonNullableRawType)
..isConst = (arguments.named.isEmpty)
]));
}
}
@override
Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
String name, Arguments arguments, int offset, bool isSuper) {
return _instantiateInvocation(coreTypes, name, arguments);
}
Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
Expression receiver, String name, Arguments arguments, int offset,
{bool isMethod = false,
bool isGetter = false,
bool isSetter = false,
bool isField = false,
bool isLocalVariable = false,
bool isDynamic = false,
bool isSuper = false,
bool isStatic = false,
bool isConstructor = false,
bool isTopLevel = false}) {
return StaticInvocation(
coreTypes.noSuchMethodErrorDefaultConstructor,
Arguments(
[receiver, _instantiateInvocation(coreTypes, name, arguments)]));
}
@override
bool get supportsSetLiterals => true;
@override
int get enabledLateLowerings => LateLowering.all;
@override
int get enabledConstructorTearOffLowerings => ConstructorTearOffLowering.all;
@override
bool get supportsExplicitGetterCalls => true;
@override
bool get supportsLateLoweringSentinel => false;
@override
bool get useStaticFieldLowering => false;
@override
bool enableNative(Uri uri) => true;
@override
Class concreteListLiteralClass(CoreTypes coreTypes) {
return _growableList ??=
coreTypes.index.getClass('dart:core', '_GrowableList');
}
@override
Class concreteConstListLiteralClass(CoreTypes coreTypes) {
return _immutableList ??=
coreTypes.index.getClass('dart:core', '_ImmutableList');
}
@override
Class concreteMapLiteralClass(CoreTypes coreTypes) {
return _wasmDefaultMap ??=
coreTypes.index.getClass('dart:collection', '_WasmDefaultMap');
}
@override
Class concreteConstMapLiteralClass(CoreTypes coreTypes) {
return _wasmImmutableMap ??=
coreTypes.index.getClass('dart:collection', '_WasmImmutableMap');
}
@override
Class concreteSetLiteralClass(CoreTypes coreTypes) {
return _wasmDefaultSet ??=
coreTypes.index.getClass('dart:collection', '_WasmDefaultSet');
}
@override
Class concreteConstSetLiteralClass(CoreTypes coreTypes) {
return _wasmImmutableSet ??=
coreTypes.index.getClass('dart:collection', '_WasmImmutableSet');
}
@override
Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
// In JSCM all strings are JS strings.
if (mode == Mode.jsCompatibility) {
return _jsString ??=
coreTypes.index.getClass("dart:_js_types", "JSStringImpl");
}
const int maxLatin1 = 0xff;
for (int i = 0; i < value.length; ++i) {
if (value.codeUnitAt(i) > maxLatin1) {
return _twoByteString ??=
coreTypes.index.getClass('dart:_string', 'TwoByteString');
}
}
return _oneByteString ??=
coreTypes.index.getClass('dart:_string', 'OneByteString');
}
@override
Class concreteClosureClass(CoreTypes coreTypes) {
return _closure ??= coreTypes.index.getClass('dart:core', '_Closure');
}
@override
bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("wasm:");
late final Map<RecordShape, Class> recordClasses;
@override
Class getRecordImplementationClass(CoreTypes coreTypes,
int numPositionalFields, List<String> namedFields) =>
recordClasses[RecordShape(numPositionalFields, namedFields)]!;
}
class WasmVerification extends Verification {
const WasmVerification();
@override
bool allowNoFileOffset(VerificationStage stage, TreeNode node) {
if (super.allowNoFileOffset(stage, node)) {
return true;
}
if (stage >= VerificationStage.afterModularTransformations) {
// Allow synthesized classes, procedures, fields and casts.
// TODO(askesc): Improve the precision of these exceptions.
return node is Class ||
node is Constructor ||
node is Procedure ||
node is Field ||
node is AsExpression;
}
return false;
}
}