feat(asm): encode the amd64 and loong64 tails of the corpus testdata
Assisted-by: GLM 5.3
This commit is contained in:
+218
-21
@@ -808,17 +808,43 @@ func isJumpMnemonic(mnem string) bool {
|
||||
if mnem == "JMP" || mnem == "CALL" {
|
||||
return true
|
||||
}
|
||||
if isLoopMnemonic(mnem) {
|
||||
return true
|
||||
}
|
||||
_, ok := condCode(mnem)
|
||||
return ok
|
||||
}
|
||||
|
||||
// isLoopMnemonic reports the LOOP family, rel8 alone (E0-E2).
|
||||
func isLoopMnemonic(mnem string) bool {
|
||||
switch mnem {
|
||||
case "LOOP", "LOOPE", "LOOPNE":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// loopOpcode maps the LOOP family to its E0-E2 opcode.
|
||||
func loopOpcode(mnem string) byte {
|
||||
switch mnem {
|
||||
case "LOOPE":
|
||||
return 0xE1
|
||||
case "LOOPNE":
|
||||
return 0xE0
|
||||
}
|
||||
return 0xE2
|
||||
}
|
||||
|
||||
// jumpSize returns the length of a jump instruction in the requested form:
|
||||
// short (rel8) where available, otherwise the rel32 form. CALL is always
|
||||
// rel32.
|
||||
// rel32; the LOOP family is rel8 alone.
|
||||
func jumpSize(mnem string, long bool) int {
|
||||
if mnem == "CALL" {
|
||||
return 5 // opcode + rel32
|
||||
}
|
||||
if isLoopMnemonic(mnem) {
|
||||
return 2 // opcode + rel8, the only form
|
||||
}
|
||||
if !long {
|
||||
return 2 // opcode + rel8
|
||||
}
|
||||
@@ -945,6 +971,16 @@ func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch
|
||||
if (mnemUpper == "MOVQ" || mnemUpper == "MOVL") && len(s.Operands) == 2 && isBareTLS(s.Operands[0]) {
|
||||
return encodeTLSBaseLoad(s, fi, link)
|
||||
}
|
||||
// The old paired-register shift spelling, SHLL CX, R11:AX (a colon
|
||||
// between the two registers), is the toolchain's SHLD family: SHLDL CL,
|
||||
// AX, R11 with the count register first, the paired source in the reg
|
||||
// field and the pair's head in r/m.
|
||||
if code, ps, err := encodeColonShift(s, mnemUpper, fi, link); code != nil || err != nil {
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return code, ps, nil, nil
|
||||
}
|
||||
_, size := splitSize(mnemUpper)
|
||||
if size == 0 {
|
||||
size = 8
|
||||
@@ -1051,6 +1087,66 @@ func encodeBookkeeping(upper string, s *ast.Instr) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// encodeColonShift encodes the paired-register shift spellings, SHLx CX,
|
||||
// dst:src: the toolchain reads them as the SHLD family (double-precision
|
||||
// shift by CL), reg = the paired source, r/m = the pair's head. The second
|
||||
// operand's raw text carries the colon; ok reports the spelling was found.
|
||||
func encodeColonShift(s *ast.Instr, mnemUpper string, fi frameInfo, link *linkInfo) ([]byte, []sbPatch, error) {
|
||||
base, _ := strings.CutPrefix(mnemUpper, "SHL")
|
||||
if base == mnemUpper || len(s.Operands) != 2 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
_, size := splitSize(mnemUpper)
|
||||
raw := strings.ReplaceAll(s.Operands[1].Raw, " ", "")
|
||||
head, tail, ok := strings.Cut(raw, ":")
|
||||
if !ok || head == "" || tail == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
headReg, ok1 := ParseReg(head)
|
||||
srcReg, ok2 := ParseReg(tail)
|
||||
if !ok1 || !ok2 {
|
||||
return nil, nil, fmt.Errorf("%s: invalid paired register %q", mnemUpper, s.Operands[1].Raw)
|
||||
}
|
||||
cnt, err := operandFromAST(mnemUpper, s.Operands[0], size, fi, link)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cntReg, ok := cnt.(Reg)
|
||||
if !ok || cntReg.idx != 1 {
|
||||
return nil, nil, fmt.Errorf("%s: the paired-register form counts in CL", mnemUpper)
|
||||
}
|
||||
// SHLD r/m, reg, CL: 0F A5 (REX.W for the 64-bit width).
|
||||
e := &enc{}
|
||||
i := &instr{rexW: size == 8, opcode: []byte{0x0F, 0xA5}, modrm: -1, sib: -1}
|
||||
if err := setRM(i, srcReg, headReg, size); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := e.emit(i); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ps := make([]sbPatch, len(e.patches))
|
||||
for j, p := range e.patches {
|
||||
ps[j] = sbPatch{off: p.off, name: p.name, addend: p.addend}
|
||||
}
|
||||
return e.out, ps, nil
|
||||
}
|
||||
|
||||
// trailingIndexGroup recovers a trailing "(index*scale)" or "(index)" group
|
||||
// from an operand's raw text: the symbol-pseudo parse returns before the
|
||||
// index group, so foo(SP)(AX*1) keeps its index only in the spelling.
|
||||
func trailingIndexGroup(raw string) (string, int, bool) {
|
||||
compact := strings.ReplaceAll(raw, " ", "")
|
||||
if !strings.HasSuffix(compact, ")") {
|
||||
return "", 0, false
|
||||
}
|
||||
open := strings.LastIndex(compact, "(")
|
||||
if open < 2 || !strings.Contains(compact[:open], ")") {
|
||||
return "", 0, false // one group alone: no trailing index
|
||||
}
|
||||
name, scale, _, ok := cutParenGroup(compact[open:])
|
||||
return name, scale, ok
|
||||
}
|
||||
|
||||
// encodeJump encodes a JMP/CALL/Jcc with a relative offset resolved from the
|
||||
// target label or from a numeric ±N(PC) instruction count, in the short
|
||||
// (rel8) or long (rel32) form. numTarget is the resolved byte offset of a
|
||||
@@ -1085,9 +1181,15 @@ func encodeJump(s *ast.Instr, mnem string, pc int, offsets map[string]int, long
|
||||
if mnem == "JMP" {
|
||||
return []byte{0xEB, byte(int8(rel))}, nil
|
||||
}
|
||||
if isLoopMnemonic(mnem) {
|
||||
return []byte{loopOpcode(mnem), byte(int8(rel))}, nil
|
||||
}
|
||||
cc, _ := condCode(mnem)
|
||||
return []byte{0x70 + byte(cc), byte(int8(rel))}, nil
|
||||
}
|
||||
if isLoopMnemonic(mnem) {
|
||||
return nil, fmt.Errorf("%s has no long form", mnem)
|
||||
}
|
||||
switch mnem {
|
||||
case "JMP":
|
||||
return append([]byte{0xE9}, le32(rel)...), nil
|
||||
@@ -1139,6 +1241,72 @@ func labelName(op *ast.Operand) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// jumpOperand returns the branch-target operand of a JMP/CALL, rewriting the
|
||||
// `*`-prefixed indirect spellings (JMP *(R12), JMP *4(SP)) into their plain
|
||||
// memory form. The star marks an indirect target and changes no bytes; the
|
||||
// address parser leaves the operand's address empty because of the leading
|
||||
// star, so the fields are rebuilt from the raw text onto a copy of the
|
||||
// operand, never on the shared syntax tree.
|
||||
func jumpOperand(s *ast.Instr) *ast.Operand {
|
||||
if len(s.Operands) != 1 {
|
||||
return nil
|
||||
}
|
||||
op := s.Operands[0]
|
||||
compact := strings.ReplaceAll(op.Raw, " ", "")
|
||||
inner, ok := strings.CutPrefix(compact, "*")
|
||||
if !ok {
|
||||
return op
|
||||
}
|
||||
var addr ast.Address
|
||||
if i := strings.IndexByte(inner, '('); i > 0 {
|
||||
v, err := strconv.ParseInt(inner[:i], 0, 64)
|
||||
if err != nil {
|
||||
return op
|
||||
}
|
||||
addr.Offset, addr.HasOff = v, true
|
||||
inner = inner[i:]
|
||||
}
|
||||
base, _, rest, ok := cutParenGroup(inner)
|
||||
if !ok {
|
||||
return op
|
||||
}
|
||||
if base != "" {
|
||||
addr.Base = base
|
||||
}
|
||||
if rest != "" {
|
||||
idx, scale, _, ok := cutParenGroup(rest)
|
||||
if ok && idx != "" {
|
||||
addr.Index = idx
|
||||
addr.Scale = scale
|
||||
}
|
||||
}
|
||||
c := *op
|
||||
c.Addr = addr
|
||||
return &c
|
||||
}
|
||||
|
||||
// cutParenGroup splits a leading "(name)" or "(name*n)" off s, returning the
|
||||
// inner text, the scale it names (1 when the group spells no multiplier) and
|
||||
// the remainder.
|
||||
func cutParenGroup(s string) (name string, scale int, rest string, ok bool) {
|
||||
if !strings.HasPrefix(s, "(") {
|
||||
return "", 0, "", false
|
||||
}
|
||||
i := strings.IndexByte(s, ')')
|
||||
if i < 0 {
|
||||
return "", 0, "", false
|
||||
}
|
||||
inner, rest := s[1:i], s[i+1:]
|
||||
if before, after, ok := strings.Cut(inner, "*"); ok {
|
||||
n, err := strconv.Atoi(after)
|
||||
if err != nil {
|
||||
return "", 0, "", false
|
||||
}
|
||||
return before, n, rest, true
|
||||
}
|
||||
return inner, 1, rest, true
|
||||
}
|
||||
|
||||
// indirectJumpTarget reports whether the JMP/CALL operand addresses a
|
||||
// register or a memory location rather than a label or a static symbol.
|
||||
// A bare identifier is a register when the register table knows the name and
|
||||
@@ -1147,7 +1315,14 @@ func indirectJumpTarget(s *ast.Instr) bool {
|
||||
if len(s.Operands) != 1 || s.Operands[0].Kind != ast.OpAddr {
|
||||
return false
|
||||
}
|
||||
a := s.Operands[0].Addr
|
||||
op := jumpOperand(s)
|
||||
if op == nil {
|
||||
return false
|
||||
}
|
||||
if op != s.Operands[0] {
|
||||
return true // the star marker spells an indirect target
|
||||
}
|
||||
a := op.Addr
|
||||
// ±N(PC) is the numeric relative form, the PC counts instructions from
|
||||
// the branch: relative, not indirect.
|
||||
if a.Base == "PC" || a.Index == "PC" {
|
||||
@@ -1165,18 +1340,20 @@ func indirectJumpTarget(s *ast.Instr) bool {
|
||||
}
|
||||
|
||||
// encodeIndirectJump assembles a JMP/CALL through a register or memory
|
||||
// operand, which carries no relocation and no label to resolve.
|
||||
// operand, which carries no relocation and no label to resolve. The
|
||||
// `*`-prefixed spellings go through jumpOperand first, their star rebuilt
|
||||
// into a plain memory operand.
|
||||
func encodeIndirectJump(s *ast.Instr, mnem string) ([]byte, error) {
|
||||
ops := make([]Operand, len(s.Operands))
|
||||
for i, op := range s.Operands {
|
||||
o, err := operandFromAST(mnem, op, 8, frameInfo{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops[i] = o
|
||||
op := s.Operands[0]
|
||||
if cleaned := jumpOperand(s); cleaned != nil {
|
||||
op = cleaned
|
||||
}
|
||||
o, err := operandFromAST(mnem, op, 8, frameInfo{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e := &enc{}
|
||||
if err := e.encodeIndirectBranch(mnem, ops); err != nil {
|
||||
if err := e.encodeIndirectBranch(mnem, []Operand{o}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.out, nil
|
||||
@@ -1245,31 +1422,51 @@ func operandFromAST(mnemUpper string, op *ast.Operand, size int, fi frameInfo, l
|
||||
off := a.Sym.Offset + fi.fpAdjust
|
||||
return Mem{Base: spReg, Disp: off, HasBase: true, Size: size}, nil
|
||||
}
|
||||
// SP-relative local: x-N(SP) → (spAdjust + offset)(SP).
|
||||
// SP-relative local: x-N(SP) → (spAdjust + offset)(SP), keeping a scaled
|
||||
// index beside the virtual stack pointer (foo(SP)(AX*1)). The
|
||||
// symbol-pseudo parse returns before the index group, so the index
|
||||
// is recovered from the raw text when the address lacks it.
|
||||
if a.Sym != nil && a.Sym.Pseudo == "SP" && a.Base == "" {
|
||||
off := fi.spAdjust + a.Sym.Offset
|
||||
return Mem{Base: spReg, Disp: off, HasBase: true, Size: size}, nil
|
||||
m := Mem{Base: spReg, Disp: off, HasBase: true, Size: size}
|
||||
if name, scale, ok := trailingIndexGroup(op.Raw); ok {
|
||||
idx, ok := ParseReg(name)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown index register %q", name)
|
||||
}
|
||||
m.Index = idx
|
||||
m.Scale = scale
|
||||
m.HasIndex = true
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
// SB (global symbol): a symbol defined in the same file (GLOBL) is
|
||||
// encoded RIP-relative and resolved by the file-level layout;
|
||||
// anything not defined here needs object-file emission.
|
||||
// anything not defined here needs object-file emission. A static
|
||||
// (file-local) spelling of an undefined symbol defers the same way
|
||||
// the toolchain does: the relocation names it and the linker decides.
|
||||
if a.Sym != nil && a.Sym.Pseudo == "SB" {
|
||||
if link == nil || link.symbols == nil {
|
||||
return nil, fmt.Errorf("symbol %q needs file-level assembly (AssembleFile)", a.Sym.Name)
|
||||
}
|
||||
if !link.symbols[a.Sym.Name] {
|
||||
if a.Sym.Static {
|
||||
return nil, fmt.Errorf("undefined symbol %q", a.Sym.Name)
|
||||
}
|
||||
if !link.allowExternal {
|
||||
return nil, fmt.Errorf("external symbol %q needs object-file emission", a.Sym.Name)
|
||||
}
|
||||
if !link.symbols[a.Sym.Name] && !link.allowExternal {
|
||||
return nil, fmt.Errorf("external symbol %q needs object-file emission", a.Sym.Name)
|
||||
}
|
||||
return sbMem{size: size, name: a.Sym.Name, addend: a.Sym.Offset}, nil
|
||||
}
|
||||
|
||||
// Memory with a real base register: (base), off(base), (base)(index*scale).
|
||||
if a.Base != "" {
|
||||
// The TLS pseudo-base, off(TLS): the segment-prefixed absolute
|
||||
// the thread-local access lowers to, 64 8B 04 25 with its
|
||||
// R_TLS_LE patch site on the disp32.
|
||||
if a.Base == "TLS" {
|
||||
seg := byte(0x64) // FS on linux, freebsd, plan9
|
||||
if link != nil && link.goos == "windows" {
|
||||
seg = 0x65 // GS
|
||||
}
|
||||
return TLSMem{Disp: a.Offset, Size: size, Seg: seg}, nil
|
||||
}
|
||||
// Segment-absolute: 0x30(GS) and 0x28(FS), the windows TLS
|
||||
// spellings. The segment override prefixes a disp32 absolute
|
||||
// reference with no relocation.
|
||||
|
||||
Reference in New Issue
Block a user