| // 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, messageWasmImportOrExportInUserCode; |
| 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, EvaluationMode; |
| 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; |
| |
| enum Mode { |
| regular, |
| 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 = |
| 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? _oneByteString; |
| Class? _twoByteString; |
| Class? _jsString; |
| 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' |
| }; |
| } |
| |
| String get platformFile { |
| return switch (mode) { |
| Mode.regular => 'dart2wasm_platform.dill', |
| Mode.jsCompatibility => 'dart2wasm_js_compatibility_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:_js_helper', |
| 'dart:_js_types', |
| 'dart:_list', |
| 'dart:_string', |
| 'dart:_wasm', |
| 'dart:async', |
| 'dart:developer', |
| 'dart:ffi', |
| 'dart:io', |
| 'dart:js_interop', |
| 'dart:js_interop_unsafe', |
| 'dart:js_util', |
| 'dart:nativewrappers', |
| 'dart:typed_data', |
| ]; |
| |
| @override |
| List<String> get extraIndexedLibraries => [ |
| 'dart:_boxed_bool', |
| 'dart:_boxed_double', |
| 'dart:_boxed_int', |
| 'dart:_compact_hash', |
| 'dart:_js_helper', |
| 'dart:_js_types', |
| 'dart:_list', |
| 'dart:_string', |
| 'dart:_wasm', |
| 'dart:collection', |
| 'dart:js_interop', |
| 'dart:js_interop_unsafe', |
| 'dart:js_util', |
| 'dart:typed_data', |
| ]; |
| |
| @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') { |
| return enableExperimentalWasmInterop; |
| } |
| |
| final importerString = importer.toString(); |
| |
| // We have some tests that import dart:js* |
| if (importerString.contains('tests/web/wasm')) return true; |
| |
| // Flutter's dart:ui is also package:ui (in test mode) |
| if (importerString.startsWith('package:ui/')) return true; |
| |
| // package:js can import dart:js_util & dart:_js_* |
| if (importerString.startsWith('package:js/')) 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, enableExperimentalFfi: enableExperimentalFfi); |
| // 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}) { |
| if (!enableExperimentalWasmInterop) { |
| // Check `wasm:import` and `wasm:export` pragmas before FFI transforms as |
| // FFI transforms convert JS interop annotations to these pragmas. |
| _checkWasmImportExportPragmas(libraries, coreTypes, |
| diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>); |
| } |
| |
| 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")), |
| ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( |
| component, Uri.parse("dart:convert")), |
| ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed( |
| component, 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, |
| 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 { |
| 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(component, coreTypes, hierarchy, |
| transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex); |
| logger?.call("Transformed ffi annotations"); |
| } |
| |
| wasmTrans.transformLibraries(libraries, coreTypes, hierarchy); |
| |
| awaitTrans.transformLibraries(libraries, hierarchy, coreTypes); |
| } |
| |
| @override |
| void performTransformationsOnProcedure( |
| CoreTypes coreTypes, |
| ClassHierarchy hierarchy, |
| Procedure procedure, |
| Map<String, String>? environmentDefines, |
| {void Function(String msg)? logger}) { |
| 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 |
| 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 |
| 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) { |
| // In JSCM all strings are JS strings. |
| if (mode == Mode.jsCompatibility) { |
| return _jsString ??= |
| coreTypes.index.getClass("dart:_string", "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'); |
| } |
| |
| // In dart2wasm we can't assume that `x == "hello"` means `x`'s class is |
| // `concreteStringLiteralClass("hello")`, it may also be `JSStringImpl` when |
| // it's obtained from a JS call, or `TwoByteString` when it's a substring of a |
| // `TwoByteString`. |
| @override |
| bool get canInferStringClassAfterEqualityComparison => false; |
| |
| @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; |
| } |
| } |
| |
| final _dartCoreUri = Uri.parse('dart:core'); |
| |
| /// Check that `wasm:import` and `wasm:export` pragmas are only used in `dart:` |
| /// libraries and in tests, with the exception of |
| /// `reject_import_export_pragmas` test. |
| void _checkWasmImportExportPragmas(List<Library> libraries, CoreTypes coreTypes, |
| DiagnosticReporter<Message, LocatedMessage> diagnosticReporter) { |
| for (Library library in libraries) { |
| final importUri = library.importUri; |
| if (importUri.isScheme('dart') || |
| (importUri.isScheme('package') && |
| JsInteropChecks.allowedInteropLibrariesInDart2WasmPackages |
| .any((pkg) => importUri.pathSegments.first == pkg))) { |
| continue; |
| } |
| |
| for (Member member in library.members) { |
| for (Expression annotation in member.annotations) { |
| if (annotation is! ConstantExpression) { |
| continue; |
| } |
| final annotationConstant = annotation.constant; |
| if (annotationConstant is! InstanceConstant) { |
| continue; |
| } |
| final cls = annotationConstant.classNode; |
| if (cls.name == 'pragma' && |
| cls.enclosingLibrary.importUri == _dartCoreUri) { |
| final pragmaName = annotationConstant |
| .fieldValues[coreTypes.pragmaName.fieldReference]; |
| if (pragmaName is StringConstant) { |
| if (pragmaName.value == 'wasm:import' || |
| pragmaName.value == 'wasm:export') { |
| diagnosticReporter.report( |
| messageWasmImportOrExportInUserCode, |
| annotation.fileOffset, |
| 0, |
| library.fileUri, |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |