| // Copyright (c) 2018, 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'; |
| import 'dart:typed_data'; |
| |
| import 'package:compiler/compiler_api.dart' as api; |
| import 'package:compiler/src/commandline_options.dart'; |
| import 'package:compiler/src/common/codegen.dart'; |
| import 'package:compiler/src/compiler.dart'; |
| import 'package:compiler/src/dump_info.dart'; |
| import 'package:compiler/src/inferrer/types.dart'; |
| import 'package:compiler/src/io/source_information.dart'; |
| import 'package:compiler/src/js_model/js_world.dart'; |
| import 'package:compiler/src/serialization/serialization.dart'; |
| import 'package:compiler/src/serialization/strategies.dart'; |
| import 'package:compiler/src/util/memory_compiler.dart'; |
| import 'package:expect/expect.dart'; |
| import 'package:kernel/ast.dart' as ir; |
| |
| import '../helpers/text_helpers.dart'; |
| |
| /// Entries in dump info that naturally differ between compilations. |
| const List<String> dumpInfoExceptions = [ |
| '"compilationMoment":', |
| '"compilationDuration":', |
| '"toJsonDuration":', |
| '"ramUsage":', |
| ]; |
| |
| Future<void> generateJavaScriptCode( |
| Compiler compiler, |
| GlobalTypeInferenceResults globalTypeInferenceResults, |
| ) async { |
| final codegenInputs = compiler.initializeCodegen(globalTypeInferenceResults); |
| final codegenResults = OnDemandCodegenResults( |
| codegenInputs, |
| compiler.backendStrategy.functionCompiler, |
| ); |
| final programSize = compiler.runCodegenEnqueuer( |
| codegenResults, |
| globalTypeInferenceResults.inferredData, |
| SourceLookup(compiler.componentForTesting), |
| globalTypeInferenceResults.closedWorld, |
| ); |
| if (compiler.options.stage.emitsDumpInfo) { |
| await compiler.runDumpInfo( |
| codegenResults, |
| globalTypeInferenceResults, |
| DumpInfoProgramData.fromEmitterResults( |
| compiler.backendStrategy.emitterTask, |
| compiler.dumpInfoRegistry, |
| programSize, |
| ), |
| ); |
| } |
| } |
| |
| Future<void> finishCompileAndCompare( |
| Map<api.OutputType, Map<String, String>> expectedOutput, |
| OutputCollector actualOutputCollector, |
| Compiler compiler, |
| SerializationStrategy strategy, { |
| bool stoppedAfterClosedWorld = false, |
| bool stoppedAfterTypeInference = false, |
| }) async { |
| if (stoppedAfterClosedWorld) { |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; |
| var newClosedWorld = cloneClosedWorld(compiler, closedWorld, strategy); |
| compiler.performGlobalTypeInference(newClosedWorld); |
| } |
| |
| if (stoppedAfterClosedWorld || stoppedAfterTypeInference) { |
| GlobalTypeInferenceResults globalInferenceResults = |
| compiler.globalInference.resultsForTesting!; |
| GlobalTypeInferenceResults newGlobalInferenceResults = |
| cloneInferenceResults(compiler, globalInferenceResults, strategy); |
| await generateJavaScriptCode(compiler, newGlobalInferenceResults); |
| } |
| var actualOutput = actualOutputCollector.clear(); |
| Expect.setEquals( |
| expectedOutput.keys, |
| actualOutput.keys, |
| "Output type mismatch.", |
| ); |
| |
| void check(api.OutputType outputType, Map<String, String> fileMap) { |
| Map<String, String> newFileMap = actualOutput[outputType]!; |
| Expect.setEquals( |
| fileMap.keys, |
| newFileMap.keys, |
| "File mismatch for output type $outputType.", |
| ); |
| fileMap.forEach((String fileName, String code) { |
| String newCode = newFileMap[fileName]!; |
| bool Function(int, List<String>, List<String>)? filter; |
| if (outputType == api.OutputType.dumpInfo) { |
| filter = (int index, List<String> lines1, List<String> lines2) { |
| if (index <= lines1.length && index <= lines2.length) { |
| String line1 = lines1[index]; |
| String line2 = lines2[index]; |
| for (String exception in dumpInfoExceptions) { |
| if (line1.trim().startsWith(exception) && |
| line2.trim().startsWith(exception)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| }; |
| } |
| int? failureLine = checkEqualContentAndShowDiff( |
| code, |
| newCode, |
| filter: filter, |
| ); |
| Expect.isNull( |
| failureLine, |
| "Output mismatch at line $failureLine in " |
| "file '${fileName}' of type ${outputType}.", |
| ); |
| }); |
| } |
| |
| expectedOutput.forEach(check); |
| } |
| |
| runTest({ |
| Uri? entryPoint, |
| Map<String, String> memorySourceFiles = const <String, String>{}, |
| Uri? packageConfig, |
| Uri? librariesSpecificationUri, |
| required List<String> options, |
| SerializationStrategy strategy = const BytesInMemorySerializationStrategy(), |
| bool useDataKinds = false, |
| }) async { |
| var commonOptions = options + ['--out=out.js']; |
| OutputCollector collector = OutputCollector(); |
| CompilationResult result = await runCompiler( |
| entryPoint: entryPoint, |
| memorySourceFiles: memorySourceFiles, |
| packageConfig: packageConfig, |
| librariesSpecificationUri: librariesSpecificationUri, |
| options: commonOptions, |
| outputProvider: collector, |
| beforeRun: (Compiler compiler) { |
| compiler.forceSerializationForTesting = true; |
| }, |
| ); |
| Expect.isTrue(result.isSuccess); |
| Map<api.OutputType, Map<String, String>> expectedOutput = collector.clear(); |
| |
| OutputCollector collector2 = OutputCollector(); |
| CompilationResult result2 = await runCompiler( |
| entryPoint: entryPoint, |
| memorySourceFiles: memorySourceFiles, |
| packageConfig: packageConfig, |
| librariesSpecificationUri: librariesSpecificationUri, |
| options: commonOptions, |
| outputProvider: collector2, |
| beforeRun: (Compiler compiler) { |
| compiler.forceSerializationForTesting = true; |
| compiler.stopAfterClosedWorldForTesting = true; |
| }, |
| ); |
| Expect.isTrue(result2.isSuccess); |
| |
| Directory dir = await Directory.systemTemp.createTemp( |
| 'serialization_test_helper', |
| ); |
| final cfeDillFileUri = dir.uri.resolve('cfe.dill'); |
| |
| OutputCollector cfeDillCollector = OutputCollector(); |
| CompilationResult resultCfeDill = await runCompiler( |
| entryPoint: entryPoint, |
| memorySourceFiles: memorySourceFiles, |
| packageConfig: packageConfig, |
| librariesSpecificationUri: librariesSpecificationUri, |
| outputProvider: cfeDillCollector, |
| options: options + ['--out=$cfeDillFileUri', '${Flags.stage}=cfe'], |
| ); |
| Expect.isTrue(resultCfeDill.isSuccess); |
| Expect.isTrue(cfeDillCollector.binaryOutputMap.containsKey(cfeDillFileUri)); |
| |
| File( |
| cfeDillFileUri.path, |
| ).writeAsBytesSync(cfeDillCollector.binaryOutputMap[cfeDillFileUri]!.list); |
| |
| var closedWorldUri = Uri.parse('world.data'); |
| OutputCollector collector3a = OutputCollector(); |
| CompilationResult result3a = await runCompiler( |
| entryPoint: entryPoint, |
| memorySourceFiles: memorySourceFiles, |
| packageConfig: packageConfig, |
| librariesSpecificationUri: librariesSpecificationUri, |
| options: |
| options + |
| [ |
| '${Flags.inputDill}=$cfeDillFileUri', |
| '${Flags.closedWorldUri}=$closedWorldUri', |
| '${Flags.stage}=closed-world', |
| ], |
| outputProvider: collector3a, |
| beforeRun: (Compiler compiler) { |
| compiler.forceSerializationForTesting = true; |
| }, |
| ); |
| Expect.isTrue(result3a.isSuccess); |
| Expect.isTrue(collector3a.binaryOutputMap.containsKey(closedWorldUri)); |
| |
| final closedWorldFileUri = dir.uri.resolve('world.data'); |
| final globalDataUri = Uri.parse('global.data'); |
| final closedWorldBytes = collector3a.binaryOutputMap[closedWorldUri]!.list; |
| File(closedWorldFileUri.path).writeAsBytesSync(closedWorldBytes); |
| OutputCollector collector3b = OutputCollector(); |
| CompilationResult result3b = await runCompiler( |
| entryPoint: entryPoint, |
| memorySourceFiles: memorySourceFiles, |
| packageConfig: packageConfig, |
| librariesSpecificationUri: librariesSpecificationUri, |
| options: |
| commonOptions + |
| [ |
| '${Flags.inputDill}=$cfeDillFileUri', |
| '${Flags.closedWorldUri}=$closedWorldFileUri', |
| '${Flags.globalInferenceUri}=$globalDataUri', |
| '${Flags.stage}=global-inference', |
| ], |
| outputProvider: collector3b, |
| beforeRun: (Compiler compiler) { |
| compiler.forceSerializationForTesting = true; |
| compiler.stopAfterGlobalTypeInferenceForTesting = true; |
| }, |
| ); |
| Expect.isTrue(result3b.isSuccess); |
| Expect.isTrue(collector3b.binaryOutputMap.containsKey(globalDataUri)); |
| |
| final globalDataFileUri = dir.uri.resolve('global.data'); |
| |
| // We must write the global data bytes before calling |
| // `finishCompileAndCompare` below as that clears the collector. |
| |
| final globalDataBytes = collector3b.binaryOutputMap[globalDataUri]!.list; |
| File(globalDataFileUri.path).writeAsBytesSync(globalDataBytes); |
| |
| await finishCompileAndCompare( |
| expectedOutput, |
| collector2, |
| result2.compiler!, |
| strategy, |
| stoppedAfterClosedWorld: true, |
| ); |
| await finishCompileAndCompare( |
| expectedOutput, |
| collector3b, |
| result3b.compiler!, |
| strategy, |
| stoppedAfterTypeInference: true, |
| ); |
| |
| final jsOutUri = Uri.parse('out.js'); |
| OutputCollector collector4 = OutputCollector(); |
| CompilationResult result4 = await runCompiler( |
| entryPoint: entryPoint, |
| memorySourceFiles: memorySourceFiles, |
| packageConfig: packageConfig, |
| librariesSpecificationUri: librariesSpecificationUri, |
| options: |
| commonOptions + |
| [ |
| '${Flags.inputDill}=$cfeDillFileUri', |
| '${Flags.closedWorldUri}=$closedWorldFileUri', |
| '${Flags.globalInferenceUri}=$globalDataFileUri', |
| '${Flags.stage}=codegen-emit-js', |
| '--out=$jsOutUri', |
| ], |
| outputProvider: collector4, |
| beforeRun: (Compiler compiler) { |
| compiler.forceSerializationForTesting = true; |
| }, |
| ); |
| Expect.isTrue(result4.isSuccess); |
| |
| await dir.delete(recursive: true); |
| } |
| |
| void checkData(List<int> data, List<int> newData) { |
| Expect.equals( |
| data.length, |
| newData.length, |
| "Reserialization data length mismatch.", |
| ); |
| for (int i = 0; i < data.length; i++) { |
| if (data[i] != newData[i]) { |
| print('Reserialization data mismatch at offset $i:'); |
| for (int j = i - 50; j < i + 50; j++) { |
| if (0 <= j && j <= data.length) { |
| String text; |
| if (data[j] == newData[j]) { |
| text = '${data[j]}'; |
| } else { |
| text = '${data[j]} <> ${newData[j]}'; |
| } |
| print('${j == i ? '> ' : ' '}$j: $text'); |
| } |
| } |
| break; |
| } |
| } |
| Expect.listEquals(data, newData); |
| } |
| |
| JClosedWorld cloneClosedWorld( |
| Compiler compiler, |
| JClosedWorld closedWorld, |
| SerializationStrategy strategy, |
| ) { |
| SerializationIndices indices = SerializationIndices(); |
| ir.Component component = closedWorld.elementMap.programEnv.mainComponent; |
| Uint8List irData = strategy.serializeComponent(component); |
| final closedWorldData = |
| strategy.serializeClosedWorld(closedWorld, compiler.options, indices) |
| as List<int>; |
| print('data size: ${closedWorldData.length}'); |
| |
| ir.Component newComponent = strategy.deserializeComponent(irData); |
| var newClosedWorld = strategy.deserializeClosedWorld( |
| compiler.options, |
| compiler.reporter, |
| compiler.abstractValueStrategy, |
| newComponent, |
| closedWorldData, |
| indices, |
| ); |
| indices = SerializationIndices(); |
| final newClosedWorldData = |
| strategy.serializeClosedWorld(newClosedWorld, compiler.options, indices) |
| as List<int>; |
| checkData(closedWorldData, newClosedWorldData); |
| return newClosedWorld; |
| } |
| |
| /// Tests that cloned inference results serialize to the same data. |
| /// |
| /// Does 3 round trips to serialize/deserialize the provided data. The first |
| /// round normalizes the data as some information might be dropped in the |
| /// serialization/deserialization process. The second and third rounds are |
| /// compared for consistency. |
| GlobalTypeInferenceResults cloneInferenceResults( |
| Compiler compiler, |
| GlobalTypeInferenceResults results, |
| SerializationStrategy strategy, |
| ) { |
| SerializationIndices indices = SerializationIndices(testMode: true); |
| Uint8List irData = strategy.unpackAndSerializeComponent(results); |
| final closedWorldData = |
| strategy.serializeClosedWorld( |
| results.closedWorld, |
| compiler.options, |
| indices, |
| ) |
| as List<int>; |
| ir.Component newComponent = strategy.deserializeComponent(irData); |
| var newClosedWorld = strategy.deserializeClosedWorld( |
| compiler.options, |
| compiler.reporter, |
| compiler.abstractValueStrategy, |
| newComponent, |
| closedWorldData, |
| indices, |
| ); |
| indices = SerializationIndices(testMode: true); |
| final worldData = |
| strategy.serializeGlobalTypeInferenceResults( |
| results, |
| compiler.options, |
| indices, |
| ) |
| as List<int>; |
| print('data size: ${worldData.length}'); |
| GlobalTypeInferenceResults initialResults = strategy |
| .deserializeGlobalTypeInferenceResults( |
| compiler.options, |
| compiler.reporter, |
| compiler.environment, |
| compiler.abstractValueStrategy, |
| newComponent, |
| newClosedWorld, |
| worldData, |
| indices, |
| ); |
| indices = SerializationIndices(testMode: true); |
| final initialWorldData = |
| strategy.serializeGlobalTypeInferenceResults( |
| initialResults, |
| compiler.options, |
| indices, |
| ) |
| as List<int>; |
| GlobalTypeInferenceResults finalResults = strategy |
| .deserializeGlobalTypeInferenceResults( |
| compiler.options, |
| compiler.reporter, |
| compiler.environment, |
| compiler.abstractValueStrategy, |
| newComponent, |
| newClosedWorld, |
| worldData, |
| indices, |
| ); |
| indices = SerializationIndices(testMode: true); |
| final finalWorldData = |
| strategy.serializeGlobalTypeInferenceResults( |
| finalResults, |
| compiler.options, |
| indices, |
| ) |
| as List<int>; |
| checkData(initialWorldData, finalWorldData); |
| return finalResults; |
| } |