//===-- W65816Disassembler.cpp - Disassembler for W65816 -----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements the W65816Disassembler class, which converts a byte // stream into a stream of MCInsts. W65816 instructions are 1-4 bytes long: // a single-byte opcode followed by 0-3 bytes of little-endian operand data. // // For every opcode that has both an _Imm8 and _Imm16 variant (LDA, LDX, LDY, // ADC, SBC, CMP, AND, ORA, EOR, BIT, CPX, CPY), the bytes on the wire are // identical for both forms up to the opcode; the real operand width depends // on the M/X processor-mode bits. The TableGen descriptions put the _Imm8 // forms into the "W65816MHigh" / "W65816XHigh" decoder namespaces, so the // default "W65816" table contains only the 3-byte _Imm16 forms. This // scaffold disassembler reads exclusively from the default table, so those // mnemonics always decode as 3-byte (16-bit) immediates. A future mode- // tracking pass will consult the MHigh / XHigh tables when MReg / XReg are // set by preceding REP/SEP instructions. // //===----------------------------------------------------------------------===// #include "MCTargetDesc/W65816MCTargetDesc.h" #include "TargetInfo/W65816TargetInfo.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCDecoder.h" #include "llvm/MC/MCDecoderOps.h" #include "llvm/MC/MCDisassembler/MCDisassembler.h" #include "llvm/MC/MCInst.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Support/Compiler.h" using namespace llvm; using namespace llvm::MCD; #define DEBUG_TYPE "w65816-disassembler" using DecodeStatus = MCDisassembler::DecodeStatus; namespace { class W65816Disassembler : public MCDisassembler { public: W65816Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx) : MCDisassembler(STI, Ctx) {} DecodeStatus getInstruction(MCInst &MI, uint64_t &Size, ArrayRef Bytes, uint64_t Address, raw_ostream &CStream) const override; }; } // end anonymous namespace //===----------------------------------------------------------------------===// // Operand decoder callbacks. The encodings are little-endian immediates // immediately following the opcode; each callback just creates an MCOperand // whose integer value is the raw field bits. The printer does the pretty- // printing (hex, '$' prefix, etc.). //===----------------------------------------------------------------------===// static DecodeStatus decodeImm8(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { Inst.addOperand(MCOperand::createImm(Imm & 0xFF)); return MCDisassembler::Success; } static DecodeStatus decodeImm16(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { Inst.addOperand(MCOperand::createImm(Imm & 0xFFFF)); return MCDisassembler::Success; } static DecodeStatus decodeAddr8(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { Inst.addOperand(MCOperand::createImm(Imm & 0xFF)); return MCDisassembler::Success; } static DecodeStatus decodeAddr16(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { Inst.addOperand(MCOperand::createImm(Imm & 0xFFFF)); return MCDisassembler::Success; } static DecodeStatus decodeAddr24(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { Inst.addOperand(MCOperand::createImm(Imm & 0xFFFFFF)); return MCDisassembler::Success; } static DecodeStatus decodePCRel8(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { // Sign-extend the 8-bit displacement so the printer shows the correct // signed offset. Address-symbolization happens later if requested. int8_t Disp = static_cast(Imm & 0xFF); Inst.addOperand(MCOperand::createImm(Disp)); return MCDisassembler::Success; } static DecodeStatus decodePCRel16(MCInst &Inst, uint64_t Imm, uint64_t Address, const MCDisassembler *Decoder) { int16_t Disp = static_cast(Imm & 0xFFFF); Inst.addOperand(MCOperand::createImm(Disp)); return MCDisassembler::Success; } // The generated decoder tables reference the decode callbacks above by name, // so this include must come AFTER their declarations. #include "W65816GenDisassemblerTables.inc" //===----------------------------------------------------------------------===// // Size-dispatch: try each 1/2/3/4-byte table in turn from shortest to longest, // accepting the first successful decode. The tablegen tables are keyed on // the full little-endian byte pattern, so a single-byte implied-operand // opcode like NOP (0xEA) matches in DecoderTableW658168 and shorter-circuits // any accidental collision with a longer encoding that happens to begin with // the same byte. Every W65816 opcode byte uniquely determines the // instruction (modulo the M/X-mode ambiguity documented above), so the // first successful decode is always the correct one. //===----------------------------------------------------------------------===// DecodeStatus W65816Disassembler::getInstruction(MCInst &MI, uint64_t &Size, ArrayRef Bytes, uint64_t Address, raw_ostream &CStream) const { struct TableEntry { const uint8_t *Table; unsigned Bytes; }; static const TableEntry Tables[] = { { DecoderTableW658168, 1 }, { DecoderTableW6581616, 2 }, { DecoderTableW6581624, 3 }, { DecoderTableW6581632, 4 }, }; for (const TableEntry &T : Tables) { if (Bytes.size() < T.Bytes) { continue; } uint64_t Insn = 0; for (unsigned I = 0; I < T.Bytes; ++I) { Insn |= static_cast(Bytes[I]) << (8 * I); } MCInst Candidate; DecodeStatus Result = decodeInstruction(T.Table, Candidate, Insn, Address, this, STI); if (Result != MCDisassembler::Fail) { MI = Candidate; Size = T.Bytes; return Result; } } Size = 1; return MCDisassembler::Fail; } //===----------------------------------------------------------------------===// // Registration. //===----------------------------------------------------------------------===// static MCDisassembler *createW65816Disassembler(const Target &T, const MCSubtargetInfo &STI, MCContext &Ctx) { return new W65816Disassembler(STI, Ctx); } extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeW65816Disassembler() { TargetRegistry::RegisterMCDisassembler(getTheW65816Target(), createW65816Disassembler); }