More fixes.
This commit is contained in:
parent
d626c54e92
commit
a6dc3eb147
7 changed files with 134 additions and 16 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<W65816Operand> createAddr(const MCExpr *Val, SMLoc S,
|
||||
SMLoc E,
|
||||
bool ForceLong = false) {
|
||||
return std::make_unique<W65816Operand>(k_Addr, Val, S, E, ForceLong);
|
||||
bool ForceLong = false,
|
||||
bool ForceAbs = false) {
|
||||
return std::make_unique<W65816Operand>(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<MCConstantExpr>(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<MCConstantExpr>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -129,6 +129,18 @@ def addrAbs : Operand<i16> {
|
|||
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<i16> {
|
||||
let ParserMatchClass = W65816AddrOp<"AddrAbs">;
|
||||
let OperandType = "OPERAND_MEMORY";
|
||||
let Type = i16;
|
||||
let PrintMethod = "printAddrAbsInd";
|
||||
let EncoderMethod = "encodeAddr16";
|
||||
let DecoderMethod = "decodeAddr16";
|
||||
}
|
||||
|
||||
def addrLong : Operand<i32> {
|
||||
let ParserMatchClass = W65816AddrOp<"AddrLong">;
|
||||
let OperandType = "OPERAND_MEMORY";
|
||||
|
|
@ -344,7 +356,7 @@ class InstDPIndX<bits<8> op, string mnem>
|
|||
// Used by the indirect-call trampoline in crt0/libgcc to dispatch
|
||||
// through a function-pointer slot.
|
||||
class InstAbsInd<bits<8> 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<bits<8> op, string mnem>
|
|||
// jumps there. Useful for jump tables when the base address is known
|
||||
// at assembly time.
|
||||
class InstAbsIndX<bits<8> 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<bits<8> op, string mnem>
|
|||
// offset address. Square brackets distinguish from the 16-bit
|
||||
// indirect form.
|
||||
class InstAbsIndLong<bits<8> op, string mnem>
|
||||
: W65816Inst<(outs), (ins addrAbs:$addr),
|
||||
: W65816Inst<(outs), (ins addrAbsInd:$addr),
|
||||
!strconcat(mnem, "\t[${addr}]")> {
|
||||
let Size = 3;
|
||||
bits<16> addr;
|
||||
|
|
|
|||
33
src/llvm/test/CodeGen/W65816/asm-force-abs.s
Normal file
33
src/llvm/test/CodeGen/W65816/asm-force-abs.s
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue