// 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 ( "fmt" "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 Arch defines how to generate assembly for a specific architecture. type Arch struct { Name string // name of architecture Build string // build tag WordBits int // length of word in bits (32 or 64) WordBytes int // length of word in bytes (4 or 8) CarrySafeLoop bool // whether loops preserve carry flag across iterations // Registers. regs []string // usable general registers, in allocation order reg0 string // dedicated zero register regCarry string // dedicated carry register, for systems with no hardware carry bits regAltCarry string // dedicated secondary carry register, for systems with no hardware carry bits regTmp string // dedicated temporary register // regShift indicates that the architecture supports // using REG1>>REG2 and REG1< src1), for carry-less systems lea string // load effective address // addF and subF implement a.Add and a.Sub // on systems where the situation is more complicated than // the six basic instructions (add, adds, adcs, sub, subs, sbcs). // They return a boolean indicating whether the operation was handled. addF func(a *Asm, src1, src2, dst Reg, carry Carry) bool subF func(a *Asm, src1, src2, dst Reg, carry Carry) bool // mulF and mulWideF implement Mul and MulWide. // They call Fatalf if the operation is unsupported. // An architecture can set the mul field instead of mulF. // mulWide is optional, but otherwise mulhi should be set. mulWideF func(a *Asm, src1, src2, dstlo, dsthi Reg) // addWords is a printf format taking src1, src2, dst // and sets dst = WordBytes*src1+src2. // It may modify the carry flag. addWords string // subCarryIsBorrow is true when the actual processor carry bit used in subtraction // is really a “borrow” bit, meaning 1 means borrow and 0 means no borrow. // In contrast, most systems (except x86) use a carry bit with the opposite // meaning: 0 means a borrow happened, and 1 means it didn't. subCarryIsBorrow bool // Jump instruction printf formats. // jmpZero and jmpNonZero are printf formats taking src, label // and jump to label if src is zero / non-zero. jmpZero string jmpNonZero string // loopTop is a printf format taking src, label that should // jump to label if src is zero, or else set up for a loop. // If loopTop is not set, jmpZero is used. loopTop string // loopBottom is a printf format taking dst, label that should // decrement dst and then jump to label if src is non-zero. // If loopBottom is not set, a subtraction is used followed by // use of jmpNonZero. loopBottom string // loopBottomNeg is like loopBottom but used in negative-index // loops, which only happen memIndex is also set (only on 386). // It increments dst instead of decrementing it. loopBottomNeg string // Indexed memory access. // If set, memIndex returns a memory reference for a mov instruction // addressing off(ptr)(ix*WordBytes). // Using memIndex costs an extra register but allows the end-of-loop // to do a single increment/decrement instead of advancing two or three pointers. // This is particularly important on 386. memIndex func(a *Asm, off int, ix Reg, ptr RegPtr) Reg // Incrementing/decrementing memory access. // loadIncN loads memory at ptr into regs, incrementing ptr by WordBytes after each reg. // loadDecN loads memory at ptr into regs, decrementing ptr by WordBytes before each reg. // storeIncN and storeDecN are the same, but storing from regs instead of loading into regs. // If missing, the assembler accesses memory and advances pointers using separate instructions. loadIncN func(a *Asm, ptr RegPtr, regs []Reg) loadDecN func(a *Asm, ptr RegPtr, regs []Reg) storeIncN func(a *Asm, ptr RegPtr, regs []Reg) storeDecN func(a *Asm, ptr RegPtr, regs []Reg) // options is a map from optional CPU features to functions that test for them. // The test function should jump to label if the feature is available. options map[Option]func(a *Asm, label string) } // HasShiftWide reports whether the Arch has working LshWide/RshWide instructions. // If not, calling them will panic. func (a *Arch) HasShiftWide() bool { return a.lshd != "" } // A Hint is a hint about what a register will be used for, // so that an appropriate one can be selected. type Hint uint const ( HintNone Hint = iota HintShiftCount // shift count (CX on x86) HintMulSrc // mul source operand (AX on x86) HintMulHi // wide mul high output (DX on x86) HintMemOK // a memory reference is okay HintCarry // carry flag HintAltCarry // secondary carry flag ) // A Reg is an allocated register or other assembly operand. // (For example, a constant might have name "$123" // and a memory reference might have name "0(R8)".) type Reg struct{ name string } // IsImm reports whether r is an immediate value. func (r Reg) IsImm() bool { return strings.HasPrefix(r.name, "$") } // IsMem reports whether r is a memory value. func (r Reg) IsMem() bool { return strings.HasSuffix(r.name, ")") } // String returns the assembly syntax for r. func (r Reg) String() string { return r.name } // Valid reports whether is valid, meaning r is not the zero value of Reg (a register with no name). func (r Reg) Valid() bool { return r.name != "" } // A RegPtr is like a Reg but expected to hold a pointer. // The separate Go type helps keeps pointers and scalars separate and avoid mistakes; // it is okay to convert to Reg as needed to use specific routines. type RegPtr struct{ name string } // String returns the assembly syntax for r. func (r RegPtr) String() string { return r.name } // Valid reports whether is valid, meaning r is not the zero value of RegPtr (a register with no name). func (r RegPtr) Valid() bool { return r.name != "" } // mem returns a memory reference to off bytes from the pointer r. func (r *RegPtr) mem(off int) Reg { return Reg{fmt.Sprintf("%d(%s)", off, r)} } // A Carry is a flag field explaining how an instruction sets and uses the carry flags. // Different operations expect different sets of bits. // Add and Sub expect: UseCarry or 0, SetCarry, KeepCarry, or SmashCarry; and AltCarry or 0. // ClearCarry, SaveCarry, and ConvertCarry expect: AddCarry or SubCarry; and AltCarry or 0. type Carry uint const ( SetCarry Carry = 1 << iota // sets carry UseCarry // uses carry KeepCarry // must preserve carry SmashCarry // can modify carry or not, whatever is easiest AltCarry // use the secondary carry flag AddCarry // use add carry flag semantics (for ClearCarry, ConvertCarry) SubCarry // use sub carry flag semantics (for ClearCarry, ConvertCarry) ) // An Option denotes an optional CPU feature that can be tested at runtime. type Option int const ( _ Option = iota // OptionAltCarry checks whether there is an add instruction // that uses a secondary carry flag, so that two different sums // can be accumulated in parallel with independent carry flags. // Some architectures (MIPS, Loong64, RISC-V) provide this // functionality natively, indicated by asm.Carry().Valid() being true. OptionAltCarry )