blob: 3d7fadb893a5cbc0a36aed4aa8ad26a601a775db [file]
// 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/shared_interop_transformer.dart';
import 'package:front_end/src/api_prototype/const_conditional_simplifier.dart'
show ConstConditionalSimplifier;
import 'package:front_end/src/api_prototype/constant_evaluator.dart'
as constantEvaluator
show ConstantEvaluator;
import 'package:front_end/src/codes/diagnostic.dart' as diag;
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/modular/transformations/ffi/common.dart'
as ffiHelper
show calculateTransitiveImportsOfDartFfiIfUsed;
import 'package:vm/modular/transformations/ffi/definitions.dart'
as transformFfiDefinitions
show transformLibraries;
import 'package:vm/modular/transformations/ffi/use_sites.dart'
as transformFfiUseSites
show transformLibraries;
import 'package:vm/modular/transformations/mixin_full_resolution.dart'
as transformMixins
show transformLibraries;
import 'await_transformer.dart' as awaitTrans;
import 'ffi_native_address_transformer.dart' as wasmFfiNativeAddressTrans;
import 'ffi_native_transformer.dart' as wasmFfiNativeTrans;
import 'records.dart' show RecordShape;
import 'transformers.dart' as wasmTrans;
import 'util.dart' as util;
import 'wasm_library_checks.dart' as wasmChecks;
enum Mode { regular, jsCompatibility, standalone }
class Dart2WasmConstantsBackend extends ConstantsBackend {
const Dart2WasmConstantsBackend() : super(keepLocals: false);
@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 = ConstantExpression(
newConstant,
node.getStaticType(_context!),
)..fileOffset = node.fileOffset;
return result;
}
return node;
}
}
class WasmTarget extends Target {
WasmTarget({
this.enableExperimentalFfi = true,
this.enableExperimentalWasmInterop = true,
this.removeAsserts = false,
this.mode = Mode.regular,
});
final bool removeAsserts;
final Mode mode;
final bool enableExperimentalFfi;
final bool enableExperimentalWasmInterop;
Class? _growableList;
Class? _immutableList;
Class? _wasmDefaultMap;
Class? _wasmDefaultSet;
Class? _wasmImmutableMap;
Class? _wasmImmutableSet;
Class? _stringImpl;
Class? _closure;
Class? _boxedInt;
Class? _boxedDouble;
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 {
return switch (mode) {
Mode.regular => 'wasm',
Mode.jsCompatibility => 'wasm_js_compatibility',
Mode.standalone => 'wasm_standalone',
};
}
String get platformFile {
return switch (mode) {
Mode.regular => 'dart2wasm_platform.dill',
Mode.jsCompatibility => 'dart2wasm_js_compatibility_platform.dill',
Mode.standalone => 'dart2wasm_standalone_platform.dill',
};
}
@override
TargetFlags get flags => TargetFlags();
@override
List<String> get extraRequiredLibraries => [
'dart:_boxed_bool',
'dart:_boxed_double',
'dart:_boxed_int',
'dart:_compact_hash',
'dart:_http',
'dart:_internal',
'dart:_list',
'dart:_string',
'dart:_wasm',
'dart:async',
'dart:developer',
'dart:ffi',
'dart:io',
'dart:nativewrappers',
'dart:typed_data',
if (mode == .standalone)
'dart:_embedder'
else ...[
'dart:_js_helper',
'dart:_js_types',
'dart:js_interop',
'dart:js_interop_unsafe',
],
];
@override
List<String> get extraIndexedLibraries => [
'dart:_boxed_bool',
'dart:_boxed_double',
'dart:_boxed_int',
'dart:_compact_hash',
'dart:_error_utils',
'dart:_list',
'dart:_string',
'dart:_wasm',
'dart:collection',
'dart:typed_data',
if (mode == .standalone)
'dart:_embedder'
else ...[
'dart:_js_helper',
'dart:_js_types',
'dart:js_interop',
'dart:js_interop_unsafe',
],
];
@override
bool mayDefineRestrictedType(Uri uri) => uri.isScheme('dart');
@override
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) {
if (super.allowPlatformPrivateLibraryAccess(importer, imported)) {
return true;
}
if (imported.toString() == 'dart:_wasm' ||
imported.toString() == 'dart:_js_interop_wasm') {
return enableExperimentalWasmInterop;
}
final importerString = importer.toString();
// We have some tests that import dart:*js*
if (importerString.contains('tests/web/wasm')) return true;
// We have some IR$ tests that import dart:*js*
if (importerString.contains('pkg/dart2wasm/test/ir_tests')) return true;
// Flutter's dart:ui is also package:ui (in test mode)
if (importerString.startsWith('package:ui/')) return true;
return false;
}
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 = 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 sharedInteropTransformer = SharedInteropTransformer(
TypeEnvironment(coreTypes, hierarchy),
jsInteropReporter,
jsInteropChecks.exportChecker,
jsInteropChecks.extensionIndex,
);
for (Library library in interopDependentLibraries) {
sharedInteropTransformer.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 Function(String msg)? logger,
ChangedStructureNotifier? changedStructureNotifier,
}) {
var invalidFfiUsage = false;
for (final library in libraries) {
if (!enableExperimentalFfi) {
invalidFfiUsage |= _checkDisallowedDartFfiUsage(
library,
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
);
}
// Check `wasm:import` and `wasm:export` pragmas before FFI transforms
// as FFI transforms convert JS interop annotations to these pragmas.
if (!enableExperimentalWasmInterop) {
_checkWasmImportExportPragmas(
library,
coreTypes,
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>,
);
}
}
if (mode != .standalone) {
Set<Library> transitiveImportingJSInterop = {
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:js_interop"),
),
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:convert"),
),
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:_string"),
),
};
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) {
void 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,
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 || invalidFfiUsage) {
logger?.call("Skipped ffi transformation");
} else {
wasmFfiNativeAddressTrans.transformLibraries(
component,
coreTypes,
hierarchy,
transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
);
wasmFfiNativeTrans.transformLibraries(
component,
coreTypes,
hierarchy,
transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
);
transformFfiDefinitions.transformLibraries(
component,
coreTypes,
hierarchy,
transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
changedStructureNotifier,
);
transformFfiUseSites.transformLibraries(
this,
component,
coreTypes,
hierarchy,
transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
environmentDefines,
);
logger?.call("Transformed ffi annotations");
}
wasmTrans.transformLibraries(libraries, coreTypes, hierarchy);
wasmChecks.checkDartWasmApiUseIfImported(
libraries,
coreTypes,
diagnosticReporter,
mode == .standalone,
);
awaitTrans.transformLibraries(libraries, hierarchy, coreTypes);
}
@override
void performTransformationsOnProcedure(
CoreTypes coreTypes,
ClassHierarchy hierarchy,
Procedure procedure,
Map<String, String>? environmentDefines, {
void Function(String msg)? logger,
required DiagnosticReporter diagnosticReporter,
}) {
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 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);
}
@override
bool get supportsSetLiterals => true;
@override
bool get supportsFileUriExpression => true;
@override
int get enabledLateLowerings => LateLowering.all;
@override
int get enabledConstructorTearOffLowerings =>
ConstructorTearOffLowering.typedefs;
@override
bool get supportsExplicitGetterCalls => true;
@override
bool get supportsLateLoweringSentinel => false;
@override
bool get useStaticFieldLowering => false;
@override
Class concreteListLiteralClass(CoreTypes coreTypes) {
return _growableList ??= coreTypes.index.getClass(
'dart:_list',
'GrowableList',
);
}
@override
Class concreteConstListLiteralClass(CoreTypes coreTypes) {
return _immutableList ??= coreTypes.index.getClass(
'dart:_list',
'ImmutableList',
);
}
@override
Class concreteMapLiteralClass(CoreTypes coreTypes) {
return _wasmDefaultMap ??= coreTypes.index.getClass(
'dart:_compact_hash',
'DefaultMap',
);
}
@override
Class concreteConstMapLiteralClass(CoreTypes coreTypes) {
return _wasmImmutableMap ??= coreTypes.index.getClass(
'dart:_compact_hash',
'_ConstMap',
);
}
@override
Class concreteSetLiteralClass(CoreTypes coreTypes) {
return _wasmDefaultSet ??= coreTypes.index.getClass(
'dart:_compact_hash',
'DefaultSet',
);
}
@override
Class concreteConstSetLiteralClass(CoreTypes coreTypes) {
return _wasmImmutableSet ??= coreTypes.index.getClass(
'dart:_compact_hash',
'_ConstSet',
);
}
@override
Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
return _stringImpl ??= coreTypes.index.getClass(
"dart:_string",
mode == .standalone ? "EmbedderStringImpl" : "JSStringImpl",
);
}
@override
Class concreteClosureClass(CoreTypes coreTypes) {
return _closure ??= coreTypes.index.getClass('dart:core', '_Closure');
}
@override
bool isSupportedPragma(String pragmaName) =>
pragmaName.startsWith("wasm:") || pragmaName.startsWith("dyn-module:");
late final Map<RecordShape, Class> recordClasses;
@override
Class getRecordImplementationClass(
CoreTypes coreTypes,
int numPositionalFields,
List<String> namedFields,
) => recordClasses[RecordShape(numPositionalFields, namedFields)]!;
@override
Class concreteIntLiteralClass(CoreTypes coreTypes, int value) =>
_boxedInt ??= coreTypes.index.getClass("dart:_boxed_int", "BoxedInt");
@override
Class concreteDoubleLiteralClass(CoreTypes coreTypes, double value) =>
_boxedDouble ??= coreTypes.index.getClass(
"dart:_boxed_double",
"BoxedDouble",
);
@override
DartLibrarySupport get dartLibrarySupport => CustomizedDartLibrarySupport(
unsupported: {if (!enableExperimentalFfi) 'ffi'},
);
}
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;
}
}
const _dartFfiAndPragmaAllowlist = [
// Flutter/benchmarks.
'flutter',
'engine',
'ui',
// Non-SDK packages that have been migrated for the Wasm experiment but
// still have references to older interop libraries.
'package_info_plus',
'test',
'url_launcher_web',
];
/// Return whether [importUri] is always allowed to import `dart:ffi` or use
/// the `wasm:` pragmas.
bool allowedToImportDartFfiOrUsePragmas(Uri importUri) =>
// TODO(srujzs): While we allow these imports for all `dart:*` libraries, we
// may want to restrict this further, as it may include `dart:ui`.
importUri.isScheme('dart') ||
importUri.isScheme('package') &&
_dartFfiAndPragmaAllowlist.contains(importUri.pathSegments.first);
/// Report an error if [library] incorrectly depends on `dart:ffi` and return
/// whether an error is reported.
bool _checkDisallowedDartFfiUsage(
Library library,
DiagnosticReporter<Message, LocatedMessage> diagnosticReporter,
) {
if (allowedToImportDartFfiOrUsePragmas(library.importUri)) return false;
for (final dependency in library.dependencies) {
final dependencyUriString = dependency.targetLibrary.importUri.toString();
if (dependencyUriString == 'dart:ffi') {
diagnosticReporter.report(
diag.dartFfiLibraryInDart2Wasm,
dependency.fileOffset,
dependencyUriString.length,
library.fileUri,
);
return true;
}
}
return false;
}
/// Check that `wasm:import` and `wasm:export` pragmas are only used in `dart:`
/// libraries and in tests, with the exception of the
/// `reject_import_export_pragmas` test.
void _checkWasmImportExportPragmas(
Library library,
CoreTypes coreTypes,
DiagnosticReporter<Message, LocatedMessage> diagnosticReporter,
) {
if (allowedToImportDartFfiOrUsePragmas(library.importUri)) return;
for (Member member in library.members) {
if (util.hasWasmImportPragma(coreTypes, member) ||
util.hasWasmExportPragma(coreTypes, member) ||
util.hasWasmWeakExportPragma(coreTypes, member)) {
diagnosticReporter.report(
diag.wasmImportOrExportInUserCode,
member.fileOffset,
0,
library.fileUri,
);
}
}
}