verify method
void
verify()
Checks every instruction: its lanes in range, its widths, and its units against its operands'.
Throws ExpressionBytecodeException naming the first that fails.
Implementation
void verify() {
// The dimension written to each lane, and the constant a lane holds
// when a `loadConstant` wrote it (for `pow`'s exponent).
final laneDim = List<int?>.filled(lanes, null);
final laneConstant = List<double?>.filled(lanes, null);
Never refuse(int index, String why) =>
throw ExpressionBytecodeException('instruction $index: $why');
for (final input in inputs) {
if (input.lane + input.width > lanes ||
input._dim >= _dimensions.length) {
throw ExpressionBytecodeException(
'input "${input.name}" is out of range',
);
}
for (var i = 0; i < input.width; i++) {
laneDim[input.lane + i] = input._dim;
}
}
for (var pc = 0, n = 0; pc < code.length; pc += 8, n++) {
final op = code[pc];
final width = code[pc + 1] & 0xFF;
final flags = code[pc + 1] >> 8;
final dst = code[pc + 2];
final a = code[pc + 3];
final b = code[pc + 4];
final c = code[pc + 5];
final d = code[pc + 6];
if (width < 1 || width > 4) refuse(n, 'width $width');
if (d >= _dimensions.length) refuse(n, 'no dimension $d');
final out = _dimensions[d];
final writes = switch (op) {
ExpressionOp.dot || ExpressionOp.length || ExpressionOp.not => 1,
_ => width,
};
if (dst + writes > lanes) refuse(n, 'writes past the lanes');
_Dim read(int lane, int count, String which) {
if (lane + count > lanes) refuse(n, '$which is past the lanes');
final dims = <int?>{for (var i = 0; i < count; i++) laneDim[lane + i]};
if (dims.length != 1 || dims.single == null) {
refuse(n, '$which reads lanes nothing wrote with one dimension');
}
return _dimensions[dims.single!];
}
int span(int flag) => flags & flag != 0 ? 1 : width;
void expect(bool ok, String why) {
if (!ok) refuse(n, why);
}
switch (op) {
case ExpressionOp.loadConstant:
if (a + width > constants.length) refuse(n, 'no constant $a');
case ExpressionOp.move || ExpressionOp.negate:
expect(read(a, width, 'a') == out, 'changes the unit');
case ExpressionOp.swizzle:
final source = <int>{
for (var i = 0; i < width; i++) (b >> 2 * i) & 3,
};
final top = source.reduce(math.max);
expect(read(a, top + 1, 'a') == out, 'changes the unit');
case ExpressionOp.not:
expect(read(a, 1, 'a').boolean && out.boolean, 'is not of bools');
case ExpressionOp.add || ExpressionOp.subtract:
final x = read(a, span(1), 'a');
final y = read(b, span(2), 'b');
expect(x == y && x == out && !x.boolean, 'adds two units');
case ExpressionOp.multiply || ExpressionOp.divide:
final x = read(a, span(1), 'a');
final y = read(b, span(2), 'b');
expect(!x.boolean && !y.boolean, 'multiplies a bool');
expect(
out == x.times(y, op == ExpressionOp.multiply ? 1 : -1),
'writes the wrong unit',
);
case ExpressionOp.less ||
ExpressionOp.lessEqual ||
ExpressionOp.greater ||
ExpressionOp.greaterEqual ||
ExpressionOp.equal ||
ExpressionOp.notEqual:
final x = read(a, 1, 'a');
final y = read(b, 1, 'b');
expect(x == y && out.boolean && width == 1, 'compares two units');
case ExpressionOp.and || ExpressionOp.or:
expect(
read(a, 1, 'a').boolean &&
read(b, 1, 'b').boolean &&
out.boolean &&
width == 1,
'is not of bools',
);
case ExpressionOp.select:
expect(read(a, 1, 'a').boolean, 'selects by a number');
expect(
read(b, width, 'b') == out && read(c, width, 'c') == out,
'selects between two units',
);
case ExpressionOp.dot:
expect(
out == read(a, width, 'a').times(read(b, width, 'b'), 1),
'writes the wrong unit',
);
case ExpressionOp.length ||
ExpressionOp.abs ||
ExpressionOp.floor ||
ExpressionOp.fract:
expect(read(a, width, 'a') == out, 'changes the unit');
case ExpressionOp.normalize || ExpressionOp.sign:
read(a, width, 'a');
expect(out == _Dim.none, 'is not a pure number');
case ExpressionOp.mix:
final x = read(a, span(1), 'a');
expect(
x == read(b, span(2), 'b') &&
read(c, span(4), 'c') == _Dim.none &&
x == out,
'mixes two units',
);
case ExpressionOp.clamp:
final x = read(a, span(1), 'a');
expect(
x == read(b, span(2), 'b') &&
x == read(c, span(4), 'c') &&
x == out,
'clamps across units',
);
case ExpressionOp.smoothstep:
final x = read(a, span(1), 'a');
expect(
x == read(b, span(2), 'b') &&
x == read(c, span(4), 'c') &&
out == _Dim.none,
'smooths across units',
);
case ExpressionOp.step || ExpressionOp.atan2:
expect(
read(a, span(1), 'a') == read(b, span(2), 'b') && out == _Dim.none,
'takes two units',
);
case ExpressionOp.min || ExpressionOp.max:
final x = read(a, span(1), 'a');
expect(x == read(b, span(2), 'b') && x == out, 'takes two units');
case ExpressionOp.pow:
final base = read(a, span(1), 'a');
expect(read(b, span(2), 'b') == _Dim.none, 'raises to a unit');
if (base.isNone) {
expect(out == _Dim.none, 'writes the wrong unit');
} else {
final exponent = laneConstant[b];
expect(
(flags & 2 != 0 || width == 1) &&
exponent != null &&
exponent == exponent.truncateToDouble() &&
exponent.abs() <= expressionPowerLimit &&
out == base.scaled(exponent.toInt()),
'raises a quantity to a power that is not a constant whole '
'number from -$expressionPowerLimit to $expressionPowerLimit',
);
}
case ExpressionOp.sqrt:
final x = read(a, width, 'a');
expect(x == out.scaled(2), 'writes the wrong unit');
case ExpressionOp.sin || ExpressionOp.cos || ExpressionOp.tan:
final x = read(a, width, 'a');
expect(
(x == _Dim.none || x == _Dim.angle) && out == _Dim.none,
'takes an angle or a pure number',
);
case ExpressionOp.asin ||
ExpressionOp.acos ||
ExpressionOp.atan ||
ExpressionOp.exp ||
ExpressionOp.log:
expect(
read(a, width, 'a') == _Dim.none && out == _Dim.none,
'takes a pure number',
);
default:
refuse(n, 'no opcode $op');
}
for (var i = 0; i < writes; i++) {
laneDim[dst + i] = d;
laneConstant[dst + i] = op == ExpressionOp.loadConstant
? constants[a + i]
: null;
}
}
if (resultDimension >= _dimensions.length ||
resultLane + resultWidth > lanes) {
throw const ExpressionBytecodeException('the result is out of range');
}
}