blob: 5d534460a9c29f9a9538a56423eb0b3336d3c5d9 [file] [log] [blame]
// 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.
library vm.bytecode.disassembler;
import 'dart:typed_data';
import 'package:kernel/ast.dart' show listEquals, listHashCode;
import 'dbc.dart';
import 'exceptions.dart';
class Instruction {
final Opcode opcode;
final bool isWide;
final List<int> operands;
final int pc;
Instruction(this.opcode, this.isWide, this.operands, this.pc);
Format get format => BytecodeFormats[opcode];
int get length => instructionSize(format.encoding, isWide);
@override
int get hashCode => opcode.index.hashCode ^ listHashCode(operands);
@override
bool operator ==(other) {
return (other is Instruction) &&
(opcode == other.opcode) &&
listEquals(operands, other.operands);
}
}
class BytecodeDisassembler {
Uint8List _bytecode;
List<Instruction> _instructions;
int _labelCount;
Map<int, String> _labels;
Map<int, List<String>> _markers;
String disassemble(List<int> bytecode, ExceptionsTable exceptionsTable,
{List<Map<int, String>> annotations}) {
_init(bytecode);
_scanForJumpTargets();
_markTryBlocks(exceptionsTable);
if (annotations != null) {
_markAnnotations(annotations);
}
return _disasm();
}
List<Instruction> decode(Uint8List bytecode) {
_init(bytecode);
return _instructions;
}
void _init(List<int> bytecode) {
_bytecode = new Uint8List.fromList(bytecode);
_instructions = new List<Instruction>();
for (int pos = 0; pos < _bytecode.length;) {
final instr = decodeInstructionAt(pos);
_instructions.add(instr);
pos += instr.length;
}
_labelCount = 0;
_labels = <int, String>{};
_markers = <int, List<String>>{};
}
Instruction decodeInstructionAt(int pos) {
Opcode opcode = Opcode.values[_bytecode[pos]];
bool isWide = isWideOpcode(opcode);
if (isWide) {
opcode = fromWideOpcode(opcode);
}
final format = BytecodeFormats[opcode];
final operands = _decodeOperands(format, pos, isWide);
return new Instruction(opcode, isWide, operands, pos);
}
List<int> _decodeOperands(Format format, int pos, bool isWide) {
switch (format.encoding) {
case Encoding.k0:
return const [];
case Encoding.kA:
return [_bytecode[pos + 1]];
case Encoding.kD:
return isWide ? [_decodeUint32At(pos + 1)] : [_bytecode[pos + 1]];
case Encoding.kX:
return isWide
? [_decodeUint32At(pos + 1).toSigned(32)]
: [_bytecode[pos + 1].toSigned(8)];
case Encoding.kT:
return isWide
? [
(_bytecode[pos + 1] +
(_bytecode[pos + 2] << 8) +
(_bytecode[pos + 3] << 16))
.toSigned(24)
]
: [_bytecode[pos + 1].toSigned(8)];
case Encoding.kAE:
return [
_bytecode[pos + 1],
isWide ? _decodeUint32At(pos + 2) : _bytecode[pos + 2],
];
case Encoding.kAY:
return [
_bytecode[pos + 1],
isWide
? _decodeUint32At(pos + 2).toSigned(32)
: _bytecode[pos + 2].toSigned(8)
];
case Encoding.kDF:
return isWide
? [_decodeUint32At(pos + 1), _bytecode[pos + 5]]
: [_bytecode[pos + 1], _bytecode[pos + 2]];
case Encoding.kABC:
return [_bytecode[pos + 1], _bytecode[pos + 2], _bytecode[pos + 3]];
}
throw 'Unexpected format $format';
}
_decodeUint32At(int pos) =>
_bytecode[pos] +
(_bytecode[pos + 1] << 8) +
(_bytecode[pos + 2] << 16) +
(_bytecode[pos + 3] << 24);
void _scanForJumpTargets() {
for (int i = 0; i < _instructions.length; i++) {
final instr = _instructions[i];
if (isJump(instr.opcode)) {
final target = instr.pc + instr.operands[0];
assert(0 <= target && target < _bytecode.length);
if (!_labels.containsKey(target)) {
final label = 'L${++_labelCount}';
_labels[target] = label;
_addMarker(target, '$label:');
}
}
}
}
void _markTryBlocks(ExceptionsTable exceptionsTable) {
for (var tryBlock in exceptionsTable.blocks) {
final int tryIndex = tryBlock.tryIndex;
_addMarker(tryBlock.startPC, 'Try #$tryIndex start:');
_addMarker(tryBlock.endPC, 'Try #$tryIndex end:');
_addMarker(tryBlock.handlerPC, 'Try #$tryIndex handler:');
}
}
void _markAnnotations(List<Map<int, String>> annotations) {
for (var map in annotations) {
map.forEach((int pc, String annotation) {
_addMarker(pc, '# $annotation');
});
}
}
void _addMarker(int pc, String marker) {
final markers = (_markers[pc] ??= <String>[]);
markers.add(marker);
}
String _disasm() {
StringBuffer out = new StringBuffer();
for (Instruction instr in _instructions) {
List<String> markers = _markers[instr.pc];
if (markers != null) {
markers.forEach(out.writeln);
}
writeInstruction(out, instr);
}
return out.toString();
}
void writeInstruction(StringBuffer out, Instruction instr) {
final format = BytecodeFormats[instr.opcode];
assert(format != null);
out.write(' ');
const int kOpcodeWidth = 20;
const String kOpcodePrefix = 'Opcode.k';
String opcode = instr.opcode.toString();
assert(opcode.startsWith(kOpcodePrefix));
opcode = opcode.substring(kOpcodePrefix.length);
if (instr.operands.isEmpty) {
out.writeln(opcode);
return;
}
out.write(opcode.padRight(kOpcodeWidth));
for (int i = 0; i < instr.operands.length; i++) {
if (i == 0) {
out.write(' ');
} else {
out.write(', ');
}
final operand =
_formatOperand(instr.pc, format.operands[i], instr.operands[i]);
out.write(operand);
}
out.writeln();
}
String _formatOperand(int pc, Operand fmt, int value) {
switch (fmt) {
case Operand.none:
break;
case Operand.imm:
return '$value';
case Operand.lit:
return 'CP#$value';
case Operand.reg:
return 'r$value';
case Operand.xeg:
return (value < 0) ? 'FP[$value]' : 'r$value';
case Operand.tgt:
return (_labels == null)
? value.toString()
: _labels[pc + value] ?? (throw 'Label not found');
case Operand.spe:
return SpecialIndex.values[value]
.toString()
.substring('SpecialIndex.'.length);
}
throw 'Unexpected operand format $fmt';
}
}