| |
| |
| |
|
|
| package asmgen |
|
|
| import ( |
| "bytes" |
| "cmp" |
| "fmt" |
| "math/bits" |
| "slices" |
| "strings" |
| ) |
|
|
| |
| |
| |
|
|
| |
| type Asm struct { |
| Arch *Arch |
| out bytes.Buffer |
| regavail uint64 |
| enabled map[Option]bool |
| } |
|
|
| |
| |
| func NewAsm(arch *Arch) *Asm { |
| a := &Asm{Arch: arch, enabled: make(map[Option]bool)} |
| buildTag := "" |
| if arch.Build != "" { |
| buildTag = " && (" + arch.Build + ")" |
| } |
| a.Printf(asmHeader, buildTag) |
| return a |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| var asmHeader = `// Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Code generated by 'go generate' (with ./internal/asmgen). DO NOT EDIT. |
| |
| //go:build !math_big_pure_go%s |
| |
| #include "textflag.h" |
| ` |
|
|
| |
| |
| |
| func (a *Asm) Fatalf(format string, args ...any) { |
| text := a.out.String() |
| i := strings.LastIndex(text, "\nTEXT") |
| text = text[i+1:] |
| panic("[" + a.Arch.Name + "] asmgen internal error: " + fmt.Sprintf(format, args...) + "\n" + text) |
| } |
|
|
| |
| func (a *Asm) hint(h Hint) string { |
| if h == HintCarry && a.Arch.regCarry != "" { |
| return a.Arch.regCarry |
| } |
| if h == HintAltCarry && a.Arch.regAltCarry != "" { |
| return a.Arch.regAltCarry |
| } |
| if h == HintNone || a.Arch.hint == nil { |
| return "" |
| } |
| return a.Arch.hint(a, h) |
| } |
|
|
| |
| |
| func (a *Asm) ZR() Reg { |
| return Reg{a.Arch.reg0} |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (a *Asm) tmp() Reg { |
| return Reg{a.Arch.regTmp} |
| } |
|
|
| |
| func (a *Asm) Carry() Reg { |
| return Reg{a.Arch.regCarry} |
| } |
|
|
| |
| func (a *Asm) AltCarry() Reg { |
| return Reg{a.Arch.regAltCarry} |
| } |
|
|
| |
| func (a *Asm) Imm(x int) Reg { |
| if x == 0 && a.Arch.reg0 != "" { |
| return Reg{a.Arch.reg0} |
| } |
| return Reg{fmt.Sprintf("$%d", x)} |
| } |
|
|
| |
| func (a *Asm) IsZero(r Reg) bool { |
| return r.name == "$0" || a.Arch.reg0 != "" && r.name == a.Arch.reg0 |
| } |
|
|
| |
| func (a *Asm) Reg() Reg { |
| i := bits.TrailingZeros64(a.regavail) |
| if i == 64 { |
| a.Fatalf("out of registers") |
| } |
| a.regavail ^= 1 << i |
| return Reg{a.Arch.regs[i]} |
| } |
|
|
| |
| func (a *Asm) RegHint(hint Hint) Reg { |
| if name := a.hint(hint); name != "" { |
| i := slices.Index(a.Arch.regs, name) |
| if i < 0 { |
| return Reg{name} |
| } |
| if a.regavail&(1<<i) == 0 { |
| a.Fatalf("hint for already allocated register %s", name) |
| } |
| a.regavail &^= 1 << i |
| return Reg{name} |
| } |
| return a.Reg() |
| } |
|
|
| |
| |
| func (a *Asm) Free(r Reg) { |
| i := slices.Index(a.Arch.regs, r.name) |
| if i < 0 { |
| return |
| } |
| if a.regavail&(1<<i) != 0 { |
| a.Fatalf("register %s already freed", r.name) |
| } |
| a.regavail |= 1 << i |
| } |
|
|
| |
| |
| |
| |
| |
| func (a *Asm) Unfree(r Reg) { |
| i := slices.Index(a.Arch.regs, r.name) |
| if i < 0 { |
| return |
| } |
| if a.regavail&(1<<i) == 0 { |
| a.Fatalf("register %s not free", r.name) |
| } |
| a.regavail &^= 1 << i |
| } |
|
|
| |
| type RegsUsed struct { |
| avail uint64 |
| } |
|
|
| |
| |
| func (a *Asm) RegsUsed() RegsUsed { |
| return RegsUsed{a.regavail} |
| } |
|
|
| |
| |
| |
| func (a *Asm) SetRegsUsed(used RegsUsed) { |
| a.regavail = used.avail |
| } |
|
|
| |
| func (a *Asm) FreeAll() { |
| a.regavail = 1<<len(a.Arch.regs) - 1 |
| } |
|
|
| |
| func (a *Asm) Printf(format string, args ...any) { |
| text := fmt.Sprintf(format, args...) |
| if strings.Contains(text, "%!") { |
| a.Fatalf("printf error: %s", text) |
| } |
| a.out.WriteString(text) |
| } |
|
|
| |
| func (a *Asm) Comment(format string, args ...any) { |
| fmt.Fprintf(&a.out, "\t// %s\n", fmt.Sprintf(format, args...)) |
| } |
|
|
| |
| func (a *Asm) EOL(format string, args ...any) { |
| bytes := a.out.Bytes() |
| if len(bytes) > 0 && bytes[len(bytes)-1] == '\n' { |
| a.out.Truncate(a.out.Len() - 1) |
| } |
| a.Comment(format, args...) |
| } |
|
|
| |
| |
| func (a *Asm) JmpEnable(option Option, label string) bool { |
| jmpEnable := a.Arch.options[option] |
| if jmpEnable == nil { |
| return false |
| } |
| jmpEnable(a, label) |
| return true |
| } |
|
|
| |
| |
| func (a *Asm) Enabled(option Option) bool { |
| return a.enabled[option] |
| } |
|
|
| |
| |
| func (a *Asm) SetOption(option Option, on bool) { |
| a.enabled[option] = on |
| } |
|
|
| |
| |
| |
| func (a *Asm) op3(op string, src1, src2, dst Reg) { |
| if op == "" { |
| a.Fatalf("missing instruction") |
| } |
| if src2 == dst { |
| |
| a.Printf("\t%s %s, %s\n", op, src1, dst) |
| } else if a.Arch.op3 != nil && !a.Arch.op3(op) { |
| |
| if src1 == dst { |
| a.Fatalf("implicit mov %s, %s would smash src1", src2, dst) |
| } |
| a.Mov(src2, dst) |
| a.Printf("\t%s %s, %s\n", op, src1, dst) |
| } else { |
| |
| a.Printf("\t%s %s, %s, %s\n", op, src1, src2, dst) |
| } |
| } |
|
|
| |
| func (a *Asm) Mov(src, dst Reg) { |
| if src != dst { |
| a.Printf("\t%s %s, %s\n", a.Arch.mov, src, dst) |
| } |
| } |
|
|
| |
| |
| func (a *Asm) AddWords(src1 Reg, src2, dst RegPtr) { |
| if a.Arch.addWords == "" { |
| |
| |
| t := a.Reg() |
| a.Lsh(a.Imm(bits.TrailingZeros(uint(a.Arch.WordBytes))), src1, t) |
| a.Add(t, Reg(src2), Reg(dst), KeepCarry) |
| a.Free(t) |
| return |
| } |
| a.Printf("\t"+a.Arch.addWords+"\n", src1, src2, dst) |
| } |
|
|
| |
| |
| func (a *Asm) And(src1, src2, dst Reg) { |
| a.op3(a.Arch.and, src1, src2, dst) |
| } |
|
|
| |
| |
| func (a *Asm) Or(src1, src2, dst Reg) { |
| a.op3(a.Arch.or, src1, src2, dst) |
| } |
|
|
| |
| |
| func (a *Asm) Xor(src1, src2, dst Reg) { |
| a.op3(a.Arch.xor, src1, src2, dst) |
| } |
|
|
| |
| |
| func (a *Asm) Neg(src, dst Reg) { |
| if a.Arch.neg == "" { |
| if a.Arch.rsb != "" { |
| a.Printf("\t%s $0, %s, %s\n", a.Arch.rsb, src, dst) |
| return |
| } |
| if a.Arch.sub != "" && a.Arch.reg0 != "" { |
| a.Printf("\t%s %s, %s, %s\n", a.Arch.sub, src, a.Arch.reg0, dst) |
| return |
| } |
| a.Fatalf("missing neg") |
| } |
| if src == dst { |
| a.Printf("\t%s %s\n", a.Arch.neg, dst) |
| } else { |
| a.Printf("\t%s %s, %s\n", a.Arch.neg, src, dst) |
| } |
| } |
|
|
| |
| func (a *Asm) HasRegShift() bool { |
| return a.Arch.regShift |
| } |
|
|
| |
| |
| func (a *Asm) LshReg(shift, src Reg) Reg { |
| if !a.HasRegShift() { |
| a.Fatalf("no reg shift") |
| } |
| return Reg{fmt.Sprintf("%s<<%s", src, strings.TrimPrefix(shift.name, "$"))} |
| } |
|
|
| |
| |
| func (a *Asm) Lsh(shift, src, dst Reg) { |
| if need := a.hint(HintShiftCount); need != "" && shift.name != need && !shift.IsImm() { |
| a.Fatalf("shift count not in %s", need) |
| } |
| if a.HasRegShift() { |
| a.Mov(a.LshReg(shift, src), dst) |
| return |
| } |
| a.op3(a.Arch.lsh, shift, src, dst) |
| } |
|
|
| |
| |
| func (a *Asm) LshWide(shift, adj, src, dst Reg) { |
| if a.Arch.lshd == "" { |
| a.Fatalf("no lshwide on %s", a.Arch.Name) |
| } |
| if need := a.hint(HintShiftCount); need != "" && shift.name != need && !shift.IsImm() { |
| a.Fatalf("shift count not in %s", need) |
| } |
| a.op3(fmt.Sprintf("%s %s,", a.Arch.lshd, shift), adj, src, dst) |
| } |
|
|
| |
| |
| func (a *Asm) RshReg(shift, src Reg) Reg { |
| if !a.HasRegShift() { |
| a.Fatalf("no reg shift") |
| } |
| return Reg{fmt.Sprintf("%s>>%s", src, strings.TrimPrefix(shift.name, "$"))} |
| } |
|
|
| |
| |
| func (a *Asm) Rsh(shift, src, dst Reg) { |
| if need := a.hint(HintShiftCount); need != "" && shift.name != need && !shift.IsImm() { |
| a.Fatalf("shift count not in %s", need) |
| } |
| if a.HasRegShift() { |
| a.Mov(a.RshReg(shift, src), dst) |
| return |
| } |
| a.op3(a.Arch.rsh, shift, src, dst) |
| } |
|
|
| |
| |
| func (a *Asm) RshWide(shift, adj, src, dst Reg) { |
| if a.Arch.lshd == "" { |
| a.Fatalf("no rshwide on %s", a.Arch.Name) |
| } |
| if need := a.hint(HintShiftCount); need != "" && shift.name != need && !shift.IsImm() { |
| a.Fatalf("shift count not in %s", need) |
| } |
| a.op3(fmt.Sprintf("%s %s,", a.Arch.rshd, shift), adj, src, dst) |
| } |
|
|
| |
| func (a *Asm) SLTU(src1, src2, dst Reg) { |
| switch { |
| default: |
| a.Fatalf("arch has no sltu/sgtu") |
| case a.Arch.sltu != "": |
| a.Printf("\t%s %s, %s, %s\n", a.Arch.sltu, src1, src2, dst) |
| case a.Arch.sgtu != "": |
| a.Printf("\t%s %s, %s, %s\n", a.Arch.sgtu, src2, src1, dst) |
| } |
| } |
|
|
| |
| func (a *Asm) Add(src1, src2, dst Reg, carry Carry) { |
| switch { |
| default: |
| a.Fatalf("unsupported carry behavior") |
| case a.Arch.addF != nil && a.Arch.addF(a, src1, src2, dst, carry): |
| |
| case a.Arch.add != "" && (carry == KeepCarry || carry == SmashCarry): |
| a.op3(a.Arch.add, src1, src2, dst) |
| case a.Arch.adds != "" && (carry == SetCarry || carry == SmashCarry): |
| a.op3(a.Arch.adds, src1, src2, dst) |
| case a.Arch.adc != "" && (carry == UseCarry || carry == UseCarry|SmashCarry): |
| a.op3(a.Arch.adc, src1, src2, dst) |
| case a.Arch.adcs != "" && (carry == UseCarry|SetCarry || carry == UseCarry|SmashCarry): |
| a.op3(a.Arch.adcs, src1, src2, dst) |
| case a.Arch.lea != "" && (carry == KeepCarry || carry == SmashCarry): |
| if src1.IsImm() { |
| a.Printf("\t%s %s(%s), %s\n", a.Arch.lea, src1.name[1:], src2, dst) |
| } else { |
| a.Printf("\t%s (%s)(%s), %s\n", a.Arch.lea, src1, src2, dst) |
| } |
| if src2 == dst { |
| a.EOL("ADD %s, %s", src1, dst) |
| } else { |
| a.EOL("ADD %s, %s, %s", src1, src2, dst) |
| } |
|
|
| case a.Arch.add != "" && a.Arch.regCarry != "": |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| cr := a.Carry() |
| if carry&AltCarry != 0 { |
| cr = a.AltCarry() |
| if !cr.Valid() { |
| a.Fatalf("alt carry not supported") |
| } |
| carry &^= AltCarry |
| } |
| tmp := a.tmp() |
| if !tmp.Valid() { |
| a.Fatalf("cannot simulate sub carry without regTmp") |
| } |
| switch carry { |
| default: |
| a.Fatalf("unsupported carry behavior") |
| case UseCarry, UseCarry | SmashCarry: |
| |
| if a.IsZero(src1) { |
| |
| a.Add(cr, src2, dst, KeepCarry) |
| a.EOL("ADC $0, %s, %s", src2, dst) |
| break |
| } |
| a.Add(src1, src2, dst, KeepCarry) |
| a.EOL("ADC %s, %s, %s (cr=%s)", src1, src2, dst, cr) |
| a.Add(cr, dst, dst, KeepCarry) |
| a.EOL("...") |
|
|
| case SetCarry: |
| if a.IsZero(src1) && src2 == dst { |
| |
| a.Xor(cr, cr, cr) |
| break |
| } |
| var old Reg |
| switch { |
| case dst != src1: |
| old = src1 |
| case dst != src2: |
| old = src2 |
| default: |
| |
| |
| a.Rsh(a.Imm(a.Arch.WordBits-1), src1, cr) |
| a.EOL("ADDS %s, %s, %s (cr=%s)", src1, src2, dst, cr) |
| a.Add(src1, src2, dst, KeepCarry) |
| a.EOL("...") |
| return |
| } |
| a.Add(src1, src2, dst, KeepCarry) |
| a.EOL("ADDS %s, %s, %s (cr=%s)", src1, src2, dst, cr) |
| a.SLTU(old, dst, cr) |
| a.EOL("...") |
|
|
| case UseCarry | SetCarry: |
| if a.IsZero(src1) { |
| |
| |
| a.Add(cr, src2, dst, KeepCarry) |
| a.EOL("ADCS $0, %s, %s (cr=%s)", src2, dst, cr) |
| a.SLTU(cr, dst, cr) |
| a.EOL("...") |
| break |
| } |
| |
| |
| |
| var old Reg |
| switch { |
| case dst != src1: |
| old = src1 |
| case dst != src2: |
| old = src2 |
| } |
| if old.Valid() { |
| a.Add(src1, src2, dst, KeepCarry) |
| a.EOL("ADCS %s, %s, %s (cr=%s)", src1, src2, dst, cr) |
| a.SLTU(old, dst, tmp) |
| a.EOL("...") |
| } else { |
| |
| |
| a.Rsh(a.Imm(a.Arch.WordBits-1), src1, tmp) |
| a.EOL("ADCS %s, %s, %s (cr=%s)", src1, src2, dst, cr) |
| a.Add(src1, src2, dst, KeepCarry) |
| a.EOL("...") |
| } |
| |
| a.Add(cr, dst, dst, KeepCarry) |
| a.EOL("...") |
| a.SLTU(cr, dst, cr) |
| a.EOL("...") |
| |
| a.Add(tmp, cr, cr, KeepCarry) |
| a.EOL("...") |
| } |
| } |
| } |
|
|
| |
| func (a *Asm) Sub(src1, src2, dst Reg, carry Carry) { |
| switch { |
| default: |
| a.Fatalf("unsupported carry behavior") |
| case a.Arch.subF != nil && a.Arch.subF(a, src1, src2, dst, carry): |
| |
| case a.Arch.sub != "" && (carry == KeepCarry || carry == SmashCarry): |
| a.op3(a.Arch.sub, src1, src2, dst) |
| case a.Arch.subs != "" && (carry == SetCarry || carry == SmashCarry): |
| a.op3(a.Arch.subs, src1, src2, dst) |
| case a.Arch.sbc != "" && (carry == UseCarry || carry == UseCarry|SmashCarry): |
| a.op3(a.Arch.sbc, src1, src2, dst) |
| case a.Arch.sbcs != "" && (carry == UseCarry|SetCarry || carry == UseCarry|SmashCarry): |
| a.op3(a.Arch.sbcs, src1, src2, dst) |
| case strings.HasPrefix(src1.name, "$") && (carry == KeepCarry || carry == SmashCarry): |
| |
| |
| |
| if strings.HasPrefix(src1.name, "$-") { |
| src1.name = "$" + src1.name[2:] |
| } else { |
| src1.name = "$-" + src1.name[1:] |
| } |
| a.Add(src1, src2, dst, carry) |
|
|
| case a.Arch.sub != "" && a.Arch.regCarry != "": |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| cr := a.Carry() |
| if carry&AltCarry != 0 { |
| a.Fatalf("alt carry not supported") |
| } |
| tmp := a.tmp() |
| if !tmp.Valid() { |
| a.Fatalf("cannot simulate carry without regTmp") |
| } |
| switch carry { |
| default: |
| a.Fatalf("unsupported carry behavior") |
| case UseCarry, UseCarry | SmashCarry: |
| |
| if a.IsZero(src1) { |
| |
| a.Sub(cr, src2, dst, KeepCarry) |
| a.EOL("SBC $0, %s, %s", src2, dst) |
| break |
| } |
| a.Sub(src1, src2, dst, KeepCarry) |
| a.EOL("SBC %s, %s, %s", src1, src2, dst) |
| a.Sub(cr, dst, dst, KeepCarry) |
| a.EOL("...") |
|
|
| case SetCarry: |
| if a.IsZero(src1) && src2 == dst { |
| |
| a.Xor(cr, cr, cr) |
| break |
| } |
| |
| a.SLTU(src1, src2, cr) |
| a.EOL("SUBS %s, %s, %s", src1, src2, dst) |
| a.Sub(src1, src2, dst, KeepCarry) |
| a.EOL("...") |
|
|
| case UseCarry | SetCarry: |
| if a.IsZero(src1) { |
| |
| if src2 == dst { |
| |
| |
| |
| |
| |
| |
| a.SLTU(cr, src2, tmp) |
| a.EOL("SBCS $0, %s, %s", src2, dst) |
| a.Sub(cr, src2, dst, KeepCarry) |
| a.EOL("...") |
| a.Mov(tmp, cr) |
| a.EOL("...") |
| break |
| } |
| a.Sub(cr, src2, dst, KeepCarry) |
| a.SLTU(cr, src2, cr) |
| break |
| } |
| |
| |
| |
| a.SLTU(cr, src2, tmp) |
| a.EOL("SBCS %s, %s, %s", src1, src2, dst) |
| a.Sub(cr, src2, dst, KeepCarry) |
| a.EOL("...") |
| a.SLTU(src1, dst, cr) |
| a.EOL("...") |
| a.Sub(src1, dst, dst, KeepCarry) |
| a.EOL("...") |
| a.Add(tmp, cr, cr, KeepCarry) |
| a.EOL("...") |
| } |
| } |
| } |
|
|
| |
| |
| |
| func (a *Asm) ClearCarry(which Carry) { |
| dst := Reg{a.Arch.regs[0]} |
| switch which & (AddCarry | SubCarry) { |
| default: |
| a.Fatalf("bad carry") |
| case AddCarry: |
| a.Add(a.Imm(0), dst, dst, SetCarry|which&AltCarry) |
| case SubCarry: |
| a.Sub(a.Imm(0), dst, dst, SetCarry|which&AltCarry) |
| } |
| a.EOL("clear carry") |
| } |
|
|
| |
| |
| |
| func (a *Asm) SaveCarry(dst Reg) { |
| |
| |
| |
| if cr := a.Carry(); cr.Valid() { |
| if cr == dst { |
| return |
| } |
| a.Mov(cr, dst) |
| } else { |
| a.Sub(dst, dst, dst, UseCarry|SmashCarry) |
| } |
| a.EOL("save carry") |
| } |
|
|
| |
| |
| func (a *Asm) RestoreCarry(src Reg) { |
| if cr := a.Carry(); cr.Valid() { |
| if cr == src { |
| return |
| } |
| a.Mov(src, cr) |
| } else if a.Arch.subCarryIsBorrow { |
| a.Add(src, src, src, SetCarry) |
| } else { |
| |
| |
| |
| |
| a.Sub(src, cmp.Or(a.ZR(), Reg{"SP"}), src, SetCarry) |
| } |
| a.EOL("restore carry") |
| } |
|
|
| |
| |
| func (a *Asm) ConvertCarry(which Carry, dst Reg) { |
| if a.Carry().Valid() { |
| return |
| } |
| switch which { |
| case AddCarry: |
| if a.Arch.subCarryIsBorrow { |
| a.Neg(dst, dst) |
| } else { |
| a.Add(a.Imm(1), dst, dst, SmashCarry) |
| } |
| a.EOL("convert add carry") |
| case SubCarry: |
| a.Neg(dst, dst) |
| a.EOL("convert sub carry") |
| } |
| } |
|
|
| |
| |
| func (a *Asm) SaveConvertCarry(which Carry, dst Reg) { |
| switch which { |
| default: |
| a.Fatalf("bad carry") |
| case AddCarry: |
| if (a.Arch.adc != "" || a.Arch.adcs != "") && a.ZR().Valid() { |
| a.Add(a.ZR(), a.ZR(), dst, UseCarry|SmashCarry) |
| a.EOL("save & convert add carry") |
| return |
| } |
| case SubCarry: |
| |
| } |
| a.SaveCarry(dst) |
| a.ConvertCarry(which, dst) |
| } |
|
|
| |
| |
| |
| func (a *Asm) MulWide(src1, src2, dstlo, dsthi Reg) { |
| switch { |
| default: |
| a.Fatalf("mulwide not available") |
| case a.Arch.mulWideF != nil: |
| a.Arch.mulWideF(a, src1, src2, dstlo, dsthi) |
| case a.Arch.mul != "" && !dsthi.Valid(): |
| a.op3(a.Arch.mul, src1, src2, dstlo) |
| case a.Arch.mulhi != "" && !dstlo.Valid(): |
| a.op3(a.Arch.mulhi, src1, src2, dsthi) |
| case a.Arch.mul != "" && a.Arch.mulhi != "" && dstlo != src1 && dstlo != src2: |
| a.op3(a.Arch.mul, src1, src2, dstlo) |
| a.op3(a.Arch.mulhi, src1, src2, dsthi) |
| case a.Arch.mul != "" && a.Arch.mulhi != "" && dsthi != src1 && dsthi != src2: |
| a.op3(a.Arch.mulhi, src1, src2, dsthi) |
| a.op3(a.Arch.mul, src1, src2, dstlo) |
| } |
| } |
|
|
| |
| func (a *Asm) Jmp(label string) { |
| |
| a.Printf("\tJMP %s\n", label) |
| } |
|
|
| |
| |
| func (a *Asm) JmpZero(src Reg, label string) { |
| a.Printf("\t"+a.Arch.jmpZero+"\n", src, label) |
| } |
|
|
| |
| |
| func (a *Asm) JmpNonZero(src Reg, label string) { |
| a.Printf("\t"+a.Arch.jmpNonZero+"\n", src, label) |
| } |
|
|
| |
| func (a *Asm) Label(name string) { |
| a.Printf("%s:\n", name) |
| } |
|
|
| |
| func (a *Asm) Ret() { |
| a.Printf("\tRET\n") |
| } |
|
|