diff --git a/pkg/analysis_server/test/verify_sorted_test.dart b/pkg/analysis_server/test/verify_sorted_test.dart index 106df5b6da1..08111263f6b 100644 --- a/pkg/analysis_server/test/verify_sorted_test.dart +++ b/pkg/analysis_server/test/verify_sorted_test.dart @@ -87,6 +87,7 @@ void buildTestsForAnalyzer() { excludedPaths: [ 'lib/src/context/packages.dart', 'lib/src/summary/format.dart', + 'lib/src/wolf/ir/ir.g.dart', 'test/generated/test_all.dart', ], ); diff --git a/pkg/analyzer/lib/src/wolf/ir/ir.dart b/pkg/analyzer/lib/src/wolf/ir/ir.dart new file mode 100644 index 00000000000..f8c2c00eb5c --- /dev/null +++ b/pkg/analyzer/lib/src/wolf/ir/ir.dart @@ -0,0 +1,198 @@ +// Copyright (c) 2023, 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. + +/// This library contains data structures that define an intermediate +/// representation for Dart expressions and statements. +/// +/// The intermediate representation models a stack-based machine in which stack +/// entries are Dart values. It aims to be fairly minimal, so that it can be +/// used as the basis for implementing lint rules and other static analyses, +/// without requiring the implementer to handle every possible Dart construct. +library; + +part 'ir.g.dart'; + +/// Container for a sequence of IR instructions, which represents the body of a +/// single function, method, getter, setter, constructor, or initializer +/// expression. +/// +/// The data is organized into lists of integers rather than a deeply nested +/// AST structure, in order to allow for random access and reduce the number of +/// cache lines that need to be loaded in order to perform analysis. +/// +/// Other data structures that need to be referenced by the IR (e.g. types) are +/// stored in auxiliary tables, which are provided by subclasses. +/// +/// To construct a sequence of IR instructions, see [RawIRWriter]. +abstract class BaseIRContainer with IRToStringMixin { + /// The opcode of each encoded instruction. + final List _opcodes; + @override + final List _params0; + @override + final List _params1; + + BaseIRContainer(RawIRWriter writer) + : _opcodes = writer._opcodes, + _params0 = writer._params0, + _params1 = writer._params1; + + int get endAddress => _opcodes.length; + + /// Given a [TypeRef] that represents a function type, returns the number of + /// function parameters. + int countParameters(TypeRef type); + + @override + String functionFlagsToString(FunctionFlags flags) => flags.describe(); + + @override + String literalRefToString(LiteralRef literal) => 'literal#${literal.index}'; + + @override + Opcode opcodeAt(int address) => _opcodes[address]; + + @override + String typeRefToString(TypeRef type) => 'typeRef#${type.index}'; +} + +/// Flags describing properties of a function declaration that affect the +/// interpretation of its body. +/// +/// TODO(paulberry): when extension types are supported, make this an extension +/// type. +class FunctionFlags { + static const _asyncBit = 0; + static const _generatorBit = 1; + static const _instanceBit = 2; + + final int _flags; + + const FunctionFlags( + {bool async = false, bool generator = false, bool instance = false}) + : _flags = (async ? (1 << _asyncBit) : 0) | + (generator ? (1 << _generatorBit) : 0) | + (instance ? (1 << _instanceBit) : 0); + + const FunctionFlags._(this._flags); + + @override + int get hashCode => _flags.hashCode; + + /// True if the function was declared with `async` or `async*`. + bool get isAsync => _flags & (1 << _asyncBit) != 0; + + /// True if the function was declared with `sync*` or `async*`. + bool get isGenerator => _flags & (1 << _generatorBit) != 0; + + /// True if the function contains an implicit `this` parameter. + bool get isInstance => _flags & (1 << _instanceBit) != 0; + + @override + bool operator ==(other) => other is FunctionFlags && _flags == other._flags; + + String describe() { + var parts = [ + if (isAsync) 'async', + if (isGenerator) 'generator', + if (isInstance) 'instance' + ]; + return parts.isEmpty ? '0' : parts.join('|'); + } + + @override + String toString() => _flags.toString(); +} + +/// Wrapper for an integer representing a simple Dart literal (Null, bool, int, +/// double, String, or Symbol). +/// +/// The actual value of the literal isn't stored directly in the instruction +/// stream. This integer is an index into an auxiliary table stored in a subtype +/// of [BaseIRContainer]. +/// +/// TODO(paulberry): when extension types are supported, make this an extension +/// type. +class LiteralRef { + final int index; + + LiteralRef(this.index); + + @override + int get hashCode => index.hashCode; + + @override + bool operator ==(other) => other is LiteralRef && index == other.index; + + @override + String toString() => index.toString(); +} + +/// Interface used by generated code to read and decode instructions from +/// [BaseIRContainer]. +abstract class RawIRContainerInterface { + /// The first parameter of each encoded instruction. + List get _params0; + + /// The second parameter of each encoded instruction. + List get _params1; + + String functionFlagsToString(FunctionFlags flags); + + String literalRefToString(LiteralRef literal); + + Opcode opcodeAt(int address); + + String typeRefToString(TypeRef type); +} + +/// Writer of an IR instruction stream. +/// +/// This class contains methods to add each kind of instruction to the stream +/// (in [_RawIRWriterMixin]). To create an instruction stream, create an +/// instance of this class, call methods in [_RawIRWriterMixin] to add the +/// instructions, and then pass this object to [BaseIRContainer]. +/// +/// Subclasses provide the ability to create reference to other data structures +/// (e.g. types), which are stored in auxiliary tables. +class RawIRWriter with _RawIRWriterMixin { + @override + final _opcodes = []; + @override + final _params0 = []; + @override + final _params1 = []; + + int get nextInstructionAddress => _opcodes.length; +} + +/// Wrapper for an integer representing a Dart type. +/// +/// The actual type isn't stored directly in the instruction stream. This +/// integer is an index into an auxiliary table stored in a subtype of +/// [BaseIRContainer]. +/// +/// TODO(paulberry): when extension types are supported, make this an extension +/// type. +class TypeRef { + final int index; + + TypeRef(this.index); + + @override + int get hashCode => index.hashCode; + + @override + bool operator ==(other) => other is TypeRef && index == other.index; + + @override + String toString() => index.toString(); +} + +/// Interface used by generated code to write instructions into [RawIRWriter]. +abstract class _RawIRWriterMixinInterface { + List get _opcodes; + List get _params0; + List get _params1; +} diff --git a/pkg/analyzer/lib/src/wolf/ir/ir.g.dart b/pkg/analyzer/lib/src/wolf/ir/ir.g.dart new file mode 100644 index 00000000000..b4789ae5f41 --- /dev/null +++ b/pkg/analyzer/lib/src/wolf/ir/ir.g.dart @@ -0,0 +1,105 @@ +// Copyright (c) 2023, 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. + +// THIS FILE IS GENERATED. DO NOT EDIT. +// +// Instead modify 'pkg/analyzer/tool/wolf/generate.dart' and run +// 'dart run pkg/analyzer/tool/wolf/generate.dart' to update. + +part of 'ir.dart'; + +mixin _RawIRWriterMixin implements _RawIRWriterMixinInterface { + void drop() { + _opcodes.add(Opcode.drop); + _params0.add(0); + _params1.add(0); + } + + void end() { + _opcodes.add(Opcode.end); + _params0.add(0); + _params1.add(0); + } + + void function(TypeRef type, FunctionFlags flags) { + _opcodes.add(Opcode.function); + _params0.add(type.index); + _params1.add(flags._flags); + } + + void literal(LiteralRef value) { + _opcodes.add(Opcode.literal); + _params0.add(value.index); + _params1.add(0); + } +} + +mixin IRToStringMixin implements RawIRContainerInterface { + String instructionToString(int address) { + switch (opcodeAt(address)) { + case Opcode.literal: + return 'literal(${literalRefToString(Opcode.literal.decodeValue(this, address))})'; + + case Opcode.drop: + return 'drop'; + + case Opcode.function: + return 'function(${typeRefToString(Opcode.function.decodeType(this, address))}, ${functionFlagsToString(Opcode.function.decodeFlags(this, address))})'; + + case Opcode.end: + return 'end'; + default: + return '???'; + } + } +} + +class _ParameterShape0 extends Opcode { + const _ParameterShape0._(super.index) : super._(); + + LiteralRef decodeValue(RawIRContainerInterface ir, int address) { + assert(ir.opcodeAt(address).index == index); + return LiteralRef(ir._params0[address]); + } +} + +class _ParameterShape1 extends Opcode { + const _ParameterShape1._(super.index) : super._(); +} + +class _ParameterShape2 extends Opcode { + const _ParameterShape2._(super.index) : super._(); + + TypeRef decodeType(RawIRContainerInterface ir, int address) { + assert(ir.opcodeAt(address).index == index); + return TypeRef(ir._params0[address]); + } + + FunctionFlags decodeFlags(RawIRContainerInterface ir, int address) { + assert(ir.opcodeAt(address).index == index); + return FunctionFlags._(ir._params1[address]); + } +} + +/// TODO(paulberry): when extension types are supported, make this an extension +/// type, as well as all the `_ParameterShape` classes. +class Opcode { + final int index; + + const Opcode._(this.index); + + static const literal = _ParameterShape0._(0); + static const drop = _ParameterShape1._(1); + static const function = _ParameterShape2._(2); + static const end = _ParameterShape1._(3); + + String describe() => opcodeNameTable[index]; + + static const opcodeNameTable = [ + "literal", + "drop", + "function", + "end", + ]; +} diff --git a/pkg/analyzer/lib/src/wolf/ir/validator.dart b/pkg/analyzer/lib/src/wolf/ir/validator.dart new file mode 100644 index 00000000000..e88a2bf407b --- /dev/null +++ b/pkg/analyzer/lib/src/wolf/ir/validator.dart @@ -0,0 +1,134 @@ +// Copyright (c) 2023, 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:analyzer/src/wolf/ir/ir.dart'; + +/// Checks that [ir] is well-formed. +/// +/// Throws [ValidationError] if it's not. +void validate(BaseIRContainer ir) { + _Validator(ir).run(); +} + +class ValidationError extends Error { + final int address; + final String instructionString; + final String message; + + ValidationError( + {required this.address, + required this.instructionString, + required this.message}); + + @override + String toString() => + 'Validation error at $address ($instructionString): $message'; +} + +/// Used by [_Validator] to track a control flow instruction (`block`, `loop`, +/// `tryCatch`, `tryFinally`, or `function` instruction) whose `matching `end` +/// instruction has not yet been encountered. +class _ControlFlowElement { + /// The state of [_Validator._functionFlags] before the control flow + /// instruction was encountered. + final FunctionFlags functionFlagsBefore; + + /// The number of entries that will be in the value stack after the matching + /// `end` instruction. + final int valueStackDepthAfter; + + /// The number of values that will be consumed from the value stack when the + /// control flow construct is ended (or branched out of). + final int branchValueCount; + + /// Whether the control flow instruction was a `function` instruction. + final bool isFunction; + + _ControlFlowElement( + {required this.functionFlagsBefore, + required this.valueStackDepthAfter, + required this.branchValueCount, + this.isFunction = false}); +} + +class _Validator { + final BaseIRContainer ir; + final _controlFlowStack = <_ControlFlowElement>[]; + var _address = 0; + + /// Flags from the most recent `function` instruction whose corresponding + /// `end` instruction has not yet been encountered. + var _functionFlags = FunctionFlags(); + + var _valueStackDepth = 0; + + _Validator(this.ir); + + void run() { + _check(ir.endAddress > 0, 'No instructions'); + for (_address = 0; _address < ir.endAddress; _address++) { + var opcode = ir.opcodeAt(_address); + _check(_address != 0 || opcode == Opcode.function, + 'First instruction must be function'); + switch (opcode) { + case Opcode.drop: + _popValues(1); + case Opcode.end: + _check(_controlFlowStack.isNotEmpty, 'unmatched end'); + var controlFlowElement = _controlFlowStack.removeLast(); + _popValues(controlFlowElement.branchValueCount); + _check(_valueStackDepth == 0, + '$_valueStackDepth superfluous value(s) remaining'); + _pushValues(controlFlowElement.valueStackDepthAfter); + _functionFlags = controlFlowElement.functionFlagsBefore; + case Opcode.function: + var type = Opcode.function.decodeType(ir, _address); + var kind = Opcode.function.decodeFlags(ir, _address); + _check(!kind.isInstance || _address == 0, + 'Instance function may only be used at instruction address 0'); + _controlFlowStack.add(_ControlFlowElement( + functionFlagsBefore: _functionFlags, + valueStackDepthAfter: _valueStackDepth + 1, + branchValueCount: 1, + isFunction: true)); + _functionFlags = kind; + _valueStackDepth = 0; + _pushValues(ir.countParameters(type) + (kind.isInstance ? 1 : 0)); + case Opcode.literal: + _pushValues(1); + default: + _fail('Unexpected opcode $opcode'); + } + } + _check(_controlFlowStack.isEmpty, 'Missing end'); + } + + /// Reports a validation error if [condition] is `false`. + void _check(bool condition, String message) { + if (!condition) { + _fail(message); + } + } + + /// Unconditionally reports a validation error. + Never _fail(String message) { + throw ValidationError( + address: _address, + instructionString: _address < ir.endAddress + ? ir.instructionToString(_address) + : 'after last instruction', + message: message); + } + + void _popValues(int count) { + assert(count >= 0); + _check(_valueStackDepth >= count, 'Value stack underflow'); + _valueStackDepth -= count; + } + + void _pushValues(int count) { + assert(count >= 0); + _valueStackDepth += count; + } +} diff --git a/pkg/analyzer/pubspec.yaml b/pkg/analyzer/pubspec.yaml index 4e534bf519d..501e7b5e9a5 100644 --- a/pkg/analyzer/pubspec.yaml +++ b/pkg/analyzer/pubspec.yaml @@ -29,6 +29,7 @@ dev_dependencies: analyzer_utilities: any args: any async: any + checks: any heap_snapshot: any linter: any lints: any diff --git a/pkg/analyzer/test/src/test_all.dart b/pkg/analyzer/test/src/test_all.dart index 35bf88eb1b2..0ebc0df2816 100644 --- a/pkg/analyzer/test/src/test_all.dart +++ b/pkg/analyzer/test/src/test_all.dart @@ -22,6 +22,7 @@ import 'task/test_all.dart' as task; import 'test_utilities/test_all.dart' as test_utilities; import 'util/test_all.dart' as util; import 'utilities/test_all.dart' as utilities; +import 'wolf/test_all.dart' as wolf; import 'workspace/test_all.dart' as workspace; main() { @@ -44,6 +45,7 @@ main() { test_utilities.main(); util.main(); utilities.main(); + wolf.main(); workspace.main(); }, name: 'src'); } diff --git a/pkg/analyzer/test/src/wolf/ir/test_all.dart b/pkg/analyzer/test/src/wolf/ir/test_all.dart new file mode 100644 index 00000000000..c6ba7904589 --- /dev/null +++ b/pkg/analyzer/test/src/wolf/ir/test_all.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2023, 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:test_reflective_loader/test_reflective_loader.dart'; + +import 'validator_test.dart' as validator; + +main() { + defineReflectiveSuite(() { + validator.main(); + }, name: 'ir'); +} diff --git a/pkg/analyzer/test/src/wolf/ir/utils.dart b/pkg/analyzer/test/src/wolf/ir/utils.dart new file mode 100644 index 00000000000..b0cb2c691e6 --- /dev/null +++ b/pkg/analyzer/test/src/wolf/ir/utils.dart @@ -0,0 +1,62 @@ +// Copyright (c) 2023, 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:analyzer/src/wolf/ir/ir.dart'; + +/// Minimal representation of a function type in unit tests that use +/// [TestIRContainer]. +class TestFunctionType { + final int parameterCount; + + TestFunctionType(this.parameterCount); +} + +/// Container for a sequence of IR instructions that aren't connected to an +/// analyzer AST data structure. +/// +/// Suitable for use in unit tests that test the IR instructions directly rather +/// than generate them from a Dart AST. +/// +/// To construct a sequence of IR instructions, see [TestIRWriter]. +class TestIRContainer extends BaseIRContainer { + final List _functionTypes; + + TestIRContainer(TestIRWriter super.writer) + : _functionTypes = writer._functionTypes; + + @override + int countParameters(TypeRef type) => + _functionTypes[type.index].parameterCount; +} + +/// Writer of an IR instruction stream that's not connected to an analyzer AST +/// data structure. +/// +/// Suitable for use in unit tests that test the IR instructions directly rather +/// than generate them from a Dart AST. +class TestIRWriter extends RawIRWriter { + final _functionTypes = []; + final _literalTable = []; + final _literalToRef = {}; + final _parameterCountToFunctionTypeMap = {}; + + TypeRef encodeFunctionType({required int parameterCount}) => + _parameterCountToFunctionTypeMap.putIfAbsent(parameterCount, () { + var encoding = TypeRef(_functionTypes.length); + _functionTypes.add(TestFunctionType(parameterCount)); + return encoding; + }); + + LiteralRef encodeLiteral(Object? value) => + _literalToRef.putIfAbsent(value, () { + var encoding = LiteralRef(_literalTable.length); + _literalTable.add(value); + return encoding; + }); + + /// Convenience method for creating an ordinary function (not a method, not + /// async, not a generator). + void ordinaryFunction({int parameterCount = 0}) => function( + encodeFunctionType(parameterCount: parameterCount), FunctionFlags()); +} diff --git a/pkg/analyzer/test/src/wolf/ir/validator_test.dart b/pkg/analyzer/test/src/wolf/ir/validator_test.dart new file mode 100644 index 00000000000..9b452284a07 --- /dev/null +++ b/pkg/analyzer/test/src/wolf/ir/validator_test.dart @@ -0,0 +1,145 @@ +// Copyright (c) 2023, 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:analyzer/src/wolf/ir/ir.dart'; +import 'package:analyzer/src/wolf/ir/validator.dart'; +import 'package:checks/checks.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'utils.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(ValidatorTest); + }); +} + +@reflectiveTest +class ValidatorTest { + late TestIRContainer ir; + + test_drop() { + _analyze((ir) => ir + ..ordinaryFunction(parameterCount: 1) + ..drop() // Pop parameter + ..drop() // UNDERFLOW + ..end()); + check(_validate).throws() + ..address.equals(2) + ..message.equals('Value stack underflow'); + } + + test_firstInstruction_function() { + _analyze((ir) => ir + ..ordinaryFunction() + ..literal(ir.encodeLiteral(null)) + ..end()); + check(_validate).returnsNormally(); + } + + test_firstInstruction_instanceFunction() { + _analyze((ir) => ir + ..function(ir.encodeFunctionType(parameterCount: 0), + FunctionFlags(instance: true)) + ..end()); + check(_validate).returnsNormally(); + } + + test_firstInstruction_notFunction() { + _analyze((ir) => ir..literal(ir.encodeLiteral(null))); + check(_validate).throws() + ..address.equals(0) + ..message.equals('First instruction must be function'); + } + + test_function_nested_instanceFunction() { + _analyze((ir) => ir + ..ordinaryFunction() + ..function(ir.encodeFunctionType(parameterCount: 0), + FunctionFlags(instance: true)) + ..end() + ..end()); + check(_validate).throws() + ..address.equals(1) + ..message.equals( + 'Instance function may only be used at instruction address 0'); + } + + test_function_nested_notInstanceFunction() { + _analyze((ir) => ir + ..ordinaryFunction() + ..ordinaryFunction() + ..literal(ir.encodeLiteral(null)) + ..end() + ..end()); + check(_validate).returnsNormally(); + } + + test_function_parameterCount_instanceFunction() { + _analyze((ir) => ir + ..function(ir.encodeFunctionType(parameterCount: 2), + FunctionFlags(instance: true)) + ..drop() // Pop second parameter + ..drop() // Pop first parameter + ..drop() // Pop `this` + ..drop() // UNDERFLOW + ..end()); + check(_validate).throws() + ..address.equals(4) + ..message.equals('Value stack underflow'); + } + + test_function_parameterCount_notInstanceFunction() { + _analyze((ir) => ir + ..ordinaryFunction(parameterCount: 2) + ..drop() // Pop second parameter + ..drop() // Pop first parameter + ..drop() // UNDERFLOW + ..end()); + check(_validate).throws() + ..address.equals(3) + ..message.equals('Value stack underflow'); + } + + test_literal() { + _analyze((ir) => ir + ..ordinaryFunction() + ..literal(ir.encodeLiteral(null)) // Push `null` + ..drop() // Pop `null` + ..drop() // UNDERFLOW + ..end()); + check(_validate).throws() + ..address.equals(3) + ..message.equals('Value stack underflow'); + } + + test_missingEnd() { + _analyze((ir) => ir + ..ordinaryFunction() + ..literal(ir.encodeLiteral(null))); + check(_validate).throws() + ..address.equals(2) + ..message.equals('Missing end'); + } + + test_noInstructions() { + _analyze((ir) => ir); + check(_validate).throws() + ..address.equals(0) + ..message.equals('No instructions'); + } + + void _analyze(void Function(TestIRWriter) writeIR) { + var writer = TestIRWriter(); + writeIR(writer); + ir = TestIRContainer(writer); + } + + void _validate() => validate(ir); +} + +extension on Subject { + Subject get address => has((e) => e.address, 'address'); + Subject get message => has((e) => e.message, 'message'); +} diff --git a/pkg/analyzer/test/src/wolf/test_all.dart b/pkg/analyzer/test/src/wolf/test_all.dart new file mode 100644 index 00000000000..f0c78e11b8d --- /dev/null +++ b/pkg/analyzer/test/src/wolf/test_all.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2023, 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:test_reflective_loader/test_reflective_loader.dart'; + +import 'ir/test_all.dart' as ir; + +main() { + defineReflectiveSuite(() { + ir.main(); + }, name: 'wolf'); +} diff --git a/pkg/analyzer/tool/wolf/generate.dart b/pkg/analyzer/tool/wolf/generate.dart new file mode 100644 index 00000000000..23a63a8f8a7 --- /dev/null +++ b/pkg/analyzer/tool/wolf/generate.dart @@ -0,0 +1,319 @@ +// Copyright (c) 2023, 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:convert'; + +import 'package:analyzer_utilities/package_root.dart' as pkg_root; +import 'package:analyzer_utilities/tools.dart'; +import 'package:collection/collection.dart'; +import 'package:path/path.dart'; + +Future main() async { + await GeneratedContent.generateAll(analyzerPkgPath, allTargets); +} + +final allTargets = [ + GeneratedFile( + 'lib/src/wolf/ir/ir.g.dart', (pkgPath) async => _IrGenerator().run()) +]; + +final analyzerPkgPath = normalize(join(pkg_root.packageRoot, 'analyzer')); + +final _instructions = _Instructions(); + +sealed class _Encoding { + final String type; + + const _Encoding(this.type); + + @override + int get hashCode => type.hashCode; + + @override + operator ==(other) => other is _Encoding && type == other.type; + + _Parameter call(String name) => _Parameter._(name, this); + + String decode(String value); + + String encode(String value); + + String stringInterpolation(String value); +} + +class _Instruction { + final String name; + final List<_Parameter> parameters; + final int parameterShapeId; + + _Instruction(this.name, this.parameters, this.parameterShapeId); + + String get className => '_${name.capitalized}Instruction'; + + String get signature { + var parametersString = + [for (var p in parameters) '${p.encoding.type} ${p.name}'].join(', '); + return '$name($parametersString)'; + } +} + +class _Instructions { + late final sorted = all.toList()..sort((a, b) => a.name.compareTo(b.name)); + + final all = <_Instruction>[]; + + final encodings = <_NontrivialEncoding>[]; + + final parameterShapeMap = <_ParameterShape, int>{}; + + _Instructions() { + // Encodings + var functionFlags = + encoding('FunctionFlags', fieldName: '_flags', constructorName: '_'); + var literal = encoding('LiteralRef'); + var type = encoding('TypeRef'); + + // Primitive operations + _addInstruction('literal', [literal('value')]); + // Stack manipulation + _addInstruction('drop', []); + // Flow control + _addInstruction('function', [type('type'), functionFlags('flags')]); + _addInstruction('end', []); + } + + _NontrivialEncoding encoding(String type, + {String fieldName = 'index', String constructorName = ''}) { + var encoding = _NontrivialEncoding(type, + fieldName: fieldName, constructorName: constructorName); + encodings.add(encoding); + return encoding; + } + + void _addInstruction(String name, List<_Parameter> parameters) { + var parameterShapeId = parameterShapeMap.putIfAbsent( + _ParameterShape(parameters), () => parameterShapeMap.length); + all.add(_Instruction(name, parameters, parameterShapeId)); + } +} + +class _IrGenerator { + final _substringsToOutput = []; + + void blankLine() { + output('\n'); + } + + void output(String s) { + _substringsToOutput.add(s); + } + + void outputIRToStringMixin() { + output(''' +mixin IRToStringMixin implements RawIRContainerInterface { + String instructionToString(int address) { + switch (opcodeAt(address)) { +'''); + _instructions.all.forEachSeparated(blankLine, (instruction) { + var opcode = 'Opcode.${instruction.name}'; + var interpolation = instruction.name.demangled; + if (instruction.parameters.isNotEmpty) { + var interpolationParts = []; + for (var p in instruction.parameters) { + interpolationParts.add(p.encoding.stringInterpolation( + '$opcode.decode${p.name.capitalized}(this, address)')); + } + interpolation += '(${interpolationParts.join(', ')})'; + } + output(''' + case $opcode: + return '$interpolation'; +'''); + }); + output(''' + default: + return '???'; + } + } +} + +'''); + } + + void outputOpcode() { + output(''' +/// TODO(paulberry): when extension types are supported, make this an extension +/// type, as well as all the `_ParameterShape` classes. +class Opcode { + final int index; + + const Opcode._(this.index); + +'''); + _instructions.all.forEachIndexed((i, instruction) { + var shapeId = instruction.parameterShapeId; + output(' static const ${instruction.name} = ' + '_ParameterShape$shapeId._(${i++});\n'); + }); + output(''' + + String describe() => opcodeNameTable[index]; + + static const opcodeNameTable = [ +'''); + _instructions.all.forEachIndexed((i, instruction) { + output(' ${json.encode(instruction.name.demangled)},'); + }); + output(''' + ]; +} + +'''); + } + + void outputParameterShapes() { + _instructions.parameterShapeMap.forEach((parameterShape, id) { + output(''' +class _ParameterShape$id extends Opcode { + const _ParameterShape$id._(super.index) : super._(); +'''); + var i = 0; + for (var parameter in parameterShape._parameters) { + var returnType = parameter.encoding.type; + var name = 'decode${parameter.name.capitalized}'; + var value = parameter.encoding.decode('ir._params${i++}[address]'); + output(''' + + $returnType $name(RawIRContainerInterface ir, int address) { + assert(ir.opcodeAt(address).index == index); + return $value; + } +'''); + } + output(''' +} + +'''); + }); + } + + void outputRawIRWriterMixin() { + output(''' +mixin _RawIRWriterMixin implements _RawIRWriterMixinInterface { +'''); + _instructions.sorted.forEachSeparated(blankLine, (instruction) { + output(''' + void ${instruction.signature} { + _opcodes.add(Opcode.${instruction.name}); +'''); + var i = 0; + for (var p in instruction.parameters) { + output(' _params${i++}.add(${p.encoding.encode(p.name)});\n'); + } + while (i < 2) { + output(' _params${i++}.add(0);\n'); + } + output(''' + } +'''); + }); + output('}\n\n'); + } + + String run() { + output(r''' +// Copyright (c) 2023, 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. + +// THIS FILE IS GENERATED. DO NOT EDIT. +// +// Instead modify 'pkg/analyzer/tool/wolf/generate.dart' and run +// 'dart run pkg/analyzer/tool/wolf/generate.dart' to update. + +part of 'ir.dart'; + +'''); + outputRawIRWriterMixin(); + outputIRToStringMixin(); + outputParameterShapes(); + outputOpcode(); + return _substringsToOutput.join(''); + } +} + +class _NontrivialEncoding extends _Encoding { + final String fieldName; + final String constructorName; + + _NontrivialEncoding(super.type, + {required this.fieldName, required this.constructorName}); + + @override + String decode(String value) => + '$type${constructorName.isEmpty ? '' : '.$constructorName'}($value)'; + + @override + String encode(String value) => '$value.$fieldName'; + + @override + String stringInterpolation(String value) => + '\${${type.uncapitalized}ToString($value)}'; +} + +class _Parameter { + final String name; + final _Encoding encoding; + + _Parameter._(this.name, this.encoding); + + String get fieldName => '_$name'; + + @override + int get hashCode => Object.hash(name, encoding); + + @override + bool operator ==(other) => + other is _Parameter && name == other.name && encoding == other.encoding; +} + +class _ParameterShape { + final List<_Parameter> _parameters; + + _ParameterShape(this._parameters); + + @override + int get hashCode => Object.hashAll(_parameters); + + @override + bool operator ==(other) { + if (other is! _ParameterShape || + _parameters.length != other._parameters.length) { + return false; + } + for (var i = 0; i < _parameters.length; i++) { + if (_parameters[i] != other._parameters[i]) return false; + } + return true; + } +} + +extension on List { + forEachSeparated(void Function() separator, void Function(T) callback) { + void Function()? nextSeparator; + for (var item in this) { + nextSeparator?.call(); + callback(item); + nextSeparator = separator; + } + } +} + +extension on String { + String get capitalized => '${this[0].toUpperCase()}${substring(1)}'; + + String get demangled => endsWith('_') ? substring(0, length - 1) : this; + + String get uncapitalized => '${this[0].toLowerCase()}${substring(1)}'; +} diff --git a/pkg/analyzer/tool/wolf/generate_test.dart b/pkg/analyzer/tool/wolf/generate_test.dart new file mode 100644 index 00000000000..3d3a3b23b97 --- /dev/null +++ b/pkg/analyzer/tool/wolf/generate_test.dart @@ -0,0 +1,16 @@ +// Copyright (c) 2023, 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. + +// This test verifies that all files generated by `generate.dart` are up to +// date. + +import 'package:analyzer_utilities/tools.dart'; +import 'package:path/path.dart'; + +import 'generate.dart'; + +Future main() async { + await GeneratedContent.checkAll(analyzerPkgPath, + join(analyzerPkgPath, 'tool', 'wolf', 'generate.dart'), allTargets); +}