feat(asm): encode indirect JMP and CALL on all four architectures
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+29
-8
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user