feat(asm): encode indirect JMP and CALL on all four architectures

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-19 19:17:07 +02:00
parent 96e81cc98d
commit 23c001be51
24 changed files with 494 additions and 27 deletions
+29 -8
View File
@@ -209,10 +209,10 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
lastTerminal := false
hasMacro := false
instrCount := 0
dead := false // inside a region unreachable from above
reportedDead := false // the current dead region has already been reported
hasPCRel := referencesPC(t) // PC-relative jumps defeat reachability analysis
hasIndirect := hasIndirectBranch(t) // register-indirect branches do too
dead := false // inside a region unreachable from above
reportedDead := false // the current dead region has already been reported
hasPCRel := referencesPC(t) // PC-relative jumps defeat reachability analysis
hasIndirect := hasIndirectBranch(t, tab) // register-indirect branches do too
// Unreachable-code analysis is only sound in functions whose control flow is
// fully label-resolvable: no PC-relative jumps, no register-indirect
// branches, and (file-level) no preprocessor conditionals.
@@ -549,10 +549,12 @@ func referencesPC(t *ast.Text) bool {
}
// hasIndirectBranch reports whether a function transfers control through a
// register (JALR/JR/JIRL/BR/BLR). Such targets are computed at runtime, so
// reachability cannot be determined statically and the unreachable-code check is
// suppressed for the whole function.
func hasIndirectBranch(t *ast.Text) bool {
// register or a computed memory address: the RISC branch-register mnemonics
// (JALR/JR/JIRL/BR/BLR), or a JMP/CALL whose target is a register or memory
// operand rather than a label or symbol. Such targets are computed at
// runtime, so reachability cannot be determined statically and the
// unreachable-code check is suppressed for the whole function.
func hasIndirectBranch(t *ast.Text, tab *arch.Table) bool {
for _, s := range t.Body {
in, ok := s.(*ast.Instr)
if !ok {
@@ -561,11 +563,30 @@ func hasIndirectBranch(t *ast.Text) bool {
switch strings.ToUpper(in.Mnemonic.Text) {
case "JALR", "JR", "JIRL", "BR", "BLR":
return true
case "JMP", "CALL":
if indirectJumpTarget(in, tab) {
return true
}
}
}
return false
}
// indirectJumpTarget reports whether the JMP/CALL operand addresses a
// register or a memory location rather than a label or a static symbol. The
// parser delivers a bare register and a bare label in the same shape, so
// register membership decides.
func indirectJumpTarget(in *ast.Instr, tab *arch.Table) bool {
if len(in.Operands) != 1 || in.Operands[0].Kind != ast.OpAddr {
return false
}
a := in.Operands[0].Addr
if a.Base != "" || a.Index != "" {
return true
}
return a.Sym != nil && a.Sym.Pseudo == "" && a.Sym.Name != "" && tab.IsRegister(a.Sym.Name)
}
// isMacroInvocation reports whether a mnemonic is a macro invocation rather
// than a machine instruction. No Plan 9 mnemonic contains an underscore, so an
// underscore is a reliable macro marker (the runtime headers define macros such