blob: d1d8309c8f2c10d132837cd0dd20ef7dd363f2a6 [file] [log] [blame]
// 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.
package asmgen
import (
"bytes"
"cmp"
"fmt"
"math/bits"
"slices"
"strings"
)
// Note: Exported fields and methods are expected to be used
// by function generators (like the ones in add.go and so on).
// Unexported fields and methods should not be.
// An Asm is an assembly file being written.
type Asm struct {
Arch *Arch // architecture
out bytes.Buffer // output buffer
regavail uint64 // bitmap of available registers
enabled map[Option]bool // enabled optional CPU features
}
// NewAsm returns a new Asm preparing assembly
// for the given architecture to be written to file.
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
}
// Note: Using Copyright 2025, not the current year, to avoid test failures
// on January 1 and spurious diffs when regenerating assembly.
// The generator was written in 2025; that's good enough.
// (As a matter of policy the Go project does not update copyright
// notices every year, since copyright terms are so long anyway.)
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"
`
// Fatalf reports a fatal error by panicking.
// Panicking is appropriate because there is a bug in the generator,
// and panicking will show the exact source lines leading to that bug.
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)
}
// hint returns the register name for the given hint.
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)
}
// ZR returns the zero register (the specific register guaranteed to hold the integer 0),
// or else the zero Reg (Reg{}, which has r.Valid() == false).
func (a *Asm) ZR() Reg {
return Reg{a.Arch.reg0}
}
// tmp returns the temporary register, or else the zero Reg.
// The temporary register is one available for use implementing logical instructions
// that compile into multiple actual instructions on a given system.
// The assembler sometimes uses it for that purpose, as do we.
// Of course, if we are using it, we'd better not emit an instruction that
// will cause the assembler to smash it while we want it to be holding
// a live value. In general it is the architecture implementation's responsibility
// not to suggest the use of any such pseudo-instructions in situations
// where they would cause problems.
func (a *Asm) tmp() Reg {
return Reg{a.Arch.regTmp}
}
// Carry returns the carry register, or else the zero Reg.
func (a *Asm) Carry() Reg {
return Reg{a.Arch.regCarry}
}
// AltCarry returns the secondary carry register, or else the zero Reg.
func (a *Asm) AltCarry() Reg {
return Reg{a.Arch.regAltCarry}
}
// Imm returns a Reg representing an immediate (constant) value.
func (a *Asm) Imm(x int) Reg {
if x == 0 && a.Arch.reg0 != "" {
return Reg{a.Arch.reg0}
}
return Reg{fmt.Sprintf("$%d", x)}
}
// IsZero reports whether r is a zero immediate or the zero register.
func (a *Asm) IsZero(r Reg) bool {
return r.name == "$0" || a.Arch.reg0 != "" && r.name == a.Arch.reg0
}
// Reg allocates a new register.
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]}
}
// RegHint allocates a new register, with a hint as to its purpose.
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()
}
// Free frees a previously allocated register.
// If r is not a register (if it's an immediate or a memory reference), Free is a no-op.
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
}
// Unfree reallocates a previously freed register r.
// If r is not a register (if it's an immediate or a memory reference), Unfree is a no-op.
// If r is not free for allocation, Unfree panics.
// A Free paired with Unfree can release a register for use temporarily
// but then reclaim it, such as at the end of a loop body when it must be restored.
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
}
// A RegsUsed is a snapshot of which registers are allocated.
type RegsUsed struct {
avail uint64
}
// RegsUsed returns a snapshot of which registers are currently allocated,
// which can be passed to a future call to [Asm.SetRegsUsed].
func (a *Asm) RegsUsed() RegsUsed {
return RegsUsed{a.regavail}
}
// SetRegsUsed sets which registers are currently allocated.
// The argument should have been returned from a previous
// call to [Asm.RegsUsed].
func (a *Asm) SetRegsUsed(used RegsUsed) {
a.regavail = used.avail
}
// FreeAll frees all known registers.
func (a *Asm) FreeAll() {
a.regavail = 1<<len(a.Arch.regs) - 1
}
// Printf emits to the assembly output.
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)
}
// Comment emits a line comment to the assembly output.
func (a *Asm) Comment(format string, args ...any) {
fmt.Fprintf(&a.out, "\t// %s\n", fmt.Sprintf(format, args...))
}
// EOL appends an end-of-line comment to the previous line.
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...)
}
// JmpEnable emits a test for the optional CPU feature that jumps to label if the feature is present.
// If JmpEnable returns false, the feature is not available on this architecture and no code was emitted.
func (a *Asm) JmpEnable(option Option, label string) bool {
jmpEnable := a.Arch.options[option]
if jmpEnable == nil {
return false
}
jmpEnable(a, label)
return true
}
// Enabled reports whether the optional CPU feature is considered
// to be enabled at this point in the assembly output.
func (a *Asm) Enabled(option Option) bool {
return a.enabled[option]
}
// SetOption changes whether the optional CPU feature should be
// considered to be enabled.
func (a *Asm) SetOption(option Option, on bool) {
a.enabled[option] = on
}
// op3 emits a 3-operand instruction op src1, src2, dst,
// taking care to handle 2-operand machines and also
// to simplify the printout when src2==dst.
func (a *Asm) op3(op string, src1, src2, dst Reg) {
if op == "" {
a.Fatalf("missing instruction")
}
if src2 == dst {
// src2 and dst are same; print as 2-op form.
a.Printf("\t%s %s, %s\n", op, src1, dst)
} else if a.Arch.op3 != nil && !a.Arch.op3(op) {
// Machine does not have 3-op form for op; convert to 2-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 {
// Full 3-op form.
a.Printf("\t%s %s, %s, %s\n", op, src1, src2, dst)
}
}
// Mov emits dst = src.
func (a *Asm) Mov(src, dst Reg) {
if src != dst {
a.Printf("\t%s %s, %s\n", a.Arch.mov, src, dst)
}
}
// AddWords emits dst = src1*WordBytes + src2.
// It does not set or use the carry flag.
func (a *Asm) AddWords(src1 Reg, src2, dst RegPtr) {
if a.Arch.addWords == "" {
// Note: Assuming that Lsh does not clobber the carry flag.
// Architectures where this is not true (x86) need to provide 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)
}
// And emits dst = src1 & src2
// It may modify the carry flag.
func (a *Asm) And(src1, src2, dst Reg) {
a.op3(a.Arch.and, src1, src2, dst)
}
// Or emits dst = src1 | src2
// It may modify the carry flag.
func (a *Asm) Or(src1, src2, dst Reg) {
a.op3(a.Arch.or, src1, src2, dst)
}
// Xor emits dst = src1 ^ src2
// It may modify the carry flag.
func (a *Asm) Xor(src1, src2, dst Reg) {
a.op3(a.Arch.xor, src1, src2, dst)
}
// Neg emits dst = -src.
// It may modify the carry flag.
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)
}
}
// HasRegShift reports whether the architecture can use shift expressions as operands.
func (a *Asm) HasRegShift() bool {
return a.Arch.regShift
}
// LshReg returns a shift-expression operand src<<shift.
// If a.HasRegShift() == false, LshReg panics.
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, "$"))}
}
// Lsh emits dst = src << shift.
// It may modify the carry flag.
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)
}
// LshWide emits dst = src << shift with low bits shifted from adj.
// It may modify the carry flag.
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)
}
// RshReg returns a shift-expression operand src>>shift.
// If a.HasRegShift() == false, RshReg panics.
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, "$"))}
}
// Rsh emits dst = src >> shift.
// It may modify the carry flag.
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)
}
// RshWide emits dst = src >> shift with high bits shifted from adj.
// It may modify the carry flag.
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)
}
// SLTU emits dst = src2 < src1 (0 or 1), using an unsigned comparison.
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)
}
}
// Add emits dst = src1+src2, with the specified carry behavior.
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):
// handled
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) // name[1:] removes $
} 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 != "":
// Machine has no carry flag; instead we've dedicated a register
// and use SLTU/SGTU (set less-than/greater-than unsigned)
// to compute the carry flags as needed.
// For ADD x, y, z, SLTU x/y, z, c computes the carry (borrow) bit.
// Either of x or y can be used as the second argument, provided
// it is not aliased to z.
// To make the output less of a wall of instructions,
// we comment the “higher-level” operation, with ... marking
// continued instructions implementing the operation.
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:
// Easy case, just add the carry afterward.
if a.IsZero(src1) {
// Only here to use the carry.
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 {
// Only here to clear the carry flag. (Caller will comment.)
a.Xor(cr, cr, cr)
break
}
var old Reg // old is a src distinct from dst
switch {
case dst != src1:
old = src1
case dst != src2:
old = src2
default:
// src1 == src2 == dst.
// Overflows if and only if the high bit is set, so copy high bit to carry.
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) // dst < old (one of the src) implies carry
a.EOL("...")
case UseCarry | SetCarry:
if a.IsZero(src1) {
// Only here to use and then set the carry.
// Easy since carry is not aliased to dst.
a.Add(cr, src2, dst, KeepCarry)
a.EOL("ADCS $0, %s, %s (cr=%s)", src2, dst, cr)
a.SLTU(cr, dst, cr) // dst < cr implies carry
a.EOL("...")
break
}
// General case. Need to do two different adds (src1 + src2 + cr),
// computing carry bits for both, and add'ing them together.
// Start with src1+src2.
var old Reg // old is a src distinct from dst
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) // // dst < old (one of the src) implies carry
a.EOL("...")
} else {
// src1 == src2 == dst, like above. Sign bit is carry bit,
// but we copy it into tmp, not cr.
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("...")
}
// Add cr to dst.
a.Add(cr, dst, dst, KeepCarry)
a.EOL("...")
a.SLTU(cr, dst, cr) // sum < cr implies carry
a.EOL("...")
// Add the two carry bits (at most one can be set, because (2⁶⁴-1)+(2⁶⁴-1)+1 < 2·2⁶⁴).
a.Add(tmp, cr, cr, KeepCarry)
a.EOL("...")
}
}
}
// Sub emits dst = src2-src1, with the specified carry behavior.
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):
// handled
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):
// Running out of options; if this is an immediate
// and we don't need to worry about carry semantics,
// try adding the negation.
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 != "":
// Machine has no carry flag; instead we've dedicated a register
// and use SLTU/SGTU (set less-than/greater-than unsigned)
// to compute the carry bits as needed.
// For SUB x, y, z, SLTU x, y, c computes the carry (borrow) bit.
// To make the output less of a wall of instructions,
// we comment the “higher-level” operation, with ... marking
// continued instructions implementing the operation.
// Be careful! Subtract and add have different overflow behaviors,
// so the details here are NOT the same as in Add above.
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:
// Easy case, just subtract the carry afterward.
if a.IsZero(src1) {
// Only here to use the carry.
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 {
// Only here to clear the carry flag.
a.Xor(cr, cr, cr)
break
}
// Compute the new carry first, in case dst is src1 or src2.
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) {
// Only here to use and then set the carry.
if src2 == dst {
// Unfortunate case. Using src2==dst is common (think x -= y)
// and also more efficient on two-operand machines (like x86),
// but here subtracting from dst will smash src2, making it
// impossible to recover the carry information after the SUB.
// But we want to use the carry, so we can't compute it before
// the SUB either. Compute into a temporary and MOV.
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) // src2 not dst, so src2 preserved
a.SLTU(cr, src2, cr)
break
}
// General case. Need to do two different subtracts (src2 - cr - src1),
// computing carry bits for both, and add'ing them together.
// Doing src2 - cr first frees up cr to store the carry from the sub of src1.
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("...")
}
}
}
// ClearCarry clears the carry flag.
// The ‘which’ parameter must be AddCarry or SubCarry to specify how the flag will be used.
// (On some systems, the sub carry's actual processor bit is inverted from its usual value.)
func (a *Asm) ClearCarry(which Carry) {
dst := Reg{a.Arch.regs[0]} // not actually modified
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")
}
// SaveCarry saves the carry flag into dst.
// The meaning of the bits in dst is architecture-dependent.
// The carry flag is left in an undefined state.
func (a *Asm) SaveCarry(dst Reg) {
// Note: As implemented here, the carry flag is actually left unmodified,
// but we say it is in an undefined state in case that changes in the future.
// (The SmashCarry could be changed to SetCarry if so.)
if cr := a.Carry(); cr.Valid() {
if cr == dst {
return // avoid EOL
}
a.Mov(cr, dst)
} else {
a.Sub(dst, dst, dst, UseCarry|SmashCarry)
}
a.EOL("save carry")
}
// RestoreCarry restores the carry flag from src.
// src is left in an undefined state.
func (a *Asm) RestoreCarry(src Reg) {
if cr := a.Carry(); cr.Valid() {
if cr == src {
return // avoid EOL
}
a.Mov(src, cr)
} else if a.Arch.subCarryIsBorrow {
a.Add(src, src, src, SetCarry)
} else {
// SaveCarry saved the sub carry flag with an encoding of 0, 1 -> 0, ^0.
// Restore it by subtracting from a value less than ^0, which will carry if src != 0.
// If there is no zero register, the SP register is guaranteed to be less than ^0.
// (This may seem too clever, but on GOARCH=arm we have no other good options.)
a.Sub(src, cmp.Or(a.ZR(), Reg{"SP"}), src, SetCarry)
}
a.EOL("restore carry")
}
// ConvertCarry converts the carry flag in dst from the internal format to a 0 or 1.
// The carry flag is left in an undefined state.
func (a *Asm) ConvertCarry(which Carry, dst Reg) {
if a.Carry().Valid() { // already 0 or 1
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")
}
}
// SaveConvertCarry saves and converts the carry flag into dst: 0 unset, 1 set.
// The carry flag is left in an undefined state.
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:
// no special cases
}
a.SaveCarry(dst)
a.ConvertCarry(which, dst)
}
// MulWide emits dstlo = src1 * src2 and dsthi = (src1 * src2) >> WordBits.
// The carry flag is left in an undefined state.
// If dstlo or dsthi is the zero Reg, then those outputs are discarded.
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)
}
}
// Jmp jumps to the label.
func (a *Asm) Jmp(label string) {
// Note: Some systems prefer the spelling B or BR, but all accept JMP.
a.Printf("\tJMP %s\n", label)
}
// JmpZero jumps to the label if src is zero.
// It may modify the carry flag unless a.Arch.CarrySafeLoop is true.
func (a *Asm) JmpZero(src Reg, label string) {
a.Printf("\t"+a.Arch.jmpZero+"\n", src, label)
}
// JmpNonZero jumps to the label if src is non-zero.
// It may modify the carry flag unless a.Arch,CarrySafeLoop is true.
func (a *Asm) JmpNonZero(src Reg, label string) {
a.Printf("\t"+a.Arch.jmpNonZero+"\n", src, label)
}
// Label emits a label with the given name.
func (a *Asm) Label(name string) {
a.Printf("%s:\n", name)
}
// Ret returns.
func (a *Asm) Ret() {
a.Printf("\tRET\n")
}