4d06516832
This is a simple string-based intermediate representation for Dart code, similar to kernel but with a far simpler structure and a compact string encoding, suitable for use in unit tests. I've integrated this representation into mini_ast.dart in a preliminary way, so it is created as a by-product of running flow analysis tests. But as yet there are no tests that validate the correctness of the result. I plan to refine the representation, and test it fully, in follow-up CLs. Change-Id: Idda527efd005d0199ff88861c9e074eb651d82f0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193881 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
248 lines
8.9 KiB
Dart
248 lines
8.9 KiB
Dart
// Copyright (c) 2021, 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 implements a miniature string-based internal representation ("IR")
|
|
// of Dart code suitable for use in unit testing.
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
import 'mini_ast.dart';
|
|
|
|
/// Stack-based builder class allowing construction of a miniature string-based
|
|
/// internal representation ("IR") of Dart code suitable for use in unit
|
|
/// testing.
|
|
class MiniIrBuilder {
|
|
/// Set this to `true` to enable print-based tracing of stack operations.
|
|
static const bool _debug = false;
|
|
|
|
/// If [_debug] is enabled, number of outstanding calls to [guard]. This
|
|
/// controls indentation of debug output.
|
|
int _guardDepth = 0;
|
|
|
|
/// Number of labels allocated so far.
|
|
int _labelCounter = 0;
|
|
|
|
/// Size threshold for [_stack]. [_pop] and [_popList] will cause a test
|
|
/// failure if an attempt is made to reduce the stack size to less than this
|
|
/// amount. See [guard].
|
|
int _popLimit = 0;
|
|
|
|
/// Stack of partially built IR nodes.
|
|
final _stack = <String>[];
|
|
|
|
/// Number of temporaries allocated so far.
|
|
int _tmpCounter = 0;
|
|
|
|
/// Creates a fresh [MiniIrLabel] representing a label that can be used as a
|
|
/// break target.
|
|
///
|
|
/// See [labeled].
|
|
MiniIrLabel allocateLabel() => MiniIrLabel._();
|
|
|
|
/// Pops the top node from the stack (which should represent an expression)
|
|
/// and creates a fresh [MiniIrTmp] representing a temporary variable whose
|
|
/// initializer is that expression.
|
|
///
|
|
/// See [let].
|
|
MiniIrTmp allocateTmp() {
|
|
return MiniIrTmp._('t${_tmpCounter++}', _pop());
|
|
}
|
|
|
|
/// Pops the top [numArgs] nodes from the stack and pushes a node that
|
|
/// combines them using [name]. For example, if the stack contains `1, 2, 3`,
|
|
/// calling `apply('f', 2)` results in a stack of `1, f(2, 3)`.
|
|
void apply(String name, int numArgs) =>
|
|
_push('$name(${_popList(numArgs).join(', ')})');
|
|
|
|
/// Pushes a node on the stack representing a single atomic expression (for
|
|
/// example a literal value or a variable reference).
|
|
void atom(String name) => _push(name);
|
|
|
|
/// Verifies that the top node on the stack matches [expectedIr] exactly.
|
|
void check(String expectedIr) {
|
|
expect(_stack.last.toString(), expectedIr);
|
|
}
|
|
|
|
/// Pushes a node representing a `for-in` loop onto the stack, using a loop
|
|
/// variable, iterable expression, and loop body obtained from the stack.
|
|
///
|
|
/// If [tmp] is non-null, it is used as the loop variable instead of obtaining
|
|
/// it from the stack.
|
|
void forIn(MiniIrTmp? tmp, {required bool isAsynchronous}) {
|
|
var name = isAsynchronous ? 'forIn_async' : 'forIn';
|
|
var body = _pop();
|
|
var iterable = _pop();
|
|
var variable = tmp == null ? _pop() : tmp._name;
|
|
_push('$name($variable, $iterable, $body)');
|
|
}
|
|
|
|
/// Executes [callback], checking that it leaves all nodes presently on the
|
|
/// stack untouched, and results in exactly one node being added to the stack.
|
|
T guard<T>(Node node, T Function() callback) {
|
|
if (_debug) {
|
|
print(' ' * _guardDepth++ + '$node');
|
|
}
|
|
int previousStackDepth = _stack.length;
|
|
int previousPopLimit = _popLimit;
|
|
_popLimit = previousStackDepth;
|
|
var result = callback();
|
|
var stackDelta = _stack.length - previousStackDepth;
|
|
if (stackDelta != 1) {
|
|
fail('Stack delta of $stackDelta while visiting '
|
|
'${node.runtimeType} $node\n'
|
|
'Stack: $this');
|
|
}
|
|
if (_debug) {
|
|
print(' ' * --_guardDepth + '=> ${_stack.last}');
|
|
}
|
|
_popLimit = previousPopLimit;
|
|
return result;
|
|
}
|
|
|
|
/// Pushes a node representing an "if not null" check onto the stack, using
|
|
/// [tmp] and an expression obtained from the stack. The intended semantics
|
|
/// are `tmp == null ? null : <expression>`.
|
|
///
|
|
/// This is intended to be used as a building block for null shorting
|
|
/// operations.
|
|
void ifNotNull(MiniIrTmp tmp) {
|
|
_push('if(==(${tmp._name}, null), null, ${_pop()})');
|
|
let(tmp);
|
|
}
|
|
|
|
/// Pushes a node representing an "if null" check onto the stack, using [tmp]
|
|
/// and two expressions obtained from the stack. The intended semantics
|
|
/// are `tmp == null ? <expression 1> : <expression 2>`.
|
|
///
|
|
/// This is intended to be used as a building block for null `??` and `??=`
|
|
/// operations.
|
|
void ifNull(MiniIrTmp tmp) {
|
|
var ifNull = _pop();
|
|
var ifNotNull = _pop();
|
|
_push('if(==(${tmp._name}, null), $ifNull, $ifNotNull)');
|
|
let(tmp);
|
|
}
|
|
|
|
/// Pushes a node representing a call to `operator[]` onto the stack, using
|
|
/// a receiver and an index obtained from the stack.
|
|
void indexGet() => apply('[]', 2);
|
|
|
|
/// Pushes a node representing a call to `operator[]=` onto the stack, using
|
|
/// a receiver, index, and value obtained from the stack.
|
|
///
|
|
/// If [receiverTmp] and/or [indexTmp] is non-null, they are used instead of
|
|
/// obtaining values from the stack.
|
|
void indexSet(MiniIrTmp? receiverTmp, MiniIrTmp? indexTmp) {
|
|
var value = _pop();
|
|
var index = indexTmp == null ? _pop() : indexTmp._name;
|
|
var receiver = receiverTmp == null ? _pop() : receiverTmp._name;
|
|
_push('[]=($receiver, $index, $value)');
|
|
}
|
|
|
|
/// Pushes a node representing a labeled statement onto the stack, using an
|
|
/// inner statement obtained from the stack.
|
|
///
|
|
/// To build up a statement of the form `labeled(L0, stmt)` (where `stmt`
|
|
/// might refer to `L0`), do the following operations:
|
|
/// - Call [allocateLabel] to prepare the label.
|
|
/// - build `stmt` on the stack, using [referToLabel] to refer to label as
|
|
/// needed.
|
|
/// - Call [labeled] to build the final `labeled` statement.
|
|
void labeled(MiniIrLabel label) {
|
|
var name = label._name;
|
|
if (name != null) {
|
|
_push('labeled($name, ${_pop()})');
|
|
}
|
|
}
|
|
|
|
/// Pushes a node representing a `let` expression onto the stack, using a
|
|
/// value obtained from the stack.
|
|
///
|
|
/// To build up an expression of the form `let(#0, value, expr)` (meaning
|
|
/// "let temporary variable #0 take on value while evaluating expr"), do the
|
|
/// following operations:
|
|
/// - Build `value` on the stack.
|
|
/// - Call [allocateTmp] to pop `value` off the stack and obtain a
|
|
/// [MiniIrTmp] object. This will assign the temporary variable a name that
|
|
/// doesn't conflict with any other outstanding temporary variables.
|
|
/// - Build `expr` on the stack, using [readTmp] to refer to the temporary
|
|
/// variable as needed.
|
|
/// - Call [let] to build the final `let` expression.
|
|
void let(MiniIrTmp tmp) {
|
|
_push('let(${tmp._name}, ${tmp._value}, ${_pop()})');
|
|
}
|
|
|
|
/// Pushes a node representing a property get onto the stack, using a receiver
|
|
/// obtained from the stack.
|
|
void propertyGet(String propertyName) => apply('get_$propertyName', 1);
|
|
|
|
/// Pushes a node representing a property set onto the stack, using a receiver
|
|
/// and value obtained from the stack.
|
|
///
|
|
/// If [receiverTmp] is non-null, it is used as the receiver rather than
|
|
/// obtaining it from the stack.
|
|
void propertySet(MiniIrTmp? receiverTmp, String propertyName) {
|
|
var value = _pop();
|
|
var receiver = receiverTmp == null ? _pop() : receiverTmp._name;
|
|
_push('set_$propertyName($receiver, $value)');
|
|
}
|
|
|
|
/// Pushes a node representing a read of [tmp] onto the stack.
|
|
void readTmp(MiniIrTmp tmp) => _push(tmp._name);
|
|
|
|
/// Pushes a node representing a reference to [label] onto the stack.
|
|
void referToLabel(MiniIrLabel label) {
|
|
_push(label._name ??= 'L${_labelCounter++}');
|
|
}
|
|
|
|
@override
|
|
String toString() => _stack.join(', ');
|
|
|
|
/// Pushes a node representing a read of a local variable onto the stack.
|
|
void variableGet(Var v) => atom(v.name);
|
|
|
|
/// Pushes a node representing a set of a local variable onto the stack, using
|
|
/// a value obtained from the stack.
|
|
void variableSet(Var v) => apply('${v.name}=', 1);
|
|
|
|
/// Pops a single node off the stack.
|
|
String _pop() {
|
|
expect(_stack.length, greaterThan(_popLimit));
|
|
return _stack.removeLast();
|
|
}
|
|
|
|
/// Pops a list of nodes off the stack.
|
|
List<String> _popList(int count) {
|
|
var newLength = _stack.length - count;
|
|
expect(newLength, greaterThanOrEqualTo(_popLimit));
|
|
var result = _stack.sublist(newLength);
|
|
_stack.length = newLength;
|
|
return result;
|
|
}
|
|
|
|
/// Pushes a node onto the stack.
|
|
void _push(String node) => _stack.add(node);
|
|
}
|
|
|
|
/// Representation of a branch target label used by [MiniIrBuilder] when
|
|
/// building up `labeled` statements.
|
|
class MiniIrLabel {
|
|
/// The name of the label, or `null` if no name has been assigned yet.
|
|
String? _name;
|
|
|
|
MiniIrLabel._();
|
|
}
|
|
|
|
/// Representation of a temporary variable used by [MiniIrBuilder] when building
|
|
/// up `let` expressions.
|
|
class MiniIrTmp {
|
|
/// The name of the temporary variable.
|
|
final String _name;
|
|
|
|
/// The initial value of the temporary variable.
|
|
final String _value;
|
|
|
|
MiniIrTmp._(this._name, this._value);
|
|
}
|