Begin developing code for "Wolf analysis".

Wolf analysis is an experimental part of the analyzer that I intend to
use as the basis for new, more advanced, lint rules and other
analyses. Its first phase will consist of lowering the abstract syntax
of each function or method to a stream of instructions for a minimal
stack machine.

This code implements the first few instructions for the minimal stack
machine (`function`, `end`, `literal`, and `drop`). These are
sufficient to express a simple function like:

    int f() => 0;

Which maps to the following instruction stream:

    function(int Function(), 0)
    literal(0)
    end

This code includes a validator that verifies that an instruction
stream is well-formed. In follow-up CLs I will add logic to convert an
analyzer AST to an instruction stream.

The internal representation of an instruction stream is code
genreated, because:

- I intend to add additional instructions in follow-up CLs, and I
  don't want to have to remember all the boilerplate for adding an
  instruction each time.

- Once the implementation is far enough along to start consuming large
  amounts of real-world Dart code, I would like to be able to
  investigate the performance effects of changing the internal
  representation. Code generation will make this a lot easier.

Change-Id: I4bc299d31ed108f6eebf9cca913d1484dbc9a3cf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/334644
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
This commit is contained in:
Paul Berry
2023-11-08 17:50:43 +00:00
committed by Commit Queue
parent 7109c5de84
commit ba42706e66
12 changed files with 1009 additions and 0 deletions
@@ -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',
],
);
+198
View File
@@ -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<Opcode> _opcodes;
@override
final List<int> _params0;
@override
final List<int> _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<int> get _params0;
/// The second parameter of each encoded instruction.
List<int> 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 = <Opcode>[];
@override
final _params0 = <int>[];
@override
final _params1 = <int>[];
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<Opcode> get _opcodes;
List<int> get _params0;
List<int> get _params1;
}
+105
View File
@@ -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",
];
}
+134
View File
@@ -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;
}
}
+1
View File
@@ -29,6 +29,7 @@ dev_dependencies:
analyzer_utilities: any
args: any
async: any
checks: any
heap_snapshot: any
linter: any
lints: any
+2
View File
@@ -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');
}
@@ -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');
}
+62
View File
@@ -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<TestFunctionType> _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 = <TestFunctionType>[];
final _literalTable = <Object?>[];
final _literalToRef = <Object?, LiteralRef>{};
final _parameterCountToFunctionTypeMap = <int, TypeRef>{};
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());
}
@@ -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<ValidationError>()
..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<ValidationError>()
..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<ValidationError>()
..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<ValidationError>()
..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<ValidationError>()
..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<ValidationError>()
..address.equals(3)
..message.equals('Value stack underflow');
}
test_missingEnd() {
_analyze((ir) => ir
..ordinaryFunction()
..literal(ir.encodeLiteral(null)));
check(_validate).throws<ValidationError>()
..address.equals(2)
..message.equals('Missing end');
}
test_noInstructions() {
_analyze((ir) => ir);
check(_validate).throws<ValidationError>()
..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<ValidationError> {
Subject<int> get address => has((e) => e.address, 'address');
Subject<String> get message => has((e) => e.message, 'message');
}
+13
View File
@@ -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');
}
+319
View File
@@ -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<void> 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 = <String>[];
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 = <String>[];
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<T> on List<T> {
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)}';
}
+16
View File
@@ -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<void> main() async {
await GeneratedContent.checkAll(analyzerPkgPath,
join(analyzerPkgPath, 'tool', 'wolf', 'generate.dart'), allTargets);
}