Many deferred issues finally fixed.

This commit is contained in:
Scott Duensing 2026-06-30 18:00:50 -05:00
parent 178ed633ee
commit 1a0ef7ee87
8 changed files with 254 additions and 65 deletions

View file

@ -285,6 +285,17 @@ which runs correctly under MAME (apple2gs).
rodata/bss/init_array relocations applied to every `.debug_*` rodata/bss/init_array relocations applied to every `.debug_*`
section, so `.debug_addr` / `.debug_line` PC values are final- section, so `.debug_addr` / `.debug_line` PC values are final-
image addresses. image addresses.
- `llvm-mc --disassemble` / `llvm-objdump -d` decode W65816 object
code, tracking the M (accumulator/memory) and X (index) width
flags across a linear sweep: state is seeded to the C ABI default
(M=16/X=16), updated as REP/SEP immediates are decoded, and used to
pick the 1-byte vs 2-byte immediate form of the mode-sensitive
LDA/LDX/.../CPY opcodes. Best-effort linear heuristic only (it
cannot see mode set across branches, by a caller's ABI convention,
or through data-in-code, and there is no per-section reset), but it
decodes clang codegen (M=16/X=16 invariant) and straight-line
hand-written 8-bit-mode asm correctly — crt0.o and libc.o (23k
insns) disassemble fully coherent.
- `runtime/build.sh` builds crt0, libc, soft-float, soft-double, - `runtime/build.sh` builds crt0, libc, soft-float, soft-double,
libgcc into linkable objects. libgcc into linkable objects.
- `scripts/smokeTest.sh` runs 148 end-to-end checks at -O2: - `scripts/smokeTest.sh` runs 148 end-to-end checks at -O2:

View file

@ -136,15 +136,32 @@ fi
# --disassemble should produce the mnemonic we expect. # --disassemble should produce the mnemonic we expect.
if [ -x "$LLVM_MC" ]; then if [ -x "$LLVM_MC" ]; then
log "check: llvm-mc --disassemble decodes bytes back to mnemonics" log "check: llvm-mc --disassemble decodes bytes back to mnemonics"
disasmOut="$(printf '0xea 0xa9 0x34 0x12 0x85 0x10 0x8d 0x00 0x10 0x6b\n' \ disasmOut="$(printf '0xea 0xa9 0x34 0x12 0x85 0x10 0x8d 0x00 0x10 0xfc 0x00 0x10 0x6b\n' \
| "$LLVM_MC" --disassemble --triple=w65816 2>&1)" | "$LLVM_MC" --disassemble --triple=w65816 2>&1)"
for mnem in "nop" "lda #0x1234" "sta 0x10" "sta 0x1000" "rtl"; do for mnem in "nop" "lda #0x1234" "sta 0x10" "sta 0x1000" "jsr (0x1000, x)" "rtl"; do
if ! printf '%s\n' "$disasmOut" | grep -qF "$mnem"; then if ! printf '%s\n' "$disasmOut" | grep -qF "$mnem"; then
warn "disassembler missing: $mnem" warn "disassembler missing: $mnem"
printf '%s\n' "$disasmOut" >&2 printf '%s\n' "$disasmOut" >&2
die "disassembler round-trip failed" die "disassembler round-trip failed"
fi fi
done done
# 5b. M/X mode tracking. The same immediate-mode opcode (a9 = LDA #)
# decodes as a 1-byte immediate after SEP #$20 (M->8) and a 2-byte one
# after REP #$20 (M->16); X is tracked independently via SEP/REP #$10.
# Without the linear REP/SEP heuristic, the 8-bit LDA would over-read a
# byte and desync the rest of the stream.
log "check: llvm-mc --disassemble tracks M/X across SEP/REP"
# SEP #$20 ; lda #$34 ; ldx #$1234 ; REP #$20 ; lda #$3412 ; SEP #$10 ; ldx #$05
mxOut="$(printf '0xe2 0x20 0xa9 0x34 0xa2 0x34 0x12 0xc2 0x20 0xa9 0x12 0x34 0xe2 0x10 0xa2 0x05\n' \
| "$LLVM_MC" --disassemble --triple=w65816 2>&1)"
for mnem in "lda #0x34" "ldx #0x1234" "lda #0x3412" "ldx #0x5"; do
if ! printf '%s\n' "$mxOut" | grep -qF "$mnem"; then
warn "M/X tracking wrong: expected $mnem"
printf '%s\n' "$mxOut" >&2
die "disassembler M/X tracking failed"
fi
done
fi fi
# 6. End-to-end codegen: IR -> asm -> ELF -> disassembly. # 6. End-to-end codegen: IR -> asm -> ELF -> disassembly.

