637 lines
22 KiB
C++
637 lines
22 KiB
C++
//===-- W65816AsmPrinter.cpp - W65816 LLVM assembly writer ----------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Skeleton assembly printer. The MCInst lowering path is wired up but no
|
|
// target-specific operand formatting is implemented yet.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MCTargetDesc/W65816InstPrinter.h"
|
|
#include "W65816MachineFunctionInfo.h"
|
|
#include "W65816MCInstLower.h"
|
|
#include "W65816TargetMachine.h"
|
|
#include "TargetInfo/W65816TargetInfo.h"
|
|
#include "llvm/CodeGen/AsmPrinter.h"
|
|
#include "llvm/CodeGen/MachineInstr.h"
|
|
#include "llvm/MC/MCInst.h"
|
|
#include "llvm/MC/MCStreamer.h"
|
|
#include "llvm/MC/TargetRegistry.h"
|
|
#include "llvm/Support/Compiler.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "asm-printer"
|
|
|
|
namespace {
|
|
|
|
class W65816AsmPrinter : public AsmPrinter {
|
|
public:
|
|
W65816AsmPrinter(TargetMachine &TM, std::unique_ptr<MCStreamer> Streamer)
|
|
: AsmPrinter(TM, std::move(Streamer), ID) {}
|
|
|
|
StringRef getPassName() const override { return "W65816 Assembly Printer"; }
|
|
|
|
void emitInstruction(const MachineInstr *MI) override;
|
|
|
|
static char ID;
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
// Convert a single MachineOperand to an MCOperand using the standard
|
|
// rules (register / immediate / global / external / etc.). Used by
|
|
// pseudo expansion to lift symbolic operands; mirrors the per-operand
|
|
// path in W65816MCInstLower::Lower.
|
|
static MCOperand lowerOperand(const MachineOperand &MO,
|
|
W65816MCInstLower &Lower) {
|
|
switch (MO.getType()) {
|
|
case MachineOperand::MO_Register:
|
|
return MCOperand::createReg(MO.getReg());
|
|
case MachineOperand::MO_Immediate:
|
|
return MCOperand::createImm(MO.getImm());
|
|
case MachineOperand::MO_GlobalAddress:
|
|
return Lower.LowerSymbolOperand(MO, Lower.GetGlobalAddressSymbol(MO));
|
|
case MachineOperand::MO_ExternalSymbol:
|
|
return Lower.LowerSymbolOperand(MO, Lower.GetExternalSymbolSymbol(MO));
|
|
case MachineOperand::MO_BlockAddress:
|
|
return Lower.LowerSymbolOperand(MO, Lower.GetBlockAddressSymbol(MO));
|
|
case MachineOperand::MO_JumpTableIndex:
|
|
return Lower.LowerSymbolOperand(MO, Lower.GetJumpTableSymbol(MO));
|
|
case MachineOperand::MO_ConstantPoolIndex:
|
|
return Lower.LowerSymbolOperand(MO, Lower.GetConstantPoolIndexSymbol(MO));
|
|
default:
|
|
llvm_unreachable("W65816: unsupported operand type in pseudo lower");
|
|
}
|
|
}
|
|
|
|
void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|
W65816_MC::verifyInstructionPredicates(MI->getOpcode(),
|
|
getSubtargetInfo().getFeatureBits());
|
|
|
|
W65816MCInstLower MCInstLowering(OutContext, *this);
|
|
|
|
// Expand codegen pseudos into their MC-layer realisations. Keep this
|
|
// switch in sync with the pseudo defs at the bottom of
|
|
// W65816InstrInfo.td. All single-output pseudos here have operand
|
|
// layout (outs $dst, ins $value); we drop $dst (it lives implicitly in
|
|
// A) and lift $value into the MC instruction's single source operand.
|
|
switch (MI->getOpcode()) {
|
|
default:
|
|
break;
|
|
case W65816::ADJCALLSTACKDOWN: {
|
|
// DOWN is a no-op in our scheme — the PUSH16 sequence in LowerCall
|
|
// already shifted SP incrementally as args were pushed. Nothing
|
|
// to emit; PEI may or may not have processed it, either is fine.
|
|
return;
|
|
}
|
|
case W65816::ADJCALLSTACKUP: {
|
|
// PEI's eliminateCallFramePseudoInstr handles UP whenever the
|
|
// function has any frame work (StackSize > 0 or any FI use).
|
|
// Frame-less functions — e.g. `unsigned short sqr(unsigned short
|
|
// x) { return x*x; }` lowers to PUSH16 + jsl __mulhi3 + RTL with
|
|
// no locals — get skipped by PEI's call-frame phase, leaving
|
|
// ADJCALLSTACKUP as a pseudo all the way to here. Previously we
|
|
// silently dropped it, which left SP off by N bytes after the
|
|
// call and corrupted the caller's stack frame (caught by sqr(x)
|
|
// segfaulting MAME). Emit the SP fixup ourselves: PLY*N/2 for
|
|
// small even N, otherwise the TAY/TSC-ADC/TYA bracket.
|
|
int N = MI->getOperand(0).getImm();
|
|
if (N == 0) return;
|
|
// A holds the callee's return value; preserve it. Walk forward
|
|
// looking for X/Y uses (i64-return halves) — same logic as
|
|
// eliminateCallFramePseudoInstr.
|
|
bool YLive = false;
|
|
for (auto J = std::next(MI->getIterator()); J != MI->getParent()->end();
|
|
++J) {
|
|
if (J->isCall()) break;
|
|
bool yDef = false;
|
|
for (const MachineOperand &MO : J->operands()) {
|
|
if (!MO.isReg()) continue;
|
|
if (MO.getReg() == W65816::Y) {
|
|
if (MO.isUse()) { YLive = true; break; }
|
|
if (MO.isDef()) yDef = true;
|
|
}
|
|
}
|
|
if (YLive || yDef) break;
|
|
}
|
|
if (YLive) {
|
|
// Route through DP $E0 to preserve both A and Y.
|
|
MCInst Sta; Sta.setOpcode(W65816::STA_DP);
|
|
Sta.addOperand(MCOperand::createImm(0xE0));
|
|
EmitToStreamer(*OutStreamer, Sta);
|
|
MCInst Tsc; Tsc.setOpcode(W65816::TSC); EmitToStreamer(*OutStreamer, Tsc);
|
|
MCInst Clc; Clc.setOpcode(W65816::CLC); EmitToStreamer(*OutStreamer, Clc);
|
|
MCInst Adc; Adc.setOpcode(W65816::ADC_Imm16);
|
|
Adc.addOperand(MCOperand::createImm(N));
|
|
EmitToStreamer(*OutStreamer, Adc);
|
|
MCInst Tcs; Tcs.setOpcode(W65816::TCS); EmitToStreamer(*OutStreamer, Tcs);
|
|
MCInst Lda; Lda.setOpcode(W65816::LDA_DP);
|
|
Lda.addOperand(MCOperand::createImm(0xE0));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
} else if (N <= 14 && (N % 2) == 0) {
|
|
for (int i = 0; i < N / 2; ++i) {
|
|
MCInst Ply; Ply.setOpcode(W65816::PLY);
|
|
EmitToStreamer(*OutStreamer, Ply);
|
|
}
|
|
} else {
|
|
MCInst Tay; Tay.setOpcode(W65816::TAY); EmitToStreamer(*OutStreamer, Tay);
|
|
MCInst Tsc; Tsc.setOpcode(W65816::TSC); EmitToStreamer(*OutStreamer, Tsc);
|
|
MCInst Clc; Clc.setOpcode(W65816::CLC); EmitToStreamer(*OutStreamer, Clc);
|
|
MCInst Adc; Adc.setOpcode(W65816::ADC_Imm16);
|
|
Adc.addOperand(MCOperand::createImm(N));
|
|
EmitToStreamer(*OutStreamer, Adc);
|
|
MCInst Tcs; Tcs.setOpcode(W65816::TCS); EmitToStreamer(*OutStreamer, Tcs);
|
|
MCInst Tya; Tya.setOpcode(W65816::TYA); EmitToStreamer(*OutStreamer, Tya);
|
|
}
|
|
return;
|
|
}
|
|
case W65816::LDXi16imm: {
|
|
MCInst Ldx;
|
|
Ldx.setOpcode(W65816::LDX_Imm16);
|
|
Ldx.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Ldx);
|
|
return;
|
|
}
|
|
case W65816::LDAi16imm: {
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Imm16);
|
|
Lda.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
return;
|
|
}
|
|
case W65816::LDAi8imm: {
|
|
// i8 immediate — requires M=1 so the CPU reads only 1 immediate
|
|
// byte. The function runs in M=0 (prologue convention), so wrap
|
|
// with SEP/REP. Adjacent i8 ops collapse via W65816SepRepCleanup.
|
|
MCInst Sep; Sep.setOpcode(W65816::SEP);
|
|
Sep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Sep);
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Imm8);
|
|
int64_t Val = MI->getOperand(1).getImm() & 0xFF;
|
|
Lda.addOperand(MCOperand::createImm(Val));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
MCInst Rep; Rep.setOpcode(W65816::REP);
|
|
Rep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Rep);
|
|
return;
|
|
}
|
|
case W65816::LDAabs: {
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Abs);
|
|
Lda.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
return;
|
|
}
|
|
case W65816::STAabs: {
|
|
// STAabs is (outs), (ins Acc16:$src, addr:$addr). The MC STA_Abs
|
|
// takes only $addr; $src lives in the implicit A.
|
|
MCInst Sta;
|
|
Sta.setOpcode(W65816::STA_Abs);
|
|
Sta.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Sta);
|
|
return;
|
|
}
|
|
case W65816::ADCi16imm:
|
|
case W65816::SBCi16imm: {
|
|
bool IsSub = MI->getOpcode() == W65816::SBCi16imm;
|
|
MCInst Carry;
|
|
Carry.setOpcode(IsSub ? W65816::SEC : W65816::CLC);
|
|
EmitToStreamer(*OutStreamer, Carry);
|
|
|
|
MCInst Op;
|
|
Op.setOpcode(IsSub ? W65816::SBC_Imm16 : W65816::ADC_Imm16);
|
|
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
return;
|
|
}
|
|
case W65816::ADCEi16imm:
|
|
case W65816::SBCEi16imm: {
|
|
// Chained ADC/SBC: no CLC/SEC prefix — the carry/borrow from the
|
|
// previous addc/adde/subc/sube is already in P. See ADCi16imm
|
|
// comment in W65816InstrInfo.td.
|
|
bool IsSub = MI->getOpcode() == W65816::SBCEi16imm;
|
|
MCInst Op;
|
|
Op.setOpcode(IsSub ? W65816::SBC_Imm16 : W65816::ADC_Imm16);
|
|
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
return;
|
|
}
|
|
case W65816::ADCi8imm:
|
|
case W65816::SBCi8imm: {
|
|
bool IsSub = MI->getOpcode() == W65816::SBCi8imm;
|
|
// SEP/REP wrap (see LDAi8imm comment).
|
|
MCInst Sep; Sep.setOpcode(W65816::SEP);
|
|
Sep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Sep);
|
|
MCInst Carry;
|
|
Carry.setOpcode(IsSub ? W65816::SEC : W65816::CLC);
|
|
EmitToStreamer(*OutStreamer, Carry);
|
|
MCInst Op;
|
|
Op.setOpcode(IsSub ? W65816::SBC_Imm8 : W65816::ADC_Imm8);
|
|
int64_t Val = MI->getOperand(2).getImm() & 0xFF;
|
|
Op.addOperand(MCOperand::createImm(Val));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
MCInst Rep; Rep.setOpcode(W65816::REP);
|
|
Rep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Rep);
|
|
return;
|
|
}
|
|
case W65816::ANDi8imm:
|
|
case W65816::ORAi8imm:
|
|
case W65816::EORi8imm: {
|
|
MCInst Op;
|
|
unsigned mc = 0;
|
|
switch (MI->getOpcode()) {
|
|
case W65816::ANDi8imm: mc = W65816::AND_Imm8; break;
|
|
case W65816::ORAi8imm: mc = W65816::ORA_Imm8; break;
|
|
case W65816::EORi8imm: mc = W65816::EOR_Imm8; break;
|
|
}
|
|
Op.setOpcode(mc);
|
|
// Mask to 8 bits so the printer doesn't show the sign-extended
|
|
// i8 value as a wider hex literal (e.g. -16 → 0xFFF0); the
|
|
// encoder only takes the low byte anyway.
|
|
int64_t Val = MI->getOperand(2).getImm() & 0xFF;
|
|
Op.addOperand(MCOperand::createImm(Val));
|
|
// SEP/REP wrap (see LDAi8imm comment).
|
|
MCInst Sep; Sep.setOpcode(W65816::SEP);
|
|
Sep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Sep);
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
MCInst Rep; Rep.setOpcode(W65816::REP);
|
|
Rep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Rep);
|
|
return;
|
|
}
|
|
case W65816::LDA8abs: {
|
|
// i8 absolute load — same byte sequence as LDA_Abs in M=0, but
|
|
// semantically loads 1 byte not 2. Need M=1 wrap so we don't
|
|
// also pull in the byte at addr+1 (often another global, which is
|
|
// harmless to read but corrupts A_hi for any consumer that cares).
|
|
MCInst Sep; Sep.setOpcode(W65816::SEP);
|
|
Sep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Sep);
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Abs);
|
|
Lda.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
MCInst Rep; Rep.setOpcode(W65816::REP);
|
|
Rep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Rep);
|
|
return;
|
|
}
|
|
case W65816::STA8abs: {
|
|
// STA_Abs is 16-bit when M=0, 8-bit when M=1. Pure-i8 functions
|
|
// run with M=1 and a bare STA is correct. M=0 functions need an
|
|
// SEP/REP wrap so the STA stores only one byte — without it, the
|
|
// store clobbers the byte at addr+1 (potentially another global).
|
|
bool UsesAcc8 = MI->getMF()
|
|
->getInfo<W65816MachineFunctionInfo>()
|
|
->getUsesAcc8();
|
|
if (!UsesAcc8) {
|
|
MCInst Sep; Sep.setOpcode(W65816::SEP);
|
|
Sep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Sep);
|
|
}
|
|
MCInst Sta;
|
|
Sta.setOpcode(W65816::STA_Abs);
|
|
Sta.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Sta);
|
|
if (!UsesAcc8) {
|
|
MCInst Rep; Rep.setOpcode(W65816::REP);
|
|
Rep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Rep);
|
|
}
|
|
return;
|
|
}
|
|
case W65816::ADCabs:
|
|
case W65816::SBCabs: {
|
|
bool IsSub = MI->getOpcode() == W65816::SBCabs;
|
|
MCInst Carry;
|
|
Carry.setOpcode(IsSub ? W65816::SEC : W65816::CLC);
|
|
EmitToStreamer(*OutStreamer, Carry);
|
|
|
|
MCInst Op;
|
|
Op.setOpcode(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs);
|
|
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
return;
|
|
}
|
|
case W65816::ADCEabs:
|
|
case W65816::SBCEabs: {
|
|
// Chained variant — no CLC/SEC prefix.
|
|
bool IsSub = MI->getOpcode() == W65816::SBCEabs;
|
|
MCInst Op;
|
|
Op.setOpcode(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs);
|
|
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
return;
|
|
}
|
|
case W65816::CMPi16imm: {
|
|
// CMPi16imm has (outs), (ins Acc16:$lhs, i16imm:$rhs); MC needs only
|
|
// the immediate.
|
|
MCInst Cmp;
|
|
Cmp.setOpcode(W65816::CMP_Imm16);
|
|
Cmp.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Cmp);
|
|
return;
|
|
}
|
|
case W65816::CMPi8imm: {
|
|
// i8 immediate compare — needs M=1 so the CPU only reads 1 byte
|
|
// for the immediate. See LDAi8imm comment for the wrap rationale.
|
|
MCInst Sep; Sep.setOpcode(W65816::SEP);
|
|
Sep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Sep);
|
|
MCInst Cmp;
|
|
Cmp.setOpcode(W65816::CMP_Imm8);
|
|
int64_t Val = MI->getOperand(1).getImm() & 0xFF;
|
|
Cmp.addOperand(MCOperand::createImm(Val));
|
|
EmitToStreamer(*OutStreamer, Cmp);
|
|
MCInst Rep; Rep.setOpcode(W65816::REP);
|
|
Rep.addOperand(MCOperand::createImm(0x20));
|
|
EmitToStreamer(*OutStreamer, Rep);
|
|
return;
|
|
}
|
|
case W65816::CMPabs: {
|
|
MCInst Cmp;
|
|
Cmp.setOpcode(W65816::CMP_Abs);
|
|
Cmp.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Cmp);
|
|
return;
|
|
}
|
|
// Bitwise immediate / memory pseudos: simple opcode swap, no carry
|
|
// prefix. Operand 0 = $dst (output, tied), 1 = $src (use), 2 = imm/addr.
|
|
case W65816::ANDi16imm:
|
|
case W65816::ORAi16imm:
|
|
case W65816::EORi16imm: {
|
|
MCInst Op;
|
|
unsigned mc = 0;
|
|
switch (MI->getOpcode()) {
|
|
case W65816::ANDi16imm: mc = W65816::AND_Imm16; break;
|
|
case W65816::ORAi16imm: mc = W65816::ORA_Imm16; break;
|
|
case W65816::EORi16imm: mc = W65816::EOR_Imm16; break;
|
|
}
|
|
Op.setOpcode(mc);
|
|
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
return;
|
|
}
|
|
case W65816::ANDabs:
|
|
case W65816::ORAabs:
|
|
case W65816::EORabs: {
|
|
MCInst Op;
|
|
unsigned mc = 0;
|
|
switch (MI->getOpcode()) {
|
|
case W65816::ANDabs: mc = W65816::AND_Abs; break;
|
|
case W65816::ORAabs: mc = W65816::ORA_Abs; break;
|
|
case W65816::EORabs: mc = W65816::EOR_Abs; break;
|
|
}
|
|
Op.setOpcode(mc);
|
|
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Op);
|
|
return;
|
|
}
|
|
case W65816::JSLpseudo: {
|
|
MCInst Jsl;
|
|
Jsl.setOpcode(W65816::JSL_Long);
|
|
Jsl.addOperand(lowerOperand(MI->getOperand(0), MCInstLowering));
|
|
EmitToStreamer(*OutStreamer, Jsl);
|
|
return;
|
|
}
|
|
case W65816::PUSH16: {
|
|
MCInst Pha;
|
|
Pha.setOpcode(W65816::PHA);
|
|
EmitToStreamer(*OutStreamer, Pha);
|
|
return;
|
|
}
|
|
case W65816::ALLOCAfi: {
|
|
// VLA / dynamic_stackalloc: A holds size on entry; on exit A holds
|
|
// pointer to the allocated region.
|
|
// TSC ; A = SP
|
|
// SEC ; clear borrow
|
|
// SBC size (in $E0) ; A = SP - size
|
|
// TCS ; SP = A
|
|
// INC A ; A = SP + 1, the lowest byte of the region
|
|
// Size is in A on entry — but we need A=SP after TSC, so first
|
|
// stash the size to DP scratch.
|
|
MCInst Sta1; Sta1.setOpcode(W65816::STA_DP);
|
|
Sta1.addOperand(MCOperand::createImm(0xE0));
|
|
EmitToStreamer(*OutStreamer, Sta1);
|
|
MCInst Tsc; Tsc.setOpcode(W65816::TSC); EmitToStreamer(*OutStreamer, Tsc);
|
|
MCInst Sec; Sec.setOpcode(W65816::SEC); EmitToStreamer(*OutStreamer, Sec);
|
|
MCInst Sbc; Sbc.setOpcode(W65816::SBC_DP);
|
|
Sbc.addOperand(MCOperand::createImm(0xE0));
|
|
EmitToStreamer(*OutStreamer, Sbc);
|
|
MCInst Tcs; Tcs.setOpcode(W65816::TCS); EmitToStreamer(*OutStreamer, Tcs);
|
|
MCInst Ina; Ina.setOpcode(W65816::INA); EmitToStreamer(*OutStreamer, Ina);
|
|
return;
|
|
}
|
|
case W65816::PUSH16X: {
|
|
MCInst Phx;
|
|
Phx.setOpcode(W65816::PHX);
|
|
EmitToStreamer(*OutStreamer, Phx);
|
|
return;
|
|
}
|
|
case W65816::ASLA16: {
|
|
MCInst Asl;
|
|
Asl.setOpcode(W65816::ASL_A);
|
|
EmitToStreamer(*OutStreamer, Asl);
|
|
return;
|
|
}
|
|
case W65816::LSRA16:
|
|
case W65816::LSRA8: {
|
|
MCInst Lsr;
|
|
Lsr.setOpcode(W65816::LSR_A);
|
|
EmitToStreamer(*OutStreamer, Lsr);
|
|
return;
|
|
}
|
|
case W65816::ASLA8: {
|
|
MCInst Asl;
|
|
Asl.setOpcode(W65816::ASL_A);
|
|
EmitToStreamer(*OutStreamer, Asl);
|
|
return;
|
|
}
|
|
case W65816::ASRA16: {
|
|
// PHA ; ASL A (sets carry from sign bit) ; PLA ; ROR A
|
|
MCInst pha; pha.setOpcode(W65816::PHA); EmitToStreamer(*OutStreamer, pha);
|
|
MCInst asl; asl.setOpcode(W65816::ASL_A); EmitToStreamer(*OutStreamer, asl);
|
|
MCInst pla; pla.setOpcode(W65816::PLA); EmitToStreamer(*OutStreamer, pla);
|
|
MCInst ror; ror.setOpcode(W65816::ROR_A); EmitToStreamer(*OutStreamer, ror);
|
|
return;
|
|
}
|
|
case W65816::XBA16: {
|
|
MCInst Xba;
|
|
Xba.setOpcode(W65816::XBA);
|
|
EmitToStreamer(*OutStreamer, Xba);
|
|
return;
|
|
}
|
|
case W65816::INA_PSEUDO: {
|
|
MCInst In;
|
|
In.setOpcode(W65816::INA);
|
|
EmitToStreamer(*OutStreamer, In);
|
|
return;
|
|
}
|
|
case W65816::DEA_PSEUDO:
|
|
case W65816::DEA_PSEUDO8: {
|
|
MCInst De;
|
|
De.setOpcode(W65816::DEA);
|
|
EmitToStreamer(*OutStreamer, De);
|
|
return;
|
|
}
|
|
case W65816::INA_PSEUDO8: {
|
|
MCInst In;
|
|
In.setOpcode(W65816::INA);
|
|
EmitToStreamer(*OutStreamer, In);
|
|
return;
|
|
}
|
|
case W65816::NEGA16: {
|
|
// EOR #$FFFF; INC A.
|
|
MCInst Eor;
|
|
Eor.setOpcode(W65816::EOR_Imm16);
|
|
Eor.addOperand(MCOperand::createImm(0xFFFF));
|
|
EmitToStreamer(*OutStreamer, Eor);
|
|
MCInst Inc;
|
|
Inc.setOpcode(W65816::INA);
|
|
EmitToStreamer(*OutStreamer, Inc);
|
|
return;
|
|
}
|
|
case W65816::NEGA8: {
|
|
// EOR #$FF; INC A — same idea as NEGA16 but in 8-bit M.
|
|
// The function context is already 8-bit M when an i8-only path
|
|
// is selected, so no SEP/REP wrap is needed here.
|
|
MCInst Eor;
|
|
Eor.setOpcode(W65816::EOR_Imm8);
|
|
Eor.addOperand(MCOperand::createImm(0xFF));
|
|
EmitToStreamer(*OutStreamer, Eor);
|
|
MCInst Inc;
|
|
Inc.setOpcode(W65816::INA);
|
|
EmitToStreamer(*OutStreamer, Inc);
|
|
return;
|
|
}
|
|
case W65816::NEGC16: {
|
|
// (subc 0, x) — lo half of multi-precision negate.
|
|
// EOR #$FFFF; CLC; ADC #1. C-out = 1 iff result = 0 (i.e. x was 0),
|
|
// matching SBC's "no borrow" convention.
|
|
MCInst Eor;
|
|
Eor.setOpcode(W65816::EOR_Imm16);
|
|
Eor.addOperand(MCOperand::createImm(0xFFFF));
|
|
EmitToStreamer(*OutStreamer, Eor);
|
|
MCInst Clc;
|
|
Clc.setOpcode(W65816::CLC);
|
|
EmitToStreamer(*OutStreamer, Clc);
|
|
MCInst Adc;
|
|
Adc.setOpcode(W65816::ADC_Imm16);
|
|
Adc.addOperand(MCOperand::createImm(1));
|
|
EmitToStreamer(*OutStreamer, Adc);
|
|
return;
|
|
}
|
|
case W65816::SRL15A: {
|
|
// ASL A; LDA #0; ROL A — extract bit 15 to bit 0.
|
|
MCInst Asl;
|
|
Asl.setOpcode(W65816::ASL_A);
|
|
EmitToStreamer(*OutStreamer, Asl);
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Imm16);
|
|
Lda.addOperand(MCOperand::createImm(0));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
MCInst Rol;
|
|
Rol.setOpcode(W65816::ROL_A);
|
|
EmitToStreamer(*OutStreamer, Rol);
|
|
return;
|
|
}
|
|
case W65816::SHL15A: {
|
|
// LSR A; LDA #0; ROR A — move bit 0 to bit 15.
|
|
MCInst Lsr;
|
|
Lsr.setOpcode(W65816::LSR_A);
|
|
EmitToStreamer(*OutStreamer, Lsr);
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Imm16);
|
|
Lda.addOperand(MCOperand::createImm(0));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
MCInst Ror;
|
|
Ror.setOpcode(W65816::ROR_A);
|
|
EmitToStreamer(*OutStreamer, Ror);
|
|
return;
|
|
}
|
|
case W65816::SRL8A: {
|
|
// XBA; AND #$00FF — high byte to low byte, zero high.
|
|
MCInst Xba;
|
|
Xba.setOpcode(W65816::XBA);
|
|
EmitToStreamer(*OutStreamer, Xba);
|
|
MCInst And;
|
|
And.setOpcode(W65816::AND_Imm16);
|
|
And.addOperand(MCOperand::createImm(0x00FF));
|
|
EmitToStreamer(*OutStreamer, And);
|
|
return;
|
|
}
|
|
case W65816::SHL8A: {
|
|
// XBA; AND #$FF00 — low byte to high byte, zero low.
|
|
MCInst Xba;
|
|
Xba.setOpcode(W65816::XBA);
|
|
EmitToStreamer(*OutStreamer, Xba);
|
|
MCInst And;
|
|
And.setOpcode(W65816::AND_Imm16);
|
|
And.addOperand(MCOperand::createImm(0xFF00));
|
|
EmitToStreamer(*OutStreamer, And);
|
|
return;
|
|
}
|
|
case W65816::SRA15A: {
|
|
// ASL A; LDA #0; ADC #-1; EOR #-1 — sign-fill from bit 15.
|
|
// ASL: C = bit 15 of input (the sign).
|
|
// LDA #0: A = 0, C unchanged.
|
|
// ADC #-1: A = 0 + (-1) + C = -1 + C. If C=1 (neg): A = 0; if
|
|
// C=0 (pos): A = -1. Inverted from what we want.
|
|
// EOR #-1: flip bits — A = -1 (neg) or 0 (pos), correct.
|
|
MCInst Asl;
|
|
Asl.setOpcode(W65816::ASL_A);
|
|
EmitToStreamer(*OutStreamer, Asl);
|
|
MCInst Lda;
|
|
Lda.setOpcode(W65816::LDA_Imm16);
|
|
Lda.addOperand(MCOperand::createImm(0));
|
|
EmitToStreamer(*OutStreamer, Lda);
|
|
MCInst Adc;
|
|
Adc.setOpcode(W65816::ADC_Imm16);
|
|
Adc.addOperand(MCOperand::createImm(0xFFFF));
|
|
EmitToStreamer(*OutStreamer, Adc);
|
|
MCInst Eor;
|
|
Eor.setOpcode(W65816::EOR_Imm16);
|
|
Eor.addOperand(MCOperand::createImm(0xFFFF));
|
|
EmitToStreamer(*OutStreamer, Eor);
|
|
return;
|
|
}
|
|
case W65816::NEGE16: {
|
|
// (sube 0, x) — hi half of multi-precision negate.
|
|
// EOR #$FFFF; ADC #0. Carry-in from the previous subc/sube is
|
|
// already in P; ADC #0 propagates it as ~x + C, which matches
|
|
// 0 - x - !C in two's complement.
|
|
MCInst Eor;
|
|
Eor.setOpcode(W65816::EOR_Imm16);
|
|
Eor.addOperand(MCOperand::createImm(0xFFFF));
|
|
EmitToStreamer(*OutStreamer, Eor);
|
|
MCInst Adc;
|
|
Adc.setOpcode(W65816::ADC_Imm16);
|
|
Adc.addOperand(MCOperand::createImm(0));
|
|
EmitToStreamer(*OutStreamer, Adc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
MCInst TmpInst;
|
|
MCInstLowering.Lower(MI, TmpInst);
|
|
EmitToStreamer(*OutStreamer, TmpInst);
|
|
}
|
|
|
|
char W65816AsmPrinter::ID = 0;
|
|
|
|
INITIALIZE_PASS(W65816AsmPrinter, "w65816-asm-printer",
|
|
"W65816 Assembly Printer", false, false)
|
|
|
|
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
|
LLVMInitializeW65816AsmPrinter() {
|
|
RegisterAsmPrinter<W65816AsmPrinter> X(getTheW65816Target());
|
|
}
|