| // 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 'dart:io' show File; |
| import 'dart:typed_data'; |
| |
| import 'package:build_integration/file_system/multi_root.dart' |
| show MultiRootFileSystem, MultiRootFileSystemEntity; |
| import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem; |
| import 'package:front_end/src/api_unstable/vm.dart' |
| show |
| CompilerOptions, |
| CompilerResult, |
| CfeDiagnosticMessage, |
| kernelForProgram, |
| CfeSeverity; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/binary/ast_from_binary.dart' |
| show BinaryBuilderWithMetadata; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/kernel.dart' |
| show writeComponentToText, loadComponentFromBytes; |
| import 'package:kernel/library_index.dart'; |
| import 'package:kernel/text/ast_to_text.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:kernel/verifier.dart'; |
| import 'package:vm/kernel_front_end.dart' show writeDepfile; |
| import 'package:vm/transformations/mixin_deduplication.dart' |
| as mixin_deduplication show transformLibraries; |
| import 'package:vm/transformations/to_string_transformer.dart' |
| as to_string_transformer; |
| import 'package:vm/transformations/type_flow/transformer.dart' as globalTypeFlow |
| show transformComponent; |
| import 'package:vm/transformations/type_flow/utils.dart' as tfa_utils; |
| import 'package:vm/transformations/unreachable_code_elimination.dart' |
| as unreachable_code_elimination; |
| import 'package:wasm_builder/wasm_builder.dart' show Serializer; |
| |
| import 'compiler_options.dart' as compiler; |
| import 'constant_evaluator.dart'; |
| import 'deferred_loading.dart'; |
| import 'dry_run.dart'; |
| import 'dynamic_module_kernel_metadata.dart'; |
| import 'dynamic_modules.dart'; |
| import 'js/method_collector.dart' show JSMethods; |
| import 'js/runtime_generator.dart' as js; |
| import 'modules.dart'; |
| import 'record_class_generator.dart'; |
| import 'records.dart'; |
| import 'target.dart' as wasm show Mode; |
| import 'target.dart' hide Mode; |
| import 'translator.dart'; |
| import 'util.dart'; |
| |
| sealed class CompilationResult {} |
| |
| abstract class CompilationDryRunResult extends CompilationResult {} |
| |
| class CompilationDryRunError extends CompilationDryRunResult {} |
| |
| class CompilationDryRunSuccess extends CompilationDryRunResult {} |
| |
| sealed class CompilationSuccess extends CompilationResult {} |
| |
| class CfeResult extends CompilationSuccess { |
| final Component component; |
| final CoreTypes coreTypes; |
| |
| CfeResult(this.component, this.coreTypes); |
| } |
| |
| class TfaResult extends CompilationSuccess { |
| final Component component; |
| final CoreTypes coreTypes; |
| final LibraryIndex libraryIndex; |
| final ModuleStrategy moduleStrategy; |
| final MainModuleMetadata mainModuleMetadata; |
| final JSMethods jsInteropMethods; |
| final Map<RecordShape, Class> recordClasses; |
| |
| TfaResult( |
| this.component, |
| this.coreTypes, |
| this.libraryIndex, |
| this.moduleStrategy, |
| this.mainModuleMetadata, |
| this.jsInteropMethods, |
| this.recordClasses); |
| } |
| |
| class CodegenResult extends CompilationSuccess { |
| final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules; |
| final String jsRuntime; |
| final String supportJs; |
| |
| CodegenResult(this.wasmModules, this.jsRuntime, this.supportJs); |
| } |
| |
| abstract class CompilationError extends CompilationResult {} |
| |
| /// The CFE has crashed with an exception. |
| /// |
| /// This is a CFE bug and should be reported by users. |
| class CFECrashError extends CompilationError { |
| final Object error; |
| final StackTrace stackTrace; |
| |
| CFECrashError(this.error, this.stackTrace); |
| |
| @override |
| String toString() => 'CFECrashError($error):\n$stackTrace'; |
| } |
| |
| /// Compiling the Dart program resulted in compile-time errors. |
| /// |
| /// This is a bug in the dart program (e.g. syntax errors, static type errors, |
| /// ...) that's being compiled. Users have to address those errors in their |
| /// code for it to compile successfully. |
| /// |
| /// The errors are already printed via the `handleDiagnosticMessage` callback. |
| /// (We print them as soon as they are reported by CFE. i.e. we stream errors |
| /// instead of accumulating/batching all of them and reporting at the end.) |
| class CFECompileTimeErrors extends CompilationError { |
| final Component? component; |
| |
| CFECompileTimeErrors(this.component); |
| } |
| |
| const List<String> _librariesToIndex = [ |
| "dart:_boxed_bool", |
| "dart:_boxed_double", |
| "dart:_boxed_int", |
| "dart:_compact_hash", |
| "dart:_internal", |
| "dart:_js_helper", |
| "dart:_js_types", |
| "dart:_list", |
| "dart:_string", |
| "dart:_wasm", |
| "dart:async", |
| "dart:collection", |
| "dart:core", |
| "dart:ffi", |
| "dart:typed_data", |
| ]; |
| |
| /// Compile a Dart file into a Wasm module. |
| /// |
| /// Returns `null` if an error occurred during compilation. The |
| /// [handleDiagnosticMessage] callback will have received an error message |
| /// describing the error. |
| /// |
| /// When generating source maps, `sourceMapUrlGenerator` argument should be |
| /// provided which takes the module name and gives the URL of the source map. |
| /// This value will be added to the Wasm module in `sourceMappingURL` section. |
| /// When this argument is null the code generator does not generate source |
| /// mappings. |
| Future<CompilationResult> compile( |
| compiler.WasmCompilerOptions options, |
| FileSystem fileSystem, |
| Uri Function(String moduleName)? sourceMapUrlGenerator, |
| void Function(CfeDiagnosticMessage) handleDiagnosticMessage) async { |
| final wasm.Mode mode; |
| if (options.translatorOptions.jsCompatibility) { |
| mode = wasm.Mode.jsCompatibility; |
| } else { |
| mode = wasm.Mode.regular; |
| } |
| final WasmTarget target = WasmTarget( |
| enableExperimentalFfi: options.translatorOptions.enableExperimentalFfi, |
| enableExperimentalWasmInterop: |
| options.translatorOptions.enableExperimentalWasmInterop, |
| removeAsserts: !options.translatorOptions.enableAsserts, |
| mode: mode); |
| |
| if (options.multiRootScheme != null) { |
| fileSystem = MultiRootFileSystem( |
| options.multiRootScheme!, |
| options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots, |
| fileSystem); |
| } |
| |
| CfeResult? cfeResult; |
| TfaResult? tfaResult; |
| CompilationResult? lastResult; |
| |
| for (final phase in options.phases) { |
| switch (phase) { |
| case compiler.CompilerPhase.cfe: |
| lastResult = await _runCfePhase( |
| options, target, fileSystem, handleDiagnosticMessage); |
| if (lastResult is! CfeResult) return lastResult; |
| cfeResult = lastResult; |
| |
| case compiler.CompilerPhase.tfa: |
| lastResult = await _runTfaPhase( |
| cfeResult ?? await _loadCfeResult(options), |
| options, |
| target, |
| fileSystem); |
| if (lastResult is! TfaResult) return lastResult; |
| tfaResult = lastResult; |
| |
| case compiler.CompilerPhase.codegen: |
| lastResult = await _runCodegenPhase( |
| tfaResult ?? await _loadTfaResult(options, target, fileSystem), |
| options, |
| fileSystem, |
| sourceMapUrlGenerator); |
| } |
| } |
| |
| return lastResult!; |
| } |
| |
| Future<CfeResult> _loadCfeResult(compiler.WasmCompilerOptions options) async { |
| final component = |
| loadComponentFromBytes(await File.fromUri(options.mainUri).readAsBytes()); |
| final coreTypes = CoreTypes(component); |
| return CfeResult(component, coreTypes); |
| } |
| |
| Future<CompilationResult> _runCfePhase( |
| compiler.WasmCompilerOptions options, |
| WasmTarget target, |
| FileSystem fileSystem, |
| void Function(CfeDiagnosticMessage) handleDiagnosticMessage) async { |
| var hadCompileTimeError = false; |
| void diagnosticMessageHandler(CfeDiagnosticMessage message) { |
| if (message.severity == CfeSeverity.error) { |
| hadCompileTimeError = true; |
| } |
| handleDiagnosticMessage(message); |
| } |
| |
| CompilerOptions compilerOptions = CompilerOptions() |
| ..target = target |
| // This is a dummy directory that always exists. This option should be |
| // unused as we pass platform.dill or libraries.json, though currently the |
| // CFE mandates this option to be there (but doesn't use it). |
| // => Remove this once CFE no longer mandates this (or remove option in CFE |
| // entirely). |
| ..sdkRoot = Uri.file('.') |
| ..librariesSpecificationUri = options.librariesSpecPath |
| ..packagesFileUri = options.packagesPath |
| ..environmentDefines = { |
| 'dart.tool.dart2wasm': 'true', |
| 'dart.tool.dart2wasm.minify': '${options.translatorOptions.minify}', |
| ...options.environment, |
| } |
| ..explicitExperimentalFlags = options.feExperimentalFlags |
| ..verbose = false |
| ..onDiagnostic = diagnosticMessageHandler |
| ..fileSystem = fileSystem; |
| |
| if (options.platformPath != null) { |
| compilerOptions.sdkSummary = options.platformPath; |
| } else { |
| compilerOptions.compileSdk = true; |
| } |
| |
| final dynamicMainModuleUri = |
| await _resolveUri(fileSystem, options.dynamicMainModuleUri); |
| final isDynamicSubmodule = |
| options.dynamicModuleType == DynamicModuleType.submodule; |
| if (isDynamicSubmodule) { |
| compilerOptions.additionalDills.add(dynamicMainModuleUri!); |
| |
| if (options.validateDynamicModules) { |
| // We must pass the unresolved URI here to be compatible with the CFE |
| // dynamic interface validator. |
| compilerOptions.dynamicInterfaceSpecificationUri = |
| options.dynamicInterfaceUri; |
| } |
| } |
| |
| CompilerResult? compilerResult; |
| try { |
| compilerResult = await kernelForProgram(options.mainUri, compilerOptions, |
| requireMain: !isDynamicSubmodule); |
| } catch (e, s) { |
| return CFECrashError(e, s); |
| } |
| if (options.dryRun) { |
| final component = compilerResult?.component; |
| if (component == null) { |
| return CompilationDryRunError(); |
| } |
| final summarizer = DryRunSummarizer(component); |
| final hasErrors = await summarizer.summarize(); |
| return hasErrors ? CompilationDryRunError() : CompilationDryRunSuccess(); |
| } |
| if (hadCompileTimeError) { |
| return CFECompileTimeErrors(compilerResult?.component); |
| } |
| final component = compilerResult!.component!; |
| |
| if (options.dumpKernelAfterCfe != null) { |
| writeComponentToText(component, |
| path: options.dumpKernelAfterCfe!, showMetadata: true); |
| } |
| |
| return CfeResult(component, compilerResult.coreTypes!); |
| } |
| |
| Future<TfaResult> _loadTfaResult(compiler.WasmCompilerOptions options, |
| WasmTarget target, FileSystem fileSystem) async { |
| final component = createEmptyComponent(); |
| final recordClassesRepository = _RecordClassesRepository(); |
| final interopMethodsRepository = _InteropMethodsRepository(); |
| component.addMetadataRepository(recordClassesRepository); |
| component.addMetadataRepository(interopMethodsRepository); |
| |
| BinaryBuilderWithMetadata(await File.fromUri(options.mainUri).readAsBytes()) |
| .readComponent(component); |
| final coreTypes = CoreTypes(component); |
| final libraryIndex = LibraryIndex(component, _librariesToIndex); |
| final classHierarchy = ClassHierarchy(component, coreTypes); |
| final dynamicMainModuleUri = |
| await _resolveUri(fileSystem, options.dynamicMainModuleUri); |
| final dynamicInterfaceUri = |
| await _resolveUri(fileSystem, options.dynamicInterfaceUri); |
| |
| final moduleStrategy = _createModuleStrategy(options, component, coreTypes, |
| target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri); |
| |
| final recordClasses = <RecordShape, Class>{}; |
| recordClassesRepository.mapping.forEach((cls, shape) { |
| recordClasses[shape] = cls; |
| }); |
| |
| final isDynamicMainModule = |
| options.dynamicModuleType == DynamicModuleType.main; |
| final isDynamicSubmodule = |
| options.dynamicModuleType == DynamicModuleType.submodule; |
| MainModuleMetadata mainModuleMetadata = |
| MainModuleMetadata.empty(options.translatorOptions, options.environment); |
| |
| if (isDynamicSubmodule) { |
| mainModuleMetadata = |
| await deserializeMainModuleMetadata(component, options); |
| mainModuleMetadata.verifyDynamicSubmoduleOptions(options); |
| } else if (isDynamicMainModule) { |
| MainModuleMetadata.verifyMainModuleOptions(options); |
| } |
| |
| return TfaResult(component, coreTypes, libraryIndex, moduleStrategy, |
| mainModuleMetadata, interopMethodsRepository.mapping, recordClasses); |
| } |
| |
| Future<CompilationResult> _runTfaPhase( |
| CfeResult cfeResult, |
| compiler.WasmCompilerOptions options, |
| WasmTarget target, |
| FileSystem fileSystem) async { |
| var CfeResult(:component, :coreTypes) = cfeResult; |
| |
| ClosedWorldClassHierarchy classHierarchy = |
| ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy; |
| LibraryIndex libraryIndex = LibraryIndex(component, _librariesToIndex); |
| |
| if (options.deleteToStringPackageUri.isNotEmpty) { |
| to_string_transformer.transformComponent( |
| component, options.deleteToStringPackageUri); |
| } |
| |
| var jsInteropMethods = js.performJSInteropTransformations( |
| component.getDynamicSubmoduleLibraries(coreTypes), |
| coreTypes, |
| classHierarchy); |
| |
| final dynamicMainModuleUri = |
| await _resolveUri(fileSystem, options.dynamicMainModuleUri); |
| final dynamicInterfaceUri = |
| await _resolveUri(fileSystem, options.dynamicInterfaceUri); |
| final isDynamicMainModule = |
| options.dynamicModuleType == DynamicModuleType.main; |
| final isDynamicSubmodule = |
| options.dynamicModuleType == DynamicModuleType.submodule; |
| |
| if (isDynamicSubmodule) { |
| // Join the submodule libraries with the TFAed component from the main |
| // module compilation. JS interop transformer must be run before this since |
| // some methods it uses may have been tree-shaken from the TFAed component. |
| (component, jsInteropMethods) = await generateDynamicSubmoduleComponent( |
| component, coreTypes, dynamicMainModuleUri!, jsInteropMethods); |
| coreTypes = CoreTypes(component); |
| classHierarchy = |
| ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy; |
| libraryIndex = LibraryIndex(component, _librariesToIndex); |
| } |
| |
| final librariesToTransform = isDynamicSubmodule |
| ? component.getDynamicSubmoduleLibraries(coreTypes) |
| : component.libraries; |
| final constantEvaluator = ConstantEvaluator( |
| options, target, component, coreTypes, classHierarchy, libraryIndex); |
| unreachable_code_elimination.transformLibraries(target, librariesToTransform, |
| constantEvaluator, options.translatorOptions.enableAsserts); |
| |
| final Map<RecordShape, Class> recordClasses = generateRecordClasses( |
| component, coreTypes, |
| isDynamicMainModule: isDynamicMainModule, |
| isDynamicSubmodule: isDynamicSubmodule); |
| target.recordClasses = recordClasses; |
| |
| if (options.dumpKernelBeforeTfa != null) { |
| writeComponentToText(component, |
| path: options.dumpKernelBeforeTfa!, showMetadata: true); |
| } |
| |
| final moduleStrategy = _createModuleStrategy(options, component, coreTypes, |
| target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri); |
| |
| // DynamicMainModuleStrategy.prepareComponent() includes |
| // dynamic_interface_annotator transformation which annotates AST nodes with |
| // pragmas and should precede other transformations looking at pragmas |
| // (such as mixin_deduplication and TFA). |
| moduleStrategy.prepareComponent(); |
| |
| final hasDeferredImports = component.libraries |
| .any((lib) => lib.dependencies.any((d) => d.isDeferred)); |
| if (hasDeferredImports) { |
| DeferredLoadingLowering.markRuntimeFunctionsAsEntrypoints(coreTypes); |
| } |
| |
| mixin_deduplication.transformLibraries( |
| librariesToTransform, coreTypes, target); |
| |
| MainModuleMetadata mainModuleMetadata = |
| MainModuleMetadata.empty(options.translatorOptions, options.environment); |
| |
| if (isDynamicSubmodule) { |
| mainModuleMetadata = |
| await deserializeMainModuleMetadata(component, options); |
| mainModuleMetadata.verifyDynamicSubmoduleOptions(options); |
| } else if (isDynamicMainModule) { |
| MainModuleMetadata.verifyMainModuleOptions(options); |
| await serializeMainModuleComponent(component, dynamicMainModuleUri!, |
| optimized: false); |
| } |
| |
| if (!isDynamicSubmodule) { |
| _patchMainTearOffs(coreTypes, component); |
| |
| // We initialize the [printStats] to `false` to prevent it's field |
| // initializer to run (which only works on VM -- but we want our compiler |
| // to also run if compiled via dart2js/dart2wasm) |
| tfa_utils.printStats = false; |
| |
| // Keep the flags in-sync with |
| // pkg/vm/test/transformations/type_flow/transformer_test.dart |
| globalTypeFlow.transformComponent(target, coreTypes, component, |
| useRapidTypeAnalysis: false); |
| } |
| |
| if (options.phases.last == compiler.CompilerPhase.tfa) { |
| // Store metadata needed for codegen so that it can be serialized. |
| final recordClassesRepo = _RecordClassesRepository(); |
| recordClasses.forEach((shape, cls) { |
| recordClassesRepo.mapping[cls] = shape; |
| }); |
| component.addMetadataRepository(recordClassesRepo); |
| |
| final interopMethodsRepo = _InteropMethodsRepository(); |
| jsInteropMethods.forEach((method, info) { |
| interopMethodsRepo.mapping[method] = info; |
| }); |
| component.addMetadataRepository(interopMethodsRepo); |
| } |
| |
| assert(() { |
| verifyComponent( |
| target, VerificationStage.afterGlobalTransformations, component); |
| return true; |
| }()); |
| |
| if (options.dumpKernelAfterTfa != null) { |
| writeComponentToText(component, |
| path: options.dumpKernelAfterTfa!, showMetadata: true); |
| } |
| |
| return TfaResult(component, coreTypes, libraryIndex, moduleStrategy, |
| mainModuleMetadata, jsInteropMethods, recordClasses); |
| } |
| |
| Future<CompilationResult> _runCodegenPhase( |
| TfaResult tfaSuccess, |
| compiler.WasmCompilerOptions options, |
| FileSystem fileSystem, |
| Uri Function(String moduleName)? sourceMapUrlGenerator) async { |
| final TfaResult( |
| :component, |
| :coreTypes, |
| :moduleStrategy, |
| :libraryIndex, |
| :recordClasses, |
| :mainModuleMetadata, |
| :jsInteropMethods |
| ) = tfaSuccess; |
| |
| final loadingMap = DeferredModuleLoadingMap.fromComponent(component); |
| component.accept(DeferredLoadingLowering(coreTypes, loadingMap)); |
| |
| // May populate [loadingMap] by creating various [ModuleOutputData]. |
| await moduleStrategy.processComponentAfterTfa(loadingMap); |
| |
| final moduleOutputData = moduleStrategy.buildModuleOutputData(); |
| |
| final translator = Translator(component, coreTypes, libraryIndex, |
| recordClasses, loadingMap, moduleOutputData, options.translatorOptions, |
| mainModuleMetadata: mainModuleMetadata, |
| enableDynamicModules: options.enableDynamicModules); |
| |
| String? depFile = options.depFile; |
| if (depFile != null) { |
| writeDepfile( |
| fileSystem, component.uriToSource.keys, options.outputFile, depFile); |
| } |
| |
| final generateSourceMaps = options.translatorOptions.generateSourceMaps; |
| final modules = translator.translate(sourceMapUrlGenerator); |
| final wasmModules = <String, ({Uint8List moduleBytes, String? sourceMap})>{}; |
| modules.forEach((moduleOutput, module) { |
| if (moduleOutput.skipEmit) return; |
| final serializer = Serializer(); |
| module.serialize(serializer); |
| final wasmModuleSerialized = serializer.data; |
| |
| final sourceMap = |
| generateSourceMaps ? serializer.sourceMapSerializer.serialize() : null; |
| wasmModules[moduleOutput.moduleName] = |
| (moduleBytes: wasmModuleSerialized, sourceMap: sourceMap); |
| }); |
| |
| final jsRuntimeFinalizer = js.RuntimeFinalizer(jsInteropMethods); |
| |
| final dynamicMainModuleUri = |
| await _resolveUri(fileSystem, options.dynamicMainModuleUri); |
| final isDynamicMainModule = |
| options.dynamicModuleType == DynamicModuleType.main; |
| final isDynamicSubmodule = |
| options.dynamicModuleType == DynamicModuleType.submodule; |
| |
| final jsRuntime = isDynamicSubmodule |
| ? jsRuntimeFinalizer.generateDynamicSubmodule( |
| translator.functions.translatedProcedures, |
| translator.options.requireJsStringBuiltin, |
| translator.internalizedStringsForJSRuntime) |
| : jsRuntimeFinalizer.generate( |
| translator.functions.translatedProcedures, |
| translator.internalizedStringsForJSRuntime, |
| translator.options.requireJsStringBuiltin, |
| translator.options.enableDeferredLoading || |
| translator.options.enableMultiModuleStressTestMode || |
| translator.dynamicModuleSupportEnabled); |
| |
| final supportJs = _generateSupportJs(options.translatorOptions); |
| if (isDynamicMainModule) { |
| await serializeMainModuleMetadata(component, translator, options); |
| await serializeMainModuleComponent(component, dynamicMainModuleUri!, |
| optimized: true); |
| } |
| |
| final loadIdsFile = options.loadsIdsUri; |
| if (loadIdsFile != null) { |
| await writeLoadIdsFile(component, coreTypes, options, loadingMap); |
| } |
| |
| return CodegenResult(wasmModules, jsRuntime, supportJs); |
| } |
| |
| ModuleStrategy _createModuleStrategy( |
| compiler.WasmCompilerOptions options, |
| Component component, |
| CoreTypes coreTypes, |
| WasmTarget target, |
| ClassHierarchy classHierarchy, |
| Uri? dynamicMainModuleUri, |
| Uri? dynamicInterfaceUri) { |
| final isDynamicMainModule = |
| options.dynamicModuleType == DynamicModuleType.main; |
| final isDynamicSubmodule = |
| options.dynamicModuleType == DynamicModuleType.submodule; |
| if (options.translatorOptions.enableDeferredLoading) { |
| return DeferredLoadingModuleStrategy(component, options, target, coreTypes); |
| } else if (options.translatorOptions.enableMultiModuleStressTestMode) { |
| return StressTestModuleStrategy( |
| component, coreTypes, options, target, classHierarchy); |
| } else if (isDynamicMainModule) { |
| return DynamicMainModuleStrategy( |
| component, |
| coreTypes, |
| options, |
| File.fromUri(dynamicInterfaceUri!).readAsStringSync(), |
| options.dynamicInterfaceUri!); |
| } else if (isDynamicSubmodule) { |
| return DynamicSubmoduleStrategy( |
| component, options, target, coreTypes, dynamicMainModuleUri!); |
| } |
| return DefaultModuleStrategy(coreTypes, component, options); |
| } |
| |
| // Patches `dart:_internal`s `mainTearOff{0,1,2}` getters. |
| void _patchMainTearOffs(CoreTypes coreTypes, Component component) { |
| final mainMethod = component.mainMethod!; |
| final mainMethodType = mainMethod.computeSignatureOrFunctionType(); |
| void patchToReturnMainTearOff(Procedure p) { |
| p.function.body = |
| ReturnStatement(ConstantExpression(StaticTearOffConstant(mainMethod))) |
| ..parent = p.function; |
| } |
| |
| final typeEnv = |
| TypeEnvironment(coreTypes, ClassHierarchy(component, coreTypes)); |
| bool mainHasType(DartType type) => typeEnv.isSubtypeOf(mainMethodType, type); |
| |
| final internalLib = coreTypes.index.getLibrary('dart:_internal'); |
| (Procedure, DartType) lookupAndInitialize(String name) { |
| final p = internalLib.procedures |
| .singleWhere((procedure) => procedure.name.text == name); |
| p.isExternal = false; |
| p.function.body = ReturnStatement(NullLiteral())..parent = p.function; |
| return (p, p.function.returnType.toNonNull()); |
| } |
| |
| final (mainTearOff0, mainArg0Type) = lookupAndInitialize('mainTearOffArg0'); |
| final (mainTearOff1, mainArg1Type) = lookupAndInitialize('mainTearOffArg1'); |
| final (mainTearOff2, mainArg2Type) = lookupAndInitialize('mainTearOffArg2'); |
| if (mainHasType(mainArg2Type)) return patchToReturnMainTearOff(mainTearOff2); |
| if (mainHasType(mainArg1Type)) return patchToReturnMainTearOff(mainTearOff1); |
| if (mainHasType(mainArg0Type)) return patchToReturnMainTearOff(mainTearOff0); |
| } |
| |
| Future<Uri?> _resolveUri(FileSystem fileSystem, Uri? uri) async { |
| if (uri == null) return null; |
| var fileSystemEntity = fileSystem.entityForUri(uri); |
| if (fileSystemEntity is MultiRootFileSystemEntity) { |
| fileSystemEntity = await fileSystemEntity.delegate; |
| } |
| return fileSystemEntity.uri; |
| } |
| |
| class _RecordClassesRepository extends MetadataRepository<RecordShape> { |
| static const String _tag = 'dart2wasm.recordClasses'; |
| @override |
| final Map<Class, RecordShape> mapping = {}; |
| |
| @override |
| RecordShape readFromBinary(Node node, BinarySource source) { |
| final positionals = source.readUInt30(); |
| final namesLength = source.readUInt30(); |
| final names = namesLength == 0 ? const <String>[] : <String>[]; |
| for (int i = 0; i < namesLength; i++) { |
| names.add(source.readStringReference()); |
| } |
| return RecordShape(positionals, names); |
| } |
| |
| @override |
| String get tag => _tag; |
| |
| @override |
| void writeToBinary(RecordShape metadata, Node node, BinarySink sink) { |
| sink.writeUInt30(metadata.positionals); |
| sink.writeUInt30(metadata.names.length); |
| for (final name in metadata.names) { |
| sink.writeStringReference(name); |
| } |
| } |
| } |
| |
| class _InteropMethodsRepository |
| extends MetadataRepository<({String importName, String jsCode})> { |
| static const String _tag = 'dart2wasm.interopMethods'; |
| @override |
| final Map<Procedure, ({String importName, String jsCode})> mapping = {}; |
| |
| @override |
| ({String importName, String jsCode}) readFromBinary( |
| Node node, BinarySource source) { |
| final importName = source.readStringReference(); |
| final jsCode = source.readStringReference(); |
| return (importName: importName, jsCode: jsCode); |
| } |
| |
| @override |
| String get tag => _tag; |
| |
| @override |
| void writeToBinary(({String importName, String jsCode}) metadata, Node node, |
| BinarySink sink) { |
| sink.writeStringReference(metadata.importName); |
| sink.writeStringReference(metadata.jsCode); |
| } |
| } |
| |
| String _generateSupportJs(TranslatorOptions options) { |
| // Copied from |
| // https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/gc/index.js |
| // |
| // Uses WasmGC types and will only validate correctly if the engine supports |
| // WasmGC: |
| // ``` |
| // (module |
| // (type $type0 (struct (field $field0 i8))) |
| // ) |
| // ``` |
| // |
| // NOTE: Once we support more feature detections we may use |
| // `package:wasm_builder` to create the module instead of having a fixed one |
| // here. |
| const String supportsWasmGC = |
| 'WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,95,1,120,0]))'; |
| |
| // Imports a `js-string` builtin spec function *with wrong signature*. An engine |
| // |
| // * *without* knowledge about `js-string` builtin would accept such an import at |
| // validation time. |
| // |
| // * *with* knowledge about `js-string` would refuse it as the signature |
| // used to import the `cast` function is not according to `js-string` spec |
| // |
| // ``` |
| // (module |
| // (func $wasm:js-string.cast (;0;) (import "wasm:js-string" "cast")) |
| // ) |
| // ``` |
| const String supportsJsStringBuiltins = |
| '!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,23,1,14,119,97,115,109,58,106,115,45,115,116,114,105,110,103,4,99,97,115,116,0,0]),{"builtins":["js-string"]})'; |
| |
| final requiredFeatures = [ |
| supportsWasmGC, |
| if (options.requireJsStringBuiltin) supportsJsStringBuiltins |
| ]; |
| return '(${requiredFeatures.join('&&')})'; |
| } |
| |
| String writeComponentToString(Component component) { |
| final buffer = StringBuffer(); |
| Printer(buffer).writeComponentFile(component); |
| return '$buffer'; |
| } |