View file

@ -70,6 +70,10 @@ public:
return Feature == "w65816"; return Feature == "w65816";
} }
bool hasBitIntType() const override { return true; }
size_t getMaxBitIntWidth() const override { return 64; }
ArrayRef<const char *> getGCCRegNames() const override; ArrayRef<const char *> getGCCRegNames() const override;
ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override { ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override {

View file

@ -15,11 +15,20 @@
// identical for both forms up to the opcode; the real operand width depends // 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 // on the M/X processor-mode bits. The TableGen descriptions put the _Imm8
// forms into the "W65816MHigh" / "W65816XHigh" decoder namespaces, so the // forms into the "W65816MHigh" / "W65816XHigh" decoder namespaces, so the
// default "W65816" table contains only the 3-byte _Imm16 forms. This // default "W65816" table contains only the 3-byte _Imm16 forms.
// scaffold disassembler reads exclusively from the default table, so those //
// mnemonics always decode as 3-byte (16-bit) immediates. A future mode- // To resolve the ambiguity this disassembler tracks the M (accumulator/memory
// tracking pass will consult the MHigh / XHigh tables when MReg / XReg are // width) and X (index width) processor flags across a linear sweep: it seeds
// set by preceding REP/SEP instructions. // them to the C ABI default (M=16/X=16), watches the REP/SEP immediates as
// they decode, and — when a flag is 8-bit — consults the MHigh16 / XHigh16
// tables (2-byte: opcode + imm8) ahead of the default table so the affected
// mnemonics decode as 1-byte immediates. This is a best-effort *linear*
// heuristic: it follows straight-line REP/SEP only and cannot see mode
// established across branches, by a caller's ABI convention, or through
// data-in-code, and there is no per-section reset hook, so a region entered
// in a different mode than the previous region left set may decode wrong.
// For clang-generated code (M=16/X=16 invariant) the default path is always
// correct; the tracking matters for hand-written 8-bit-mode asm.
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -44,9 +53,27 @@ using DecodeStatus = MCDisassembler::DecodeStatus;
namespace { namespace {
class W65816Disassembler : public MCDisassembler { class W65816Disassembler : public MCDisassembler {
// Tracked processor-mode bits, seeded to the C ABI default (16-bit).
// getInstruction is called sequentially per instruction by llvm-objdump /
// llvm-mc, so this mutable state follows the byte stream linearly.
mutable bool AccIs8 = false; // M flag: true => 8-bit accumulator/memory
mutable bool IdxIs8 = false; // X flag: true => 8-bit index registers
// A copy of the subtarget with the MHigh / XHigh assembler predicates forced
// on, used only when decoding the mode-sensitive _Imm8 tables. The generated
// decoder gates those entries behind FeatureMHigh / FeatureXHigh; the running
// subtarget leaves them off, so we supply an enabled copy when (and only
// when) our own M/X tracking has decided an 8-bit form applies.
MCSubtargetInfo ModeSTI;
public: public:
W65816Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx) W65816Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx)
: MCDisassembler(STI, Ctx) {} : MCDisassembler(STI, Ctx), ModeSTI(STI) {
FeatureBitset FB = ModeSTI.getFeatureBits();
FB.set(W65816::FeatureMHigh);
FB.set(W65816::FeatureXHigh);
ModeSTI.setFeatureBits(FB);
}
DecodeStatus getInstruction(MCInst &MI, uint64_t &Size, DecodeStatus getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes, uint64_t Address, ArrayRef<uint8_t> Bytes, uint64_t Address,
@ -129,50 +156,110 @@ static DecodeStatus decodePCRel16(MCInst &Inst, uint64_t Imm, uint64_t Address,
// opcode like NOP (0xEA) matches in DecoderTableW658168 and shorter-circuits // opcode like NOP (0xEA) matches in DecoderTableW658168 and shorter-circuits
// any accidental collision with a longer encoding that happens to begin with // any accidental collision with a longer encoding that happens to begin with
// the same byte. Every W65816 opcode byte uniquely determines the // the same byte. Every W65816 opcode byte uniquely determines the
// instruction (modulo the M/X-mode ambiguity documented above), so the // instruction (modulo the M/X-mode ambiguity resolved by the tracking below),
// first successful decode is always the correct one. // so the first successful decode is always the correct one.
//
// Mode tracking: when the tracked M (or X) flag is 8-bit, the affected
// LDA/ADC/.../CPY opcodes carry a 1-byte immediate, whose _Imm8 forms live in
// DecoderTableW65816MHigh16 / XHigh16 (2 bytes: opcode + imm8). Those tables
// hold *only* the mode-sensitive mnemonics, so a successful decode there can
// only be one of them; we try the matching table ahead of the default 16-bit
// forms. After every decode, REP/SEP immediates update the tracked flags.
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// REP / SEP opcode bytes and the P-register flag bits they manipulate.
static constexpr uint8_t kOpcodeSEP = 0xE2; // SEP #imm: sets the named P bits
static constexpr uint8_t kOpcodeREP = 0xC2; // REP #imm: clears the named P bits
static constexpr uint8_t kFlagM = 0x20; // accumulator/memory width bit
static constexpr uint8_t kFlagX = 0x10; // index-register width bit
DecodeStatus W65816Disassembler::getInstruction(MCInst &MI, uint64_t &Size, DecodeStatus W65816Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes, ArrayRef<uint8_t> Bytes,
uint64_t Address, uint64_t Address,
raw_ostream &CStream) const { raw_ostream &CStream) const {
// Decode `NBytes` little-endian bytes from the stream against `Table`, using
// the given subtarget for predicate checks (ModeSTI for the predicate-gated
// High tables, the running STI otherwise).
auto tryTable = [&](const uint8_t *Table, unsigned NBytes,
const MCSubtargetInfo &DecodeSTI,
MCInst &Out) -> DecodeStatus {
if (Bytes.size() < NBytes) {
return MCDisassembler::Fail;
}
uint64_t Insn = 0;
for (unsigned I = 0; I < NBytes; ++I) {
Insn |= static_cast<uint64_t>(Bytes[I]) << (8 * I);
}
return decodeInstruction(Table, Out, Insn, Address, this, DecodeSTI);
};
MCInst Candidate;
DecodeStatus Result = MCDisassembler::Fail;
uint64_t DecodedSize = 0;
// Mode-sensitive 8-bit-immediate forms take priority when the tracked flag
// says 8-bit; the High16 tables only contain those mnemonics, so a miss
// simply falls through to the default size-dispatch below.
if (AccIs8) {
Result = tryTable(DecoderTableW65816MHigh16, 2, ModeSTI, Candidate);
}
if (Result == MCDisassembler::Fail && IdxIs8) {
Result = tryTable(DecoderTableW65816XHigh16, 2, ModeSTI, Candidate);
}
if (Result != MCDisassembler::Fail) {
DecodedSize = 2;
} else {
struct TableEntry { struct TableEntry {
const uint8_t *Table; const uint8_t *Table;
unsigned Bytes; unsigned Bytes;
}; };
static const TableEntry Tables[] = { static const TableEntry Tables[] = {
{ DecoderTableW658168, 1 }, { DecoderTableW658168, 1 },
{ DecoderTableW6581616, 2 }, { DecoderTableW6581616, 2 },
{ DecoderTableW6581624, 3 }, { DecoderTableW6581624, 3 },
{ DecoderTableW6581632, 4 }, { DecoderTableW6581632, 4 },
}; };
for (const TableEntry &T : Tables) { for (const TableEntry &T : Tables) {
if (Bytes.size() < T.Bytes) { Result = tryTable(T.Table, T.Bytes, STI, Candidate);
continue;
}
uint64_t Insn = 0;
for (unsigned I = 0; I < T.Bytes; ++I) {
Insn |= static_cast<uint64_t>(Bytes[I]) << (8 * I);
}
MCInst Candidate;
DecodeStatus Result = decodeInstruction(T.Table, Candidate, Insn, Address,
this, STI);
if (Result != MCDisassembler::Fail) { if (Result != MCDisassembler::Fail) {
MI = Candidate; DecodedSize = T.Bytes;
Size = T.Bytes; break;
return Result; }
} }
} }
if (Result == MCDisassembler::Fail) {
Size = 1; Size = 1;
return MCDisassembler::Fail; return MCDisassembler::Fail;
} }
// Follow REP/SEP so subsequent mode-sensitive immediates decode at the right
// width. SEP #imm sets (=> 8-bit) the P bits present in imm; REP #imm
// clears (=> 16-bit) them. Both are fixed 2-byte encodings (opcode + imm8).
if (DecodedSize >= 2) {
uint8_t Imm = Bytes[1];
if (Bytes[0] == kOpcodeSEP) {
if (Imm & kFlagM) {
AccIs8 = true;
}
if (Imm & kFlagX) {
IdxIs8 = true;
}
} else if (Bytes[0] == kOpcodeREP) {
if (Imm & kFlagM) {
AccIs8 = false;
}
if (Imm & kFlagX) {
IdxIs8 = false;
}
}
}
MI = Candidate;
Size = DecodedSize;
return Result;
}
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Registration. // Registration.

View file

@ -6,9 +6,10 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// //
// This class prints a W65816 MCInst to a .s file. The skeleton does not yet // This class prints a W65816 MCInst to a .s file. Most instructions print
// implement any real instructions so the printer mostly defers to the table- // via the table-generated routines (printInstruction); this file supplies the
// generated routines and reports unimplemented cases with llvm_unreachable. // custom operand printers (hex immediates with the '$' prefix, addressing-mode
// operands, PC-relative branch targets).
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//

View file

@ -6,8 +6,12 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// //
// Minimum DAG lowering sufficient for a no-argument function returning an // DAG lowering for the W65816: argument passing and returns over the
// i16 constant. Argument passing and non-trivial calls still unimplemented. // A/X/Y/DPF0 register ABI (i8/i16 in A, i32 in A:X, i64 across A:X:Y + a DP
// slot, larger aggregates via sret), direct and indirect calls (the latter
// through the __jsl_indir trampoline), i32 arithmetic over the Wide32 reg
// class, custom BR_CC / SELECT_CC / SETCC including the i32 high/low split,
// and the pointer-deref load/store lowerings.
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -23,6 +27,7 @@
#include "llvm/CodeGen/SelectionDAG.h" #include "llvm/CodeGen/SelectionDAG.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h" #include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/Support/KnownBits.h" #include "llvm/Support/KnownBits.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Function.h" #include "llvm/IR/Function.h"
#include "llvm/Support/CommandLine.h" #include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorHandling.h"
@ -31,6 +36,24 @@ using namespace llvm;
#define DEBUG_TYPE "w65816-lower" #define DEBUG_TYPE "w65816-lower"
// Emit a clean, source-located "unsupported" diagnostic instead of an
// internal abort for ABI cases we can't represent (types wider than the
// 4-register return ABI, value types the calling convention never sees a
// legalized form of, condition codes normalizeCC can't map). These paths
// are unreachable from valid C today — the type legalizer and soft-float
// lowering normalize every user type into the i8/i16/i32 halves the ABI
// handles, and large aggregates go via sret — but a future frontend or an
// exotic input should get a proper `error:` with the function's location,
// never an LLVM-internal stack trace. Callers emit the diagnostic and then
// return a safe placeholder so the rest of the function finishes building;
// codegen still fails overall because the diagnostic is an error.
static void reportUnsupported(SelectionDAG &DAG, const SDLoc &DL,
const Twine &Msg) {
MachineFunction &MF = DAG.getMachineFunction();
DAG.getContext()->diagnose(
DiagnosticInfoUnsupported(MF.getFunction(), Msg, DL.getDebugLoc()));
}
// Loader-compat workaround: when set, LDAptr/STAptr/STBptr inserters // Loader-compat workaround: when set, LDAptr/STAptr/STBptr inserters
// load the bank byte from DP $BE (initialized by crt0 to PHK / current // load the bank byte from DP $BE (initialized by crt0 to PHK / current
// PBR) instead of forcing it to 0 via STZ $E2. This makes pointer // PBR) instead of forcing it to 0 via STZ $E2. This makes pointer
@ -777,7 +800,8 @@ SDValue W65816TargetLowering::LowerBR_CC(SDValue Op, SelectionDAG &DAG) const {
case ISD::SETUGT: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGT; break; case ISD::SETUGT: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGT; break;
case ISD::SETUGE: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGE; break; case ISD::SETUGE: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGE; break;
default: default:
report_fatal_error("W65816: unexpected i32 BR_CC condition"); reportUnsupported(DAG, DL, "unsupported i32 branch condition code");
return Chain; // safe placeholder; the diagnostic fails compilation
} }
SDValue HiOk = DAG.getSetCC(DL, MVT::i16, LH, RH, HiCC); SDValue HiOk = DAG.getSetCC(DL, MVT::i16, LH, RH, HiCC);
SDValue HiEq = DAG.getSetCC(DL, MVT::i16, LH, RH, ISD::SETEQ); SDValue HiEq = DAG.getSetCC(DL, MVT::i16, LH, RH, ISD::SETEQ);
@ -791,8 +815,10 @@ SDValue W65816TargetLowering::LowerBR_CC(SDValue Op, SelectionDAG &DAG) const {
} }
W65816CC::CondCode TCC = normalizeCC(LHS, RHS, CC, DAG, DL); W65816CC::CondCode TCC = normalizeCC(LHS, RHS, CC, DAG, DL);
if (TCC == W65816CC::COND_INVALID) if (TCC == W65816CC::COND_INVALID) {
report_fatal_error("W65816: branch condition not yet implemented"); reportUnsupported(DAG, DL, "unsupported branch condition code");
return Chain; // safe placeholder; the diagnostic fails compilation
}
// Multi-branch CCs only have inserter support via SELECT_CC16. For // Multi-branch CCs only have inserter support via SELECT_CC16. For
// BR_CC, reroute through SETCC: materialise the boolean to A, then // BR_CC, reroute through SETCC: materialise the boolean to A, then
@ -918,7 +944,8 @@ SDValue W65816TargetLowering::LowerSETCC(SDValue Op, SelectionDAG &DAG) const {
case ISD::SETUGT: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGT; break; case ISD::SETUGT: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGT; break;
case ISD::SETUGE: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGE; break; case ISD::SETUGE: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGE; break;
default: default:
report_fatal_error("W65816: unexpected i32 SETCC condition"); reportUnsupported(DAG, DL, "unsupported i32 comparison condition code");
return DAG.getUNDEF(VT); // safe placeholder
} }
SDValue HiOk = DAG.getSetCC(DL, VT, LH, RH, HiCC); SDValue HiOk = DAG.getSetCC(DL, VT, LH, RH, HiCC);
SDValue HiEq = DAG.getSetCC(DL, VT, LH, RH, ISD::SETEQ); SDValue HiEq = DAG.getSetCC(DL, VT, LH, RH, ISD::SETEQ);
@ -973,8 +1000,10 @@ SDValue W65816TargetLowering::LowerSELECT_CC(SDValue Op,
} }
W65816CC::CondCode TCC = normalizeCC(LHS, RHS, CC, DAG, DL); W65816CC::CondCode TCC = normalizeCC(LHS, RHS, CC, DAG, DL);
if (TCC == W65816CC::COND_INVALID) if (TCC == W65816CC::COND_INVALID) {
report_fatal_error("W65816: select_cc condition not yet implemented"); reportUnsupported(DAG, DL, "unsupported select condition code");
return DAG.getUNDEF(Op.getValueType()); // safe placeholder
}
SDValue Glue = DAG.getNode(W65816ISD::CMP, DL, MVT::Glue, LHS, RHS); SDValue Glue = DAG.getNode(W65816ISD::CMP, DL, MVT::Glue, LHS, RHS);
SDValue CCOp = DAG.getTargetConstant(TCC, DL, MVT::i8); SDValue CCOp = DAG.getTargetConstant(TCC, DL, MVT::i8);
@ -2029,8 +2058,14 @@ SDValue W65816TargetLowering::LowerFormalArguments(
unsigned StackOffset = 4; // Skip 3 ret-addr bytes; first slot at S+4. unsigned StackOffset = 4; // Skip 3 ret-addr bytes; first slot at S+4.
for (const ISD::InputArg &Arg : Ins) { for (const ISD::InputArg &Arg : Ins) {
MVT VT = Arg.VT; MVT VT = Arg.VT;
if (VT != MVT::i16 && VT != MVT::i8 && VT != MVT::i32) if (VT != MVT::i16 && VT != MVT::i8 && VT != MVT::i32) {
report_fatal_error("W65816: argument type not yet supported"); reportUnsupported(DAG, DL,
"unsupported argument type (only i8/i16/i32 halves "
"are representable in the W65816 ABI)");
InVals.push_back(DAG.getUNDEF(VT)); // keep InVals aligned with Ins
++ArgIdx;
continue;
}
if (ArgIdx == 0 && VT == MVT::i32) { if (ArgIdx == 0 && VT == MVT::i32) {
// Whole-i32 first arg: lo half live-in via $a, hi via $x. // Whole-i32 first arg: lo half live-in via $a, hi via $x.
@ -2159,8 +2194,13 @@ W65816TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
// Up to 4 return halves (i64 split): i8/i16 in A; i32 in A:X; // Up to 4 return halves (i64 split): i8/i16 in A; i32 in A:X;
// i64 in A:X:Y plus DP $F0..$F1 for the highest half. See // i64 in A:X:Y plus DP $F0..$F1 for the highest half. See
// LowerReturn comment for the ABI. // LowerReturn comment for the ABI.
if (Ins.size() > 4) if (Ins.size() > 4) {
report_fatal_error("W65816: return type wider than 64 bits not supported"); reportUnsupported(DAG, DL,
"return type wider than 64 bits is not supported");
for (const ISD::InputArg &I : Ins)
InVals.push_back(DAG.getUNDEF(I.VT)); // keep InVals aligned with Ins
return Chain;
}
// Indirect calls (function pointers): redirect through the runtime // Indirect calls (function pointers): redirect through the runtime
// trampoline `__jsl_indir`. The 65816 has no JSL-indirect; instead, // trampoline `__jsl_indir`. The 65816 has no JSL-indirect; instead,
@ -2187,8 +2227,14 @@ W65816TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
} }
for (const ISD::OutputArg &O : Outs) { for (const ISD::OutputArg &O : Outs) {
if (O.VT != MVT::i16 && O.VT != MVT::i8 && O.VT != MVT::i32) if (O.VT != MVT::i16 && O.VT != MVT::i8 && O.VT != MVT::i32) {
report_fatal_error("W65816: argument type not yet supported"); reportUnsupported(DAG, DL,
"unsupported argument type (only i8/i16/i32 halves "
"are representable in the W65816 ABI)");
for (const ISD::InputArg &I : Ins)
InVals.push_back(DAG.getUNDEF(I.VT)); // keep InVals aligned with Ins
return Chain;
}
} }
// i32 first-arg ABI. Two flavors: // i32 first-arg ABI. Two flavors:
@ -2343,6 +2389,7 @@ W65816TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
// walk it, copying from A, X, Y, DPF0 in order. Re-assemble i32 // walk it, copying from A, X, Y, DPF0 in order. Re-assemble i32
// halves into a Wide32 SDValue at the end. // halves into a Wide32 SDValue at the end.
SmallVector<MVT, 4> ExpVT; SmallVector<MVT, 4> ExpVT;
bool BadRet = false;
for (unsigned i = 0, e = Ins.size(); i != e; ++i) { for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
MVT VT = Ins[i].VT; MVT VT = Ins[i].VT;
if (VT == MVT::i32) { if (VT == MVT::i32) {
@ -2351,11 +2398,22 @@ W65816TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
} else if (VT == MVT::i16 || VT == MVT::i8) { } else if (VT == MVT::i16 || VT == MVT::i8) {
ExpVT.push_back(VT); ExpVT.push_back(VT);
} else { } else {
report_fatal_error("W65816: return half must be i8/i16/i32"); reportUnsupported(DAG, DL,
"unsupported return value type (only i8/i16/i32 "
"halves are representable in the W65816 ABI)");
BadRet = true;
} }
} }
if (ExpVT.size() > 4) if (!BadRet && ExpVT.size() > 4) {
report_fatal_error("W65816: return type wider than 64 bits not supported"); reportUnsupported(DAG, DL,
"return type wider than 64 bits is not supported");
BadRet = true;
}
if (BadRet) {
for (const ISD::InputArg &I : Ins)
InVals.push_back(DAG.getUNDEF(I.VT)); // keep InVals aligned with Ins
return Chain;
}
static constexpr Register RetRegs[4] = {W65816::A, W65816::X, W65816::Y, static constexpr Register RetRegs[4] = {W65816::A, W65816::X, W65816::Y,
W65816::DPF0}; W65816::DPF0};
SmallVector<SDValue, 4> Halves; SmallVector<SDValue, 4> Halves;
@ -2414,11 +2472,17 @@ SDValue W65816TargetLowering::LowerReturn(
ExpVT.push_back(VT); ExpVT.push_back(VT);
ExpVals.push_back(OutVals[i]); ExpVals.push_back(OutVals[i]);
} else { } else {
report_fatal_error("W65816: return half must be i8/i16/i32"); reportUnsupported(DAG, DL,
"unsupported return value type (only i8/i16/i32 "
"halves are representable in the W65816 ABI)");
return DAG.getNode(W65816ISD::RET_GLUE, DL, MVT::Other, Chain);
} }
} }
if (ExpVT.size() > 4) if (ExpVT.size() > 4) {
report_fatal_error("W65816: return type wider than 64 bits not supported"); reportUnsupported(DAG, DL,
"return type wider than 64 bits is not supported");
return DAG.getNode(W65816ISD::RET_GLUE, DL, MVT::Other, Chain);
}
// Single whole-i32 return: copy directly to AX32 instead of two // Single whole-i32 return: copy directly to AX32 instead of two
// halves to A and X. Saves the regalloc/coalescer some work. // halves to A and X. Saves the regalloc/coalescer some work.

View file

@ -581,8 +581,11 @@ def ASRA16 : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
} }
// Shifts by small constants unroll into 2-4 single-bit shifts. // Shifts by small constants unroll into 2-4 single-bit shifts.
// Anything beyond 4 bits would benefit from a loop or a XBA-and-mask // Counts >= 8 use the XBA-and-mask trick via the SHL8A/SRL8A pseudos
// trick; left for a future peephole. // below, and counts 9..15 chain ASL/LSR after that pseudo (see the
// SHL8A / SHL15A definitions and the 9..14 Pat blocks); counts 5..7
// fall out of the generic combiner as ASL/LSR chains. So every
// constant shift 0..15 lowers inline (no __ashl/__lshr libcall).
def : Pat<(shl Acc16:$src, (i16 2)), (ASLA16 (ASLA16 Acc16:$src))>; def : Pat<(shl Acc16:$src, (i16 2)), (ASLA16 (ASLA16 Acc16:$src))>;
def : Pat<(shl Acc16:$src, (i16 3)), def : Pat<(shl Acc16:$src, (i16 3)),
(ASLA16 (ASLA16 (ASLA16 Acc16:$src)))>; (ASLA16 (ASLA16 (ASLA16 Acc16:$src)))>;
@ -1762,6 +1765,7 @@ def JML_Long : InstAbsLong<0x5C, "jml">;
//---------------------------------------------------------------- Calls //---------------------------------------------------------------- Calls
let isCall = 1, mayLoad = 0, mayStore = 0 in { let isCall = 1, mayLoad = 0, mayStore = 0 in {
def JSR_Abs : InstAbs<0x20, "jsr">; def JSR_Abs : InstAbs<0x20, "jsr">;
def JSR_AbsIndX : InstAbsIndX<0xFC, "jsr">;
def JSL_Long : InstAbsLong<0x22, "jsl">; def JSL_Long : InstAbsLong<0x22, "jsl">;
} }

View file

@ -7,8 +7,9 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// //
// This file contains code to lower W65816 MachineInstrs to their // This file contains code to lower W65816 MachineInstrs to their
// corresponding MCInst records. The skeleton follows the MSP430 pattern // corresponding MCInst records, following the MSP430 pattern. It handles
// closely but does not yet understand any target-specific operand flags. // register, immediate, global/external-symbol, block-address, jump-table,
// and constant-pool operands (see LowerSymbolOperand).
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//