diff --git a/docs/USAGE.md b/docs/USAGE.md index a5a01dc..7909378 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -935,6 +935,32 @@ The `.byte` form is needed when llvm-mc can't yet parse an opcode literally (some 65816 addressing modes still have gaps in the assembler). Hand-encoding is a stopgap; report opcodes that need it. +### Operand widths (inline asm and `.s` files) + +The 65816 encodes an operand's width in the opcode, and the assembler +picks the narrowest form a constant fits: + +| You write | You get | Addresses | +|---|---|---| +| `sta 0x12` / `sta 0x12, x` | direct page (2 bytes) | bank 0, through D | +| `sta 0x1234` / `sta 0x1234, x` | absolute (3 bytes) | the data bank (DBR) | +| `sta 0x012000` | long (4 bytes) | that bank | +| `sta \|0x12, x` | absolute, forced | the data bank (DBR) | +| `sta >0x12` | long, forced | bank 0 | + +So a bare `sta 0, x` with X holding an address is a direct-page store +into bank 0, not the DBR-relative absolute store it may look like; +write `sta |0, x`. Symbols (labels, globals) always take the absolute +form unless prefixed. `llc -S` and `llvm-mc` print the prefixes back +where a constant would otherwise narrow, so their text assembles to the +same bytes. (`lda`/`sta n, y` has no direct-page form and is always +absolute.) + +Immediates follow the assembler's idea of the M and X flags, not the +`sep`/`rep` instructions before them: after `sep #0x20`, write `.a8` +(and `.a16` after `rep #0x20`; `.i8`/`.i16` for X) or `lda #0` still +assembles as a 3-byte immediate. + --- ## Tools reference diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh index b884880..9fb78f7 100755 --- a/scripts/smokeTest.sh +++ b/scripts/smokeTest.sh @@ -547,10 +547,12 @@ void mmioStore(unsigned char v) { *(volatile unsigned char *)0xC035 = v; } unsigned char mmioLoad(void) { return *(volatile unsigned char *)0xC035; } EOF "$CLANG" --target=w65816 -O2 -S "$cFileC" -o "$sFileC" - # Must contain `sta 0xc035` AND `lda 0xc035` (long form chosen by addr). - # Must NOT contain `sta [` or `lda [` (the old [dp],Y route). - if ! grep -qE 'sta 0xc035' "$sFileC" \ - || ! grep -qE 'lda 0xc035' "$sFileC" \ + # Must contain `sta >0xc035` AND `lda >0xc035` (long form chosen by + # addr; the printer's `>` keeps a long under 0x10000 long when the text + # is assembled again). Must NOT contain `sta [` or `lda [` (the old + # [dp],Y route). + if ! grep -qE 'sta >0xc035' "$sFileC" \ + || ! grep -qE 'lda >0xc035' "$sFileC" \ || grep -qE '(sta|lda) \[' "$sFileC"; then cat "$sFileC" >&2 die "i8 const-addr store/load: expected STA8long/LDA8long, got [dp],Y route" diff --git a/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp b/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp index e664425..c9e9f4c 100644 --- a/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp +++ b/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp @@ -100,6 +100,13 @@ class W65816Operand : public MCParsedAsmOperand { // narrowed to STA_DP/STA_Abs by value fit. bool ForceLong = false; + // Set only for k_Addr operands carrying an explicit force-absolute prefix + // (`|expr`, ORCA/M's and WDC's spelling). When true the operand matches + // ONLY the 16-bit absolute class (AddrAbs), so `sta |0,x` reaches + // STA_AbsX (0x9D), DBR-relative, instead of being narrowed by value fit + // to STA_DPX (0x95), which addresses bank 0 through D. + bool ForceAbs = false; + public: W65816Operand(StringRef T, SMLoc S) : Kind(k_Tok), Tok({T.data(), (unsigned)T.size()}), Start(S), @@ -107,8 +114,8 @@ public: W65816Operand(unsigned R, SMLoc S, SMLoc E) : Kind(k_Reg), RegNum(R), Start(S), End(E) {} W65816Operand(KindTy K, const MCExpr *E, SMLoc S, SMLoc E2, - bool ForceLong = false) - : Kind(K), Start(S), End(E2), ForceLong(ForceLong) { + bool ForceLong = false, bool ForceAbs = false) + : Kind(K), Start(S), End(E2), ForceLong(ForceLong), ForceAbs(ForceAbs) { if (K == k_Imm) Imm = E; else @@ -133,8 +140,10 @@ public: static std::unique_ptr createAddr(const MCExpr *Val, SMLoc S, SMLoc E, - bool ForceLong = false) { - return std::make_unique(k_Addr, Val, S, E, ForceLong); + bool ForceLong = false, + bool ForceAbs = false) { + return std::make_unique(k_Addr, Val, S, E, ForceLong, + ForceAbs); } // --- MCParsedAsmOperand hooks ------------------------------------------ @@ -165,7 +174,8 @@ public: // for absolute addresses and PC-relative branches, falling through // narrower variants only when the value is a constant that fits. // 24-bit `long` and `pcrel16` are opt-in (only constants in the wider - // range, or future explicit addressing-mode syntax). + // range, or the `>` prefix); the `|` prefix keeps a small constant on the + // 16-bit absolute form. static bool constFitsUnsigned(const MCExpr *Expr, unsigned width) { if (const auto *CE = dyn_cast(Expr)) { @@ -198,7 +208,7 @@ public: } bool isAddrDP() const { - return Kind == k_Addr && !ForceLong && + return Kind == k_Addr && !ForceLong && !ForceAbs && isConstant(Addr) && constFitsUnsigned(Addr, 8); } bool isAddrAbs() const { @@ -206,7 +216,7 @@ public: (!isConstant(Addr) || constFitsUnsigned(Addr, 16)); } bool isAddrLong() const { - if (Kind != k_Addr) + if (Kind != k_Addr || ForceAbs) return false; // Explicit `>expr` prefix forces the long form for any value that fits // in 24 bits (or any symbol), and the narrower AddrDP/AddrAbs predicates @@ -617,6 +627,16 @@ bool W65816AsmParser::parseOperand(OperandVector &Operands) { ForceLong = true; } + // Force-absolute prefix: `|expr` selects the 16-bit absolute form even + // when the value fits in a byte, where the matcher would otherwise narrow + // to direct page -- a different bank (DBR vs 0) for the indexed forms. + // ('|' cannot begin an expression either.) + bool ForceAbs = false; + if (!ForceLong && getLexer().is(AsmToken::Pipe)) { + Parser.Lex(); // eat '|' + ForceAbs = true; + } + // Anything else is an address / branch-target / block-move byte. We // rely on the standard LLVM asm expression parser, which accepts // identifiers (labels), decimal integers, hex ($... via MCAsmInfo @@ -626,7 +646,10 @@ bool W65816AsmParser::parseOperand(OperandVector &Operands) { if (Parser.parseExpression(Val)) return Error(S, "expected expression"); SMLoc E = getLexer().getLoc(); - Operands.push_back(W65816Operand::createAddr(Val, S, E, ForceLong)); + if (ForceAbs && isa(Val) && + !W65816Operand::constFitsUnsigned(Val, 16)) + return Error(S, "absolute address does not fit in 16 bits"); + Operands.push_back(W65816Operand::createAddr(Val, S, E, ForceLong, ForceAbs)); return false; } diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp index 1984ab5..5db2b51 100644 --- a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp @@ -67,12 +67,22 @@ void W65816InstPrinter::printOperand(const MCInst *MI, unsigned OpNo, // the stock LLVM expression lexer) can round-trip our output. Teaching // the lexer about `$` is a future cleanup; until then the printed form // is compatible-with-itself rather than idiomatic WDC syntax. +// +// A constant that would fit a narrower form carries the prefix that keeps +// this one when the text is assembled again: `|` (absolute) below 0x100, +// `>` (long) below 0x10000. The parser narrows a bare constant by value, +// and the narrower forms address another bank (dp: bank 0 through D). static void printAddrImm(const MCOperand &Op, unsigned width, raw_ostream &O, const MCAsmInfo &MAI) { if (Op.isImm()) { uint64_t mask = width >= 64 ? ~uint64_t(0) : ((uint64_t(1) << width) - 1); + uint64_t value = Op.getImm() & mask; + if (width == 16 && value < 0x100) + O << "|"; + else if (width == 24 && value < 0x10000) + O << ">"; O << "0x"; - O.write_hex(Op.getImm() & mask); + O.write_hex(value); return; } assert(Op.isExpr() && "unknown address operand kind"); @@ -89,6 +99,17 @@ void W65816InstPrinter::printAddrAbs(const MCInst *MI, unsigned OpNo, printAddrImm(MI->getOperand(OpNo), 16, O, MAI); } +void W65816InstPrinter::printAddrAbsInd(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + const MCOperand &Op = MI->getOperand(OpNo); + if (Op.isImm()) { + O << "0x"; + O.write_hex(Op.getImm() & 0xffff); + return; + } + printAddrImm(Op, 16, O, MAI); +} + void W65816InstPrinter::printAddrLong(const MCInst *MI, unsigned OpNo, raw_ostream &O) { printAddrImm(MI->getOperand(OpNo), 24, O, MAI); diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h index d296013..9bca9e3 100644 --- a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h @@ -40,6 +40,7 @@ private: void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O); void printAddrDP(const MCInst *MI, unsigned OpNo, raw_ostream &O); void printAddrAbs(const MCInst *MI, unsigned OpNo, raw_ostream &O); + void printAddrAbsInd(const MCInst *MI, unsigned OpNo, raw_ostream &O); void printAddrLong(const MCInst *MI, unsigned OpNo, raw_ostream &O); void printPCRel8(const MCInst *MI, uint64_t Address, unsigned OpNo, raw_ostream &O); diff --git a/src/llvm/lib/Target/W65816/W65816InstrFormats.td b/src/llvm/lib/Target/W65816/W65816InstrFormats.td index 1418853..2873eed 100644 --- a/src/llvm/lib/Target/W65816/W65816InstrFormats.td +++ b/src/llvm/lib/Target/W65816/W65816InstrFormats.td @@ -129,6 +129,18 @@ def addrAbs : Operand { let DecoderMethod = "decodeAddr16"; } +// addrAbs in an indirect jump (JMP/JSR (abs), (abs, x), [abs]): parsed +// the same, but no narrower form of these exists, so it prints bare, +// without the `|` printAddrAbs keeps a small constant absolute with. +def addrAbsInd : Operand { + let ParserMatchClass = W65816AddrOp<"AddrAbs">; + let OperandType = "OPERAND_MEMORY"; + let Type = i16; + let PrintMethod = "printAddrAbsInd"; + let EncoderMethod = "encodeAddr16"; + let DecoderMethod = "decodeAddr16"; +} + def addrLong : Operand { let ParserMatchClass = W65816AddrOp<"AddrLong">; let OperandType = "OPERAND_MEMORY"; @@ -344,7 +356,7 @@ class InstDPIndX op, string mnem> // Used by the indirect-call trampoline in crt0/libgcc to dispatch // through a function-pointer slot. class InstAbsInd op, string mnem> - : W65816Inst<(outs), (ins addrAbs:$addr), + : W65816Inst<(outs), (ins addrAbsInd:$addr), !strconcat(mnem, "\t(${addr})")> { let Size = 3; bits<16> addr; @@ -358,7 +370,7 @@ class InstAbsInd op, string mnem> // jumps there. Useful for jump tables when the base address is known // at assembly time. class InstAbsIndX op, string mnem> - : W65816Inst<(outs), (ins addrAbs:$addr), + : W65816Inst<(outs), (ins addrAbsInd:$addr), !strconcat(mnem, "\t(${addr}, x)")> { let Size = 3; bits<16> addr; @@ -372,7 +384,7 @@ class InstAbsIndX op, string mnem> // offset address. Square brackets distinguish from the 16-bit // indirect form. class InstAbsIndLong op, string mnem> - : W65816Inst<(outs), (ins addrAbs:$addr), + : W65816Inst<(outs), (ins addrAbsInd:$addr), !strconcat(mnem, "\t[${addr}]")> { let Size = 3; bits<16> addr; diff --git a/src/llvm/test/CodeGen/W65816/asm-force-abs.s b/src/llvm/test/CodeGen/W65816/asm-force-abs.s new file mode 100644 index 0000000..783775e --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/asm-force-abs.s @@ -0,0 +1,33 @@ +; The `|` prefix keeps a constant that fits in a byte on the 16-bit +; absolute form: `sta |0,x` is STA abs,X (0x9D), DBR-relative, where a bare +; `sta 0,x` narrows to STA dp,X (0x95), bank 0 through D. Without it no +; abs,X under 0x100 could be written: JoeyLib's iigsIndexed.s, X a +; surface's address, stored to bank 0 instead of the surface's bank. The +; printer puts the prefixes back (`|` on an absolute constant under 0x100, +; `>` on a long one under 0x10000), so its text assembles to the same +; bytes again. +; +; RUN: llvm-mc -triple=w65816 -show-encoding %s | FileCheck %s +; RUN: llvm-mc -triple=w65816 %s | llvm-mc -triple=w65816 -show-encoding | FileCheck %s +; RUN: not llvm-mc -triple=w65816 --defsym=TOOBIG=1 %s 2>&1 | FileCheck --check-prefix=ERR %s + + .text + rep #0x30 + .a16 + .i16 +; CHECK: sta |0x0, x {{.*}} encoding: [0x9d,0x00,0x00] + sta |0,x +; CHECK: lda |0x0, x {{.*}} encoding: [0xbd,0x00,0x00] + lda |0,x +; CHECK: sta 0x0, x {{.*}} encoding: [0x95,0x00] + sta 0,x +; CHECK: lda |0x10 {{.*}} encoding: [0xad,0x10,0x00] + lda |0x10 +; CHECK: sta 0x1234, y {{.*}} encoding: [0x99,0x34,0x12] + sta |0x1234,y +; CHECK: sta >0x71 {{.*}} encoding: [0x8f,0x71,0x00,0x00] + sta >0x71 +.ifdef TOOBIG +; ERR: error: absolute address does not fit in 16 bits + sta |0x12345 +.endif