asar 1.91 Quirks
Surprises in how asar 1.91 picks an instruction's encoding. Future contributors: read this before spending an hour debugging a byte offset.
The short version: asar does not track the M and X flags, and it picks an operand's width
from how the operand is written. The project's style rule (STYLE.md) is the fix for all of it:
every data instruction whose operand is a define or struct field says .b, .w or .l. With
that, every encoding in the matched code is written as an instruction; no opcode bytes are
hand-written (db $A9,$7F), and the few encodings that could not be written as an instruction
would get a named macro in asm/include/macros.inc (it is empty so far).
Earlier versions of this page recommended raw db bytes, raw BRL offsets and raw hex operands.
Those workarounds were wrong or no longer needed (the code repo's STATUS.md records two of them
under "Known issues"); the notes below are what the current source does.
1. A label or equate operand assembles as a long address
Symptom: STA INIDISP, where INIDISP = $2100 is an equate (as in asm/hardware.inc), emits
opcode 8F (STA long, 4 bytes) instead of 8D (STA absolute, 3 bytes). Everything after it
shifts by a byte.
Cause: asar treats a label or equate as a 24-bit address.
Fix: give the width: STA.w INIDISP (8D 00 21). The same goes for STX/STY, which have
no long form: plain STX A1T7L is an error ("Unknown command"), while STX.w A1T7L assembles to
8E 72 43. The matched code writes, for example, STX.w A1T7L and STY.w VMADDL.
Defines (!Name = $2100) are textual, so STA !Name takes the width of the number as written
($2100: absolute). The style rule asks for an explicit width on those too, so the encoding
cannot drift when a define's value is written differently.
2. Immediate width follows the digits, not the M flag
Symptom: in a 16-bit accumulator section (after REP #$20), LDA #$7F assembles to the
2-byte A9 7F, and the CPU then reads the next byte as the operand's high byte.
Cause: asar doesn't know that M=0. It sizes an immediate by how it is written: #$7F is one
byte, #$007F or #$0040 two (SBC #$0040 gives E9 40 00 whatever M is).
Fix: write the width the CPU will use: LDA.w #$007F in 16-bit code, LDA.b #!Bank7F in
8-bit code. The battle number formatters, entered with M=0 by their callers, carry an explicit
.w on every SBC/ADC immediate for this reason. (An earlier note blamed asar for "not tracking
REP #$20" in SBC/ADC and fixed it with db bytes; the .w suffix alone reproduces those
encodings.)
3. Direct page or absolute is chosen the same way
STA $01,X assembles as direct page, X (95 01) and STA $0001,X as absolute, X (9D 01 00),
again by the digits written. With named operands, use .b for direct page and .w for absolute;
code that relocates the direct page onto a register block writes the register relative to its
base, as in STA.b INIDISP-!DP_PPU.
4. BRL with a number stores the number as the offset
BRL SomeLabel works: asar computes the 16-bit offset from the next instruction. But
BRL $B193 stores $B193 itself as the offset, not as a target address. Write the label.
When the original's offset wraps around the 64 KB bank boundary, the matched code writes the
target minus one bank, BRL Target-!BankWrap (!BankWrap = $10000 in
asm/include/constants_engine.inc), so asar computes the same wrapped offset; Field_EndOfFrame's
tail jump to Oam_BuildShadow is one.
5. Calls to code that is not matched yet
A JSR, JSL or JML to an address outside the matched code needs a name. The project gives
each such target a label-only stub in asm/include/unmatched.asm or unmatched_battle.asm: an
org to the address and a label, emitting no bytes. When the routine is matched its stub is
deleted and the real label takes over. Bank files never hold stubs, and call sites never use raw
addresses.
Quick reference
| Situation | Wrong | Right |
|---|---|---|
| Store to a named register | STA INIDISP (long) |
STA.w INIDISP |
STX/STY to a named register |
STX A1T7L (error) |
STX.w A1T7L |
| 16-bit immediate | LDA #$7F (1-byte operand) |
LDA.w #$007F |
| 8-bit immediate from a define | LDA #!Name |
LDA.b #!Name |
| Long branch | BRL $B193 (raw offset) |
BRL Label (or Label-!BankWrap across the bank edge) |
| Call into unmatched code | JSR $9E29 |
a stub label in asm/include/unmatched*.asm |