[M68k] M68000 target emits bra.l M68020+ instructions
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
# LLVM Bug Report: M68k emits 68020-only `bra.l`/`bsr.l` (32-bit branch displacement) on sub-M68020 targets
**Component:** Backend: M68k
**Reporter:** Macticulum project (Reticulum endpoint for classic Macintosh)
---
## Summary
For branch targets further than a 16-bit displacement (and for PC-relative tail
calls), the m68k backend emits `bra.l` / `bsr.l` — the **32-bit PC-relative
branch-displacement forms** (opcodes `0x60FF` / `0x61FF`). Those encodings only
exist on the 68020 and later. On a 68000 or 68010, `0x60FF` is *not* a long
branch (the 68000 only has 8- and 16-bit branch displacements), so executing it
crashes with an illegal-instruction / address-error fault. This is emitted even
for `-mcpu=M68000`.
## Background: m68k branch displacement sizes
- 68000/68010 support only `bra.b` (8-bit) and `bra.w` (16-bit)
displacements; there is **no 32-bit PC-relative branch**.
- The 32-bit displacement forms (`0x60FF` for `bra.l`, `0x61FF` for
`bsr.l`, and the conditional `0x6x` with `disp8 = 0xFF`) were introduced
with the 68020.
- The 68000 was used in the Macintosh Plus/Classic/SE, Amiga 500/1000/2000
and Atari ST — the target audience for classic-Mac / classic-Amiga
toolchains.
## Root cause
In `llvm/lib/Target/M68k/M68kInstrControl.td`, `BRA32` / `BSR32` (and the
conditional `B32`) **are** declared 68020-only:
```td
class MxBra
: MxInst<(outs), (ins TARGET:$dst), "bra\t$dst", []> {
let Inst = (ascend
(descend 0b0110, 0b0000, disp_8),
disp_16_32);
// 32-bit displacement form is 68020+:
let Predicates = !if(!eq(TARGET, MxBrTarget32), [AtLeastM68020], []);
}
```
So the backend knows `BRA32`/`BSR32` require `atLeastM68020()`. Despite that,
the 32-bit form is still selected for sub-M68020 targets:
- The ISel pattern always selects `BRA8` initially:
`def : Pat<(br bb:$target), (BRA8 MxBrTarget8:$target)>;`
- PC-relative tail calls lower through `TAILJMPq`, which `M68kMCInstLower.cpp`
maps to `BRA8` (line 181).
- The branch is then widened to the 32-bit displacement form when the target
does not fit in 8/16 bits — and this widening does **not** consult
`Subtarget.atLeastM68020()`. The backend has no branch-relaxation gate
(there is no `isBranchOffsetInRange`/`BranchRelaxation` implementation for
M68k, and `M68kInstrInfo::analyzeBranch` only recognizes `BRA8`/`BRA16`,
not `BRA32`).
## Impact and evidence
On a 68000, a Rust `no_std` build for `m68k-unknown-none-elf` (or any
sub-M68020 m68k target) contains **109 `bra.l` (0x60FF)** instructions in a
crypto-heavy program — most are tail-call jumps at function entries:
```
00000000 <__ashldi3>:
0: 60ff 0000 0000 bral 2 <__ashldi3+0x2> ; 68020-only
```
Confirmed with `-mcpu=M68000`; disabling the higher ISA features
(`-mattr=-isa-68020,...`) does not change it. Executing `0x60FF` on a 68000
crashes ("illegal instruction" or, for the `disp8=0xFF` interpretation,
branching to an odd address → "address error").
## Minimal repro
```llvm
; A tail call (or a branch whose target is beyond 16-bit displacement).
define i32 @f() {
%r = tail call i32 @g()
ret i32 %r
}
declare i32 @g()
```
```
llc -mtriple=m68k -mcpu=M68000 f.ll # emits bra.l (0x60FF) -> illegal on 68000
```
Also reproduces via `rustc --target m68k-unknown-none-elf -Z build-std=core,alloc`
for any program with tail calls or far branches.
## Suggested fix
In the m68k backend, gate the 32-bit branch-displacement selection on
`Subtarget.atLeastM68020()`. For sub-M68020 targets:
- For far unconditional branches / tail calls, lower to the 68000-safe
absolute forms instead of `bra.l` / `bsr.l`:
- `bra.l ` → `jmp.l ` (`JMP32j`, `0x4EF9`) — already used for
the register tail call `TAILJMPj`, and 68000-valid.
- `bsr.l ` → `jsr.l ` (`0x4EB9`).
- The 68000 lacks 32-bit PC-relative branches entirely, so the long branch
must go through the jump table, `jmp`/`jsr `, or a
negate-condition + `jmp` sequence for conditional branches.
- Ensure the branch-widening logic checks the subtarget before emitting the
32-bit form.
## Workaround note
Until the backend is fixed, the encodings can be rewritten at the object
level: `bra.l`/`bsr.l` (`0x60FF`/`0x61FF`) are the same 6-byte size as
`jmp.l`/`jsr.l ` (`0x4EF9`/`0x4EB9`), and the relocation is flipped from
`R_68K_PC32` to `R_68K_32` so the linker computes the absolute target. The
Macticulum build does exactly this (`scripts/fix-m68k-bral.sh`).
Contributor guide
Research direction
Start with llvm/lib/Target/M68k/M68kInstrControl.td and the TAILJMPq handling in M68kMCInstLower.cpp, then inspect M68kInstrInfo::analyzeBranch and the existing branch-widening path. Reproduce the issue with the provided llc command for M68000 and verify that far branches and tail calls no longer emit BRA32, while 68020 targets retain the long form.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100