| // 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") |
| } |