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
|
literally (some 65816 addressing modes still have gaps in the
|
||||||
assembler). Hand-encoding is a stopgap; report opcodes that need it.
|
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
|
## 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; }
|
unsigned char mmioLoad(void) { return *(volatile unsigned char *)0xC035; }
|
||||||
EOF
|
EOF
|
||||||
"$CLANG" --target=w65816 -O2 -S "$cFileC" -o "$sFileC"
|
"$CLANG" --target=w65816 -O2 -S "$cFileC" -o "$sFileC"
|
||||||
# Must contain `sta 0xc035` AND `lda 0xc035` (long form chosen by addr).
|
# Must contain `sta >0xc035` AND `lda >0xc035` (long form chosen by
|
||||||
# Must NOT contain `sta [` or `lda [` (the old [dp],Y route).
|
# addr; the printer's `>` keeps a long under 0x10000 long when the text
|
||||||
if ! grep -qE 'sta 0xc035' "$sFileC" \
|
# is assembled again). Must NOT contain `sta [` or `lda [` (the old
|
||||||
|| ! grep -qE 'lda 0xc035' "$sFileC" \
|
# [dp],Y route).
|
||||||
|
if ! grep -qE 'sta >0xc035' "$sFileC" \
|
||||||
|
|| ! grep -qE 'lda >0xc035' "$sFileC" \
|
||||||
|| grep -qE '(sta|lda) \[' "$sFileC"; then
|
|| grep -qE '(sta|lda) \[' "$sFileC"; then
|
||||||
cat "$sFileC" >&2
|
cat "$sFileC" >&2
|
||||||
die "i8 const-addr store/load: expected STA8long/LDA8long, got [dp],Y route"
|
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.
|
// narrowed to STA_DP/STA_Abs by value fit.
|
||||||
bool ForceLong = false;
|
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:
|
public:
|
||||||
W65816Operand(StringRef T, SMLoc S)
|
W65816Operand(StringRef T, SMLoc S)
|
||||||
: Kind(k_Tok), Tok({T.data(), (unsigned)T.size()}), Start(S),
|
: Kind(k_Tok), Tok({T.data(), (unsigned)T.size()}), Start(S),
|
||||||
|
|
@ -107,8 +114,8 @@ public:
|
||||||
W65816Operand(unsigned R, SMLoc S, SMLoc E)
|
W65816Operand(unsigned R, SMLoc S, SMLoc E)
|
||||||
: Kind(k_Reg), RegNum(R), Start(S), End(E) {}
|
: Kind(k_Reg), RegNum(R), Start(S), End(E) {}
|
||||||
W65816Operand(KindTy K, const MCExpr *E, SMLoc S, SMLoc E2,
|
W65816Operand(KindTy K, const MCExpr *E, SMLoc S, SMLoc E2,
|
||||||
bool ForceLong = false)
|
bool ForceLong = false, bool ForceAbs = false)
|
||||||
: Kind(K), Start(S), End(E2), ForceLong(ForceLong) {
|
: Kind(K), Start(S), End(E2), ForceLong(ForceLong), ForceAbs(ForceAbs) {
|
||||||
if (K == k_Imm)
|
if (K == k_Imm)
|
||||||
Imm = E;
|
Imm = E;
|
||||||
else
|
else
|
||||||
|
|
@ -133,8 +140,10 @@ public:
|
||||||
|
|
||||||
static std::unique_ptr<W65816Operand> createAddr(const MCExpr *Val, SMLoc S,
|
static std::unique_ptr<W65816Operand> createAddr(const MCExpr *Val, SMLoc S,
|
||||||
SMLoc E,
|
SMLoc E,
|
||||||
bool ForceLong = false) {
|
bool ForceLong = false,
|
||||||
return std::make_unique<W65816Operand>(k_Addr, Val, S, E, ForceLong);
|
bool ForceAbs = false) {
|
||||||
|
return std::make_unique<W65816Operand>(k_Addr, Val, S, E, ForceLong,
|
||||||
|
ForceAbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MCParsedAsmOperand hooks ------------------------------------------
|
// --- MCParsedAsmOperand hooks ------------------------------------------
|
||||||
|
|
@ -165,7 +174,8 @@ public:
|
||||||
// for absolute addresses and PC-relative branches, falling through
|
// for absolute addresses and PC-relative branches, falling through
|
||||||
// narrower variants only when the value is a constant that fits.
|
// narrower variants only when the value is a constant that fits.
|
||||||
// 24-bit `long` and `pcrel16` are opt-in (only constants in the wider
|
// 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) {
|
static bool constFitsUnsigned(const MCExpr *Expr, unsigned width) {
|
||||||
if (const auto *CE = dyn_cast<MCConstantExpr>(Expr)) {
|
if (const auto *CE = dyn_cast<MCConstantExpr>(Expr)) {
|
||||||
|
|
@ -198,7 +208,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAddrDP() const {
|
bool isAddrDP() const {
|
||||||
return Kind == k_Addr && !ForceLong &&
|
return Kind == k_Addr && !ForceLong && !ForceAbs &&
|
||||||
isConstant(Addr) && constFitsUnsigned(Addr, 8);
|
isConstant(Addr) && constFitsUnsigned(Addr, 8);
|
||||||
}
|
}
|
||||||
bool isAddrAbs() const {
|
bool isAddrAbs() const {
|
||||||
|
|
@ -206,7 +216,7 @@ public:
|
||||||
(!isConstant(Addr) || constFitsUnsigned(Addr, 16));
|
(!isConstant(Addr) || constFitsUnsigned(Addr, 16));
|
||||||
}
|
}
|
||||||
bool isAddrLong() const {
|
bool isAddrLong() const {
|
||||||
if (Kind != k_Addr)
|
if (Kind != k_Addr || ForceAbs)
|
||||||
return false;
|
return false;
|
||||||
// Explicit `>expr` prefix forces the long form for any value that fits
|
// Explicit `>expr` prefix forces the long form for any value that fits
|
||||||
// in 24 bits (or any symbol), and the narrower AddrDP/AddrAbs predicates
|
// in 24 bits (or any symbol), and the narrower AddrDP/AddrAbs predicates
|
||||||
|
|
@ -617,6 +627,16 @@ bool W65816AsmParser::parseOperand(OperandVector &Operands) {
|
||||||
ForceLong = true;
|
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
|
// Anything else is an address / branch-target / block-move byte. We
|
||||||
// rely on the standard LLVM asm expression parser, which accepts
|
// rely on the standard LLVM asm expression parser, which accepts
|
||||||
// identifiers (labels), decimal integers, hex ($... via MCAsmInfo
|
// identifiers (labels), decimal integers, hex ($... via MCAsmInfo
|
||||||
|
|
@ -626,7 +646,10 @@ bool W65816AsmParser::parseOperand(OperandVector &Operands) {
|
||||||
if (Parser.parseExpression(Val))
|
if (Parser.parseExpression(Val))
|
||||||
return Error(S, "expected expression");
|
return Error(S, "expected expression");
|
||||||
SMLoc E = getLexer().getLoc();
|
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;
|
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 stock LLVM expression lexer) can round-trip our output. Teaching
|
||||||
// the lexer about `$` is a future cleanup; until then the printed form
|
// the lexer about `$` is a future cleanup; until then the printed form
|
||||||
// is compatible-with-itself rather than idiomatic WDC syntax.
|
// 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,
|
static void printAddrImm(const MCOperand &Op, unsigned width, raw_ostream &O,
|
||||||
const MCAsmInfo &MAI) {
|
const MCAsmInfo &MAI) {
|
||||||
if (Op.isImm()) {
|
if (Op.isImm()) {
|
||||||
uint64_t mask = width >= 64 ? ~uint64_t(0) : ((uint64_t(1) << width) - 1);
|
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 << "0x";
|
||||||
O.write_hex(Op.getImm() & mask);
|
O.write_hex(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(Op.isExpr() && "unknown address operand kind");
|
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);
|
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,
|
void W65816InstPrinter::printAddrLong(const MCInst *MI, unsigned OpNo,
|
||||||
raw_ostream &O) {
|
raw_ostream &O) {
|
||||||
printAddrImm(MI->getOperand(OpNo), 24, O, MAI);
|
printAddrImm(MI->getOperand(OpNo), 24, O, MAI);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ private:
|
||||||
void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O);
|
void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O);
|
||||||
void printAddrDP(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 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 printAddrLong(const MCInst *MI, unsigned OpNo, raw_ostream &O);
|
||||||
void printPCRel8(const MCInst *MI, uint64_t Address, unsigned OpNo,
|
void printPCRel8(const MCInst *MI, uint64_t Address, unsigned OpNo,
|
||||||
raw_ostream &O);
|
raw_ostream &O);
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,18 @@ def addrAbs : Operand<i16> {
|
||||||
let DecoderMethod = "decodeAddr16";
|
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> {
|
def addrLong : Operand<i32> {
|
||||||
let ParserMatchClass = W65816AddrOp<"AddrLong">;
|
let ParserMatchClass = W65816AddrOp<"AddrLong">;
|
||||||
let OperandType = "OPERAND_MEMORY";
|
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
|
// Used by the indirect-call trampoline in crt0/libgcc to dispatch
|
||||||
// through a function-pointer slot.
|
// through a function-pointer slot.
|
||||||
class InstAbsInd<bits<8> op, string mnem>
|
class InstAbsInd<bits<8> op, string mnem>
|
||||||
: W65816Inst<(outs), (ins addrAbs:$addr),
|
: W65816Inst<(outs), (ins addrAbsInd:$addr),
|
||||||
!strconcat(mnem, "\t(${addr})")> {
|
!strconcat(mnem, "\t(${addr})")> {
|
||||||
let Size = 3;
|
let Size = 3;
|
||||||
bits<16> addr;
|
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
|
// jumps there. Useful for jump tables when the base address is known
|
||||||
// at assembly time.
|
// at assembly time.
|
||||||
class InstAbsIndX<bits<8> op, string mnem>
|
class InstAbsIndX<bits<8> op, string mnem>
|
||||||
: W65816Inst<(outs), (ins addrAbs:$addr),
|
: W65816Inst<(outs), (ins addrAbsInd:$addr),
|
||||||
!strconcat(mnem, "\t(${addr}, x)")> {
|
!strconcat(mnem, "\t(${addr}, x)")> {
|
||||||
let Size = 3;
|
let Size = 3;
|
||||||
bits<16> addr;
|
bits<16> addr;
|
||||||
|
|
@ -372,7 +384,7 @@ class InstAbsIndX<bits<8> op, string mnem>
|
||||||
// offset address. Square brackets distinguish from the 16-bit
|
// offset address. Square brackets distinguish from the 16-bit
|
||||||
// indirect form.
|
// indirect form.
|
||||||
class InstAbsIndLong<bits<8> op, string mnem>
|
class InstAbsIndLong<bits<8> op, string mnem>
|
||||||
: W65816Inst<(outs), (ins addrAbs:$addr),
|
: W65816Inst<(outs), (ins addrAbsInd:$addr),
|
||||||
!strconcat(mnem, "\t[${addr}]")> {
|
!strconcat(mnem, "\t[${addr}]")> {
|
||||||
let Size = 3;
|
let Size = 3;
|
||||||
bits<16> addr;
|
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