| // 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 '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/io/source_information.dart'; |
| import 'package:compiler/src/js_model/js_world.dart'; |
| import 'package:compiler/src/inferrer/types.dart'; |
| import 'package:compiler/src/serialization/serialization.dart'; |
| import 'package:compiler/src/serialization/strategies.dart'; |
| import 'package:expect/expect.dart'; |
| import 'package:kernel/ast.dart' as ir; |
| import 'package:compiler/src/util/memory_compiler.dart'; |
| 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, |
| codegenResults, |
| 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; |
| List<int> 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); |
| List<int> 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; |
| } |