// 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 asmgenimport ()// 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.typeAsmstruct { 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 ( *Arch) *Asm { := &Asm{Arch: , enabled: make(map[Option]bool)} := ""if .Build != "" { = " && (" + .Build + ")" } .Printf(asmHeader, )return}// 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 ( *Asm) ( string, ...any) { := .out.String() := strings.LastIndex(, "\nTEXT") = [+1:]panic("[" + .Arch.Name + "] asmgen internal error: " + fmt.Sprintf(, ...) + "\n" + )}// hint returns the register name for the given hint.func ( *Asm) ( Hint) string {if == HintCarry && .Arch.regCarry != "" {return .Arch.regCarry }if == HintAltCarry && .Arch.regAltCarry != "" {return .Arch.regAltCarry }if == HintNone || .Arch.hint == nil {return"" }return .Arch.hint(, )}// 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 ( *Asm) () Reg {returnReg{.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 ( *Asm) () Reg {returnReg{.Arch.regTmp}}// Carry returns the carry register, or else the zero Reg.func ( *Asm) () Reg {returnReg{.Arch.regCarry}}// AltCarry returns the secondary carry register, or else the zero Reg.func ( *Asm) () Reg {returnReg{.Arch.regAltCarry}}// Imm returns a Reg representing an immediate (constant) value.func ( *Asm) ( int) Reg {if == 0 && .Arch.reg0 != "" {returnReg{.Arch.reg0} }returnReg{fmt.Sprintf("$%d", )}}// IsZero reports whether r is a zero immediate or the zero register.func ( *Asm) ( Reg) bool {return .name == "$0" || .Arch.reg0 != "" && .name == .Arch.reg0}// Reg allocates a new register.func ( *Asm) () Reg { := bits.TrailingZeros64(.regavail)if == 64 { .Fatalf("out of registers") } .regavail ^= 1 << returnReg{.Arch.regs[]}}// RegHint allocates a new register, with a hint as to its purpose.func ( *Asm) ( Hint) Reg {if := .hint(); != "" { := slices.Index(.Arch.regs, )if < 0 {returnReg{} }if .regavail&(1<<) == 0 { .Fatalf("hint for already allocated register %s", ) } .regavail &^= 1 << returnReg{} }return .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 ( *Asm) ( Reg) { := slices.Index(.Arch.regs, .name)if < 0 {return }if .regavail&(1<<) != 0 { .Fatalf("register %s already freed", .name) } .regavail |= 1 << }// 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 ( *Asm) ( Reg) { := slices.Index(.Arch.regs, .name)if < 0 {return }if .regavail&(1<<) == 0 { .Fatalf("register %s not free", .name) } .regavail &^= 1 << }// A RegsUsed is a snapshot of which registers are allocated.typeRegsUsedstruct { avail uint64}// RegsUsed returns a snapshot of which registers are currently allocated,// which can be passed to a future call to [Asm.SetRegsUsed].func ( *Asm) () RegsUsed {returnRegsUsed{.regavail}}// SetRegsUsed sets which registers are currently allocated.// The argument should have been returned from a previous// call to [Asm.RegsUsed].func ( *Asm) ( RegsUsed) { .regavail = .avail}// FreeAll frees all known registers.func ( *Asm) () { .regavail = 1<<len(.Arch.regs) - 1}// Printf emits to the assembly output.func ( *Asm) ( string, ...any) { := fmt.Sprintf(, ...)ifstrings.Contains(, "%!") { .Fatalf("printf error: %s", ) } .out.WriteString()}// Comment emits a line comment to the assembly output.func ( *Asm) ( string, ...any) {fmt.Fprintf(&.out, "\t// %s\n", fmt.Sprintf(, ...))}// EOL appends an end-of-line comment to the previous line.func ( *Asm) ( string, ...any) { := .out.Bytes()iflen() > 0 && [len()-1] == '\n' { .out.Truncate(.out.Len() - 1) } .Comment(, ...)}// 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 ( *Asm) ( Option, string) bool { := .Arch.options[]if == nil {returnfalse } (, )returntrue}// Enabled reports whether the optional CPU feature is considered// to be enabled at this point in the assembly output.func ( *Asm) ( Option) bool {return .enabled[]}// SetOption changes whether the optional CPU feature should be// considered to be enabled.func ( *Asm) ( Option, bool) { .enabled[] = }// 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 ( *Asm) ( string, , , Reg) {if == "" { .Fatalf("missing instruction") }if == {// src2 and dst are same; print as 2-op form. .Printf("\t%s %s, %s\n", , , ) } elseif .Arch.op3 != nil && !.Arch.op3() {// Machine does not have 3-op form for op; convert to 2-op.if == { .Fatalf("implicit mov %s, %s would smash src1", , ) } .Mov(, ) .Printf("\t%s %s, %s\n", , , ) } else {// Full 3-op form. .Printf("\t%s %s, %s, %s\n", , , , ) }}// Mov emits dst = src.func ( *Asm) (, Reg) {if != { .Printf("\t%s %s, %s\n", .Arch.mov, , ) }}// AddWords emits dst = src1*WordBytes + src2.// It does not set or use the carry flag.func ( *Asm) ( Reg, , RegPtr) {if .Arch.addWords == "" {// Note: Assuming that Lsh does not clobber the carry flag. // Architectures where this is not true (x86) need to provide Arch.addWords. := .Reg() .Lsh(.Imm(bits.TrailingZeros(uint(.Arch.WordBytes))), , ) .Add(, Reg(), Reg(), KeepCarry) .Free()return } .Printf("\t"+.Arch.addWords+"\n", , , )}// And emits dst = src1 & src2// It may modify the carry flag.func ( *Asm) (, , Reg) { .op3(.Arch.and, , , )}// Or emits dst = src1 | src2// It may modify the carry flag.func ( *Asm) (, , Reg) { .op3(.Arch.or, , , )}// Xor emits dst = src1 ^ src2// It may modify the carry flag.func ( *Asm) (, , Reg) { .op3(.Arch.xor, , , )}// Neg emits dst = -src.// It may modify the carry flag.func ( *Asm) (, Reg) {if .Arch.neg == "" {if .Arch.rsb != "" { .Printf("\t%s $0, %s, %s\n", .Arch.rsb, , )return }if .Arch.sub != "" && .Arch.reg0 != "" { .Printf("\t%s %s, %s, %s\n", .Arch.sub, , .Arch.reg0, )return } .Fatalf("missing neg") }if == { .Printf("\t%s %s\n", .Arch.neg, ) } else { .Printf("\t%s %s, %s\n", .Arch.neg, , ) }}// HasRegShift reports whether the architecture can use shift expressions as operands.func ( *Asm) () bool {return .Arch.regShift}// LshReg returns a shift-expression operand src<<shift.// If a.HasRegShift() == false, LshReg panics.func ( *Asm) (, Reg) Reg {if !.HasRegShift() { .Fatalf("no reg shift") }returnReg{fmt.Sprintf("%s<<%s", , strings.TrimPrefix(.name, "$"))}}// Lsh emits dst = src << shift.// It may modify the carry flag.func ( *Asm) (, , Reg) {if := .hint(HintShiftCount); != "" && .name != && !.IsImm() { .Fatalf("shift count not in %s", ) }if .HasRegShift() { .Mov(.LshReg(, ), )return } .op3(.Arch.lsh, , , )}// LshWide emits dst = src << shift with low bits shifted from adj.// It may modify the carry flag.func ( *Asm) (, , , Reg) {if .Arch.lshd == "" { .Fatalf("no lshwide on %s", .Arch.Name) }if := .hint(HintShiftCount); != "" && .name != && !.IsImm() { .Fatalf("shift count not in %s", ) } .op3(fmt.Sprintf("%s %s,", .Arch.lshd, ), , , )}// RshReg returns a shift-expression operand src>>shift.// If a.HasRegShift() == false, RshReg panics.func ( *Asm) (, Reg) Reg {if !.HasRegShift() { .Fatalf("no reg shift") }returnReg{fmt.Sprintf("%s>>%s", , strings.TrimPrefix(.name, "$"))}}// Rsh emits dst = src >> shift.// It may modify the carry flag.func ( *Asm) (, , Reg) {if := .hint(HintShiftCount); != "" && .name != && !.IsImm() { .Fatalf("shift count not in %s", ) }if .HasRegShift() { .Mov(.RshReg(, ), )return } .op3(.Arch.rsh, , , )}// RshWide emits dst = src >> shift with high bits shifted from adj.// It may modify the carry flag.func ( *Asm) (, , , Reg) {if .Arch.lshd == "" { .Fatalf("no rshwide on %s", .Arch.Name) }if := .hint(HintShiftCount); != "" && .name != && !.IsImm() { .Fatalf("shift count not in %s", ) } .op3(fmt.Sprintf("%s %s,", .Arch.rshd, ), , , )}// SLTU emits dst = src2 < src1 (0 or 1), using an unsigned comparison.func ( *Asm) (, , Reg) {switch {default: .Fatalf("arch has no sltu/sgtu")case .Arch.sltu != "": .Printf("\t%s %s, %s, %s\n", .Arch.sltu, , , )case .Arch.sgtu != "": .Printf("\t%s %s, %s, %s\n", .Arch.sgtu, , , ) }}// Add emits dst = src1+src2, with the specified carry behavior.func ( *Asm) (, , Reg, Carry) {switch {default: .Fatalf("unsupported carry behavior")case .Arch.addF != nil && .Arch.addF(, , , , ):// handledcase .Arch.add != "" && ( == KeepCarry || == SmashCarry): .op3(.Arch.add, , , )case .Arch.adds != "" && ( == SetCarry || == SmashCarry): .op3(.Arch.adds, , , )case .Arch.adc != "" && ( == UseCarry || == UseCarry|SmashCarry): .op3(.Arch.adc, , , )case .Arch.adcs != "" && ( == UseCarry|SetCarry || == UseCarry|SmashCarry): .op3(.Arch.adcs, , , )case .Arch.lea != "" && ( == KeepCarry || == SmashCarry):if .IsImm() { .Printf("\t%s %s(%s), %s\n", .Arch.lea, .name[1:], , ) // name[1:] removes $ } else { .Printf("\t%s (%s)(%s), %s\n", .Arch.lea, , , ) }if == { .EOL("ADD %s, %s", , ) } else { .EOL("ADD %s, %s, %s", , , ) }case .Arch.add != "" && .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. := .Carry()if &AltCarry != 0 { = .AltCarry()if !.Valid() { .Fatalf("alt carry not supported") } &^= AltCarry } := .tmp()if !.Valid() { .Fatalf("cannot simulate sub carry without regTmp") }switch {default: .Fatalf("unsupported carry behavior")caseUseCarry, UseCarry | SmashCarry:// Easy case, just add the carry afterward.if .IsZero() {// Only here to use the carry. .(, , , KeepCarry) .EOL("ADC $0, %s, %s", , )break } .(, , , KeepCarry) .EOL("ADC %s, %s, %s (cr=%s)", , , , ) .(, , , KeepCarry) .EOL("...")caseSetCarry:if .IsZero() && == {// Only here to clear the carry flag. (Caller will comment.) .Xor(, , )break }varReg// old is a src distinct from dstswitch {case != : = case != : = default:// src1 == src2 == dst. // Overflows if and only if the high bit is set, so copy high bit to carry. .Rsh(.Imm(.Arch.WordBits-1), , ) .EOL("ADDS %s, %s, %s (cr=%s)", , , , ) .(, , , KeepCarry) .EOL("...")return } .(, , , KeepCarry) .EOL("ADDS %s, %s, %s (cr=%s)", , , , ) .SLTU(, , ) // dst < old (one of the src) implies carry .EOL("...")caseUseCarry | SetCarry:if .IsZero() {// Only here to use and then set the carry. // Easy since carry is not aliased to dst. .(, , , KeepCarry) .EOL("ADCS $0, %s, %s (cr=%s)", , , ) .SLTU(, , ) // dst < cr implies carry .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.varReg// old is a src distinct from dstswitch {case != : = case != : = }if .Valid() { .(, , , KeepCarry) .EOL("ADCS %s, %s, %s (cr=%s)", , , , ) .SLTU(, , ) // // dst < old (one of the src) implies carry .EOL("...") } else {// src1 == src2 == dst, like above. Sign bit is carry bit, // but we copy it into tmp, not cr. .Rsh(.Imm(.Arch.WordBits-1), , ) .EOL("ADCS %s, %s, %s (cr=%s)", , , , ) .(, , , KeepCarry) .EOL("...") }// Add cr to dst. .(, , , KeepCarry) .EOL("...") .SLTU(, , ) // sum < cr implies carry .EOL("...")// Add the two carry bits (at most one can be set, because (2⁶⁴-1)+(2⁶⁴-1)+1 < 2·2⁶⁴). .(, , , KeepCarry) .EOL("...") } }}// Sub emits dst = src2-src1, with the specified carry behavior.func ( *Asm) (, , Reg, Carry) {switch {default: .Fatalf("unsupported carry behavior")case .Arch.subF != nil && .Arch.subF(, , , , ):// handledcase .Arch.sub != "" && ( == KeepCarry || == SmashCarry): .op3(.Arch.sub, , , )case .Arch.subs != "" && ( == SetCarry || == SmashCarry): .op3(.Arch.subs, , , )case .Arch.sbc != "" && ( == UseCarry || == UseCarry|SmashCarry): .op3(.Arch.sbc, , , )case .Arch.sbcs != "" && ( == UseCarry|SetCarry || == UseCarry|SmashCarry): .op3(.Arch.sbcs, , , )casestrings.HasPrefix(.name, "$") && ( == KeepCarry || == SmashCarry):// Running out of options; if this is an immediate // and we don't need to worry about carry semantics, // try adding the negation.ifstrings.HasPrefix(.name, "$-") { .name = "$" + .name[2:] } else { .name = "$-" + .name[1:] } .Add(, , , )case .Arch.sub != "" && .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. := .Carry()if &AltCarry != 0 { .Fatalf("alt carry not supported") } := .tmp()if !.Valid() { .Fatalf("cannot simulate carry without regTmp") }switch {default: .Fatalf("unsupported carry behavior")caseUseCarry, UseCarry | SmashCarry:// Easy case, just subtract the carry afterward.if .IsZero() {// Only here to use the carry. .(, , , KeepCarry) .EOL("SBC $0, %s, %s", , )break } .(, , , KeepCarry) .EOL("SBC %s, %s, %s", , , ) .(, , , KeepCarry) .EOL("...")caseSetCarry:if .IsZero() && == {// Only here to clear the carry flag. .Xor(, , )break }// Compute the new carry first, in case dst is src1 or src2. .SLTU(, , ) .EOL("SUBS %s, %s, %s", , , ) .(, , , KeepCarry) .EOL("...")caseUseCarry | SetCarry:if .IsZero() {// Only here to use and then set the carry.if == {// 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. .SLTU(, , ) .EOL("SBCS $0, %s, %s", , ) .(, , , KeepCarry) .EOL("...") .Mov(, ) .EOL("...")break } .(, , , KeepCarry) // src2 not dst, so src2 preserved .SLTU(, , )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. .SLTU(, , ) .EOL("SBCS %s, %s, %s", , , ) .(, , , KeepCarry) .EOL("...") .SLTU(, , ) .EOL("...") .(, , , KeepCarry) .EOL("...") .Add(, , , KeepCarry) .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 ( *Asm) ( Carry) { := Reg{.Arch.regs[0]} // not actually modifiedswitch & (AddCarry | SubCarry) {default: .Fatalf("bad carry")caseAddCarry: .Add(.Imm(0), , , SetCarry|&AltCarry)caseSubCarry: .Sub(.Imm(0), , , SetCarry|&AltCarry) } .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 ( *Asm) ( 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 := .Carry(); .Valid() {if == {return// avoid EOL } .Mov(, ) } else { .Sub(, , , UseCarry|SmashCarry) } .EOL("save carry")}// RestoreCarry restores the carry flag from src.// src is left in an undefined state.func ( *Asm) ( Reg) {if := .Carry(); .Valid() {if == {return// avoid EOL } .Mov(, ) } elseif .Arch.subCarryIsBorrow { .Add(, , , 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.) .Sub(, cmp.Or(.ZR(), Reg{"SP"}), , SetCarry) } .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 ( *Asm) ( Carry, Reg) {if .Carry().Valid() { // already 0 or 1return }switch {caseAddCarry:if .Arch.subCarryIsBorrow { .Neg(, ) } else { .Add(.Imm(1), , , SmashCarry) } .EOL("convert add carry")caseSubCarry: .Neg(, ) .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 ( *Asm) ( Carry, Reg) {switch {default: .Fatalf("bad carry")caseAddCarry:if (.Arch.adc != "" || .Arch.adcs != "") && .ZR().Valid() { .Add(.ZR(), .ZR(), , UseCarry|SmashCarry) .EOL("save & convert add carry")return }caseSubCarry:// no special cases } .SaveCarry() .ConvertCarry(, )}// 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 ( *Asm) (, , , Reg) {switch {default: .Fatalf("mulwide not available")case .Arch.mulWideF != nil: .Arch.mulWideF(, , , , )case .Arch.mul != "" && !.Valid(): .op3(.Arch.mul, , , )case .Arch.mulhi != "" && !.Valid(): .op3(.Arch.mulhi, , , )case .Arch.mul != "" && .Arch.mulhi != "" && != && != : .op3(.Arch.mul, , , ) .op3(.Arch.mulhi, , , )case .Arch.mul != "" && .Arch.mulhi != "" && != && != : .op3(.Arch.mulhi, , , ) .op3(.Arch.mul, , , ) }}// Jmp jumps to the label.func ( *Asm) ( string) {// Note: Some systems prefer the spelling B or BR, but all accept JMP. .Printf("\tJMP %s\n", )}// JmpZero jumps to the label if src is zero.// It may modify the carry flag unless a.Arch.CarrySafeLoop is true.func ( *Asm) ( Reg, string) { .Printf("\t"+.Arch.jmpZero+"\n", , )}// JmpNonZero jumps to the label if src is non-zero.// It may modify the carry flag unless a.Arch,CarrySafeLoop is true.func ( *Asm) ( Reg, string) { .Printf("\t"+.Arch.jmpNonZero+"\n", , )}// Label emits a label with the given name.func ( *Asm) ( string) { .Printf("%s:\n", )}// Ret returns.func ( *Asm) () { .Printf("\tRET\n")}
The pages are generated with Goldsv0.7.7-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.