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
+31
View File
@@ -8,6 +8,7 @@ import (
"testing"
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
)
@@ -495,3 +496,33 @@ TEXT ·f(SB), NOSPLIT, $0
t.Fatalf("amd64 must not be flagged: %+v", diags)
}
}
// TestHasIndirectBranchShape checks that a JMP/CALL through a register or
// memory suppresses reachability analysis, while a same-named label does not.
func TestHasIndirectBranchShape(t *testing.T) {
tab := arch.ForArch(arch.AMD64)
indirect := `TEXT ·f(SB), NOSPLIT, $0
JMP AX
RET
`
f, errs := parser.Parse("t_amd64.s", indirect)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
if !hasIndirectBranch(f.Decls[0].(*ast.Text), tab) {
t.Error("JMP AX: indirect branch not detected")
}
label := `TEXT ·f(SB), NOSPLIT, $0
loop:
JMP loop
RET
`
f, errs = parser.Parse("t_amd64.s", label)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
if hasIndirectBranch(f.Decls[0].(*ast.Text), tab) {
t.Error("JMP loop: label treated as an indirect branch")
}
}