arch.mx raw

   1  // Copyright 2025 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package asmgen
   6  
   7  import (
   8  	"fmt"
   9  	"bytes"
  10  )
  11  
  12  // Note: Exported fields and methods are expected to be used
  13  // by function generators (like the ones in add.go and so on).
  14  // Unexported fields and methods should not be.
  15  
  16  // An Arch defines how to generate assembly for a specific architecture.
  17  type Arch struct {
  18  	Name          []byte // name of architecture
  19  	Build         []byte // build tag
  20  	WordBits      int    // length of word in bits (32 or 64)
  21  	WordBytes     int    // length of word in bytes (4 or 8)
  22  	CarrySafeLoop bool   // whether loops preserve carry flag across iterations
  23  
  24  	// Registers.
  25  	regs        [][]byte // usable general registers, in allocation order
  26  	reg0        []byte   // dedicated zero register
  27  	regCarry    []byte   // dedicated carry register, for systems with no hardware carry bits
  28  	regAltCarry []byte   // dedicated secondary carry register, for systems with no hardware carry bits
  29  	regTmp      []byte   // dedicated temporary register
  30  
  31  	// regShift indicates that the architecture supports
  32  	// using REG1>>REG2 and REG1<<REG2 as the first source
  33  	// operand in an arithmetic instruction. (32-bit ARM does this.)
  34  	regShift bool
  35  
  36  	// setup is called to emit any per-architecture function prologue,
  37  	// immediately after the TEXT line has been emitted.
  38  	// If setup is nil, it is taken to be a no-op.
  39  	setup func(*Func)
  40  
  41  	// hint returns the register to use for a given hint.
  42  	// Returning an empty string indicates no preference.
  43  	// If hint is nil, it is considered to return an empty string.
  44  	hint func(*Asm, Hint) []byte
  45  
  46  	// op3 reports whether the named opcode accepts 3 operands
  47  	// (true on most instructions on most systems, but not true of x86 instructions).
  48  	// The assembler unconditionally turns op x,z,z into op x,z.
  49  	// If op3 returns false, then the assembler will turn op x,y,z into mov y,z; op x,z.
  50  	// If op3 is nil, then all opcodes are assumed to accept 3 operands.
  51  	op3 func(name []byte) bool
  52  
  53  	// memOK indicates that arithmetic instructions can use memory references (like on x86)
  54  	memOK bool
  55  
  56  	// maxColumns is the default maximum number of vector columns
  57  	// to process in a single [Pipe.Loop] block.
  58  	// 0 means unlimited.
  59  	// [Pipe.SetMaxColumns] overrides this.
  60  	maxColumns int
  61  
  62  	// Instruction names.
  63  	mov   []byte // move (word-sized)
  64  	add   []byte // add with no carry involvement
  65  	adds  []byte // add, setting but not using carry
  66  	adc   []byte // add, using but not setting carry
  67  	adcs  []byte // add, setting and using carry
  68  	sub   []byte // sub with no carry involvement
  69  	subs  []byte // sub, setting but not using carry
  70  	sbc   []byte // sub, using but not setting carry
  71  	sbcs  []byte // sub, setting and using carry
  72  	mul   []byte // multiply
  73  	mulhi []byte // multiply producing high bits
  74  	lsh   []byte // left shift
  75  	lshd  []byte // double-width left shift
  76  	rsh   []byte // right shift
  77  	rshd  []byte // double-width right shift
  78  	and   []byte // bitwise and
  79  	or    []byte // bitwise or
  80  	xor   []byte // bitwise xor
  81  	neg   []byte // negate
  82  	rsb   []byte // reverse subtract
  83  	sltu  []byte // set less-than unsigned (dst = src2 < src1), for carry-less systems
  84  	sgtu  []byte // set greater-than unsigned (dst = src2 > src1), for carry-less systems
  85  	lea   []byte // load effective address
  86  
  87  	// addF and subF implement a.Add and a.Sub
  88  	// on systems where the situation is more complicated than
  89  	// the six basic instructions (add, adds, adcs, sub, subs, sbcs).
  90  	// They return a boolean indicating whether the operation was handled.
  91  	addF func(a *Asm, src1, src2, dst Reg, carry Carry) bool
  92  	subF func(a *Asm, src1, src2, dst Reg, carry Carry) bool
  93  
  94  	// mulF and mulWideF implement Mul and MulWide.
  95  	// They call Fatalf if the operation is unsupported.
  96  	// An architecture can set the mul field instead of mulF.
  97  	// mulWide is optional, but otherwise mulhi should be set.
  98  	mulWideF func(a *Asm, src1, src2, dstlo, dsthi Reg)
  99  
 100  	// addWords is a printf format taking src1, src2, dst
 101  	// and sets dst = WordBytes*src1+src2.
 102  	// It may modify the carry flag.
 103  	addWords []byte
 104  
 105  	// subCarryIsBorrow is true when the actual processor carry bit used in subtraction
 106  	// is really a “borrow” bit, meaning 1 means borrow and 0 means no borrow.
 107  	// In contrast, most systems (except x86) use a carry bit with the opposite
 108  	// meaning: 0 means a borrow happened, and 1 means it didn't.
 109  	subCarryIsBorrow bool
 110  
 111  	// Jump instruction printf formats.
 112  	// jmpZero and jmpNonZero are printf formats taking src, label
 113  	// and jump to label if src is zero / non-zero.
 114  	jmpZero    []byte
 115  	jmpNonZero []byte
 116  
 117  	// loopTop is a printf format taking src, label that should
 118  	// jump to label if src is zero, or else set up for a loop.
 119  	// If loopTop is not set, jmpZero is used.
 120  	loopTop []byte
 121  
 122  	// loopBottom is a printf format taking dst, label that should
 123  	// decrement dst and then jump to label if src is non-zero.
 124  	// If loopBottom is not set, a subtraction is used followed by
 125  	// use of jmpNonZero.
 126  	loopBottom []byte
 127  
 128  	// loopBottomNeg is like loopBottom but used in negative-index
 129  	// loops, which only happen memIndex is also set (only on 386).
 130  	// It increments dst instead of decrementing it.
 131  	loopBottomNeg []byte
 132  
 133  	// Indexed memory access.
 134  	// If set, memIndex returns a memory reference for a mov instruction
 135  	// addressing off(ptr)(ix*WordBytes).
 136  	// Using memIndex costs an extra register but allows the end-of-loop
 137  	// to do a single increment/decrement instead of advancing two or three pointers.
 138  	// This is particularly important on 386.
 139  	memIndex func(a *Asm, off int, ix Reg, ptr RegPtr) Reg
 140  
 141  	// Incrementing/decrementing memory access.
 142  	// loadIncN loads memory at ptr into regs, incrementing ptr by WordBytes after each reg.
 143  	// loadDecN loads memory at ptr into regs, decrementing ptr by WordBytes before each reg.
 144  	// storeIncN and storeDecN are the same, but storing from regs instead of loading into regs.
 145  	// If missing, the assembler accesses memory and advances pointers using separate instructions.
 146  	loadIncN  func(a *Asm, ptr RegPtr, regs []Reg)
 147  	loadDecN  func(a *Asm, ptr RegPtr, regs []Reg)
 148  	storeIncN func(a *Asm, ptr RegPtr, regs []Reg)
 149  	storeDecN func(a *Asm, ptr RegPtr, regs []Reg)
 150  
 151  	// options is a map from optional CPU features to functions that test for them.
 152  	// The test function should jump to label if the feature is available.
 153  	options map[Option]func(a *Asm, label []byte)
 154  }
 155  
 156  // HasShiftWide reports whether the Arch has working LshWide/RshWide instructions.
 157  // If not, calling them will panic.
 158  func (a *Arch) HasShiftWide() bool {
 159  	return a.lshd != ""
 160  }
 161  
 162  // A Hint is a hint about what a register will be used for,
 163  // so that an appropriate one can be selected.
 164  type Hint uint
 165  
 166  const (
 167  	HintNone       Hint = iota
 168  	HintShiftCount      // shift count (CX on x86)
 169  	HintMulSrc          // mul source operand (AX on x86)
 170  	HintMulHi           // wide mul high output (DX on x86)
 171  	HintMemOK           // a memory reference is okay
 172  	HintCarry           // carry flag
 173  	HintAltCarry        // secondary carry flag
 174  )
 175  
 176  // A Reg is an allocated register or other assembly operand.
 177  // (For example, a constant might have name "$123"
 178  // and a memory reference might have name "0(R8)".)
 179  type Reg struct{ name []byte }
 180  
 181  // IsImm reports whether r is an immediate value.
 182  func (r Reg) IsImm() bool { return bytes.HasPrefix(r.name, "$") }
 183  
 184  // IsMem reports whether r is a memory value.
 185  func (r Reg) IsMem() bool { return bytes.HasSuffix(r.name, ")") }
 186  
 187  // String returns the assembly syntax for r.
 188  func (r Reg) String() string { return r.name }
 189  
 190  // Valid reports whether is valid, meaning r is not the zero value of Reg (a register with no name).
 191  func (r Reg) Valid() bool { return r.name != "" }
 192  
 193  // A RegPtr is like a Reg but expected to hold a pointer.
 194  // The separate Go type helps keeps pointers and scalars separate and avoid mistakes;
 195  // it is okay to convert to Reg as needed to use specific routines.
 196  type RegPtr struct{ name []byte }
 197  
 198  // String returns the assembly syntax for r.
 199  func (r RegPtr) String() string { return r.name }
 200  
 201  // Valid reports whether is valid, meaning r is not the zero value of RegPtr (a register with no name).
 202  func (r RegPtr) Valid() bool { return r.name != "" }
 203  
 204  // mem returns a memory reference to off bytes from the pointer r.
 205  func (r *RegPtr) mem(off int) Reg { return Reg{fmt.Sprintf("%d(%s)", off, r)} }
 206  
 207  // A Carry is a flag field explaining how an instruction sets and uses the carry flags.
 208  // Different operations expect different sets of bits.
 209  // Add and Sub expect: UseCarry or 0, SetCarry, KeepCarry, or SmashCarry; and AltCarry or 0.
 210  // ClearCarry, SaveCarry, and ConvertCarry expect: AddCarry or SubCarry; and AltCarry or 0.
 211  type Carry uint
 212  
 213  const (
 214  	SetCarry   Carry = 1 << iota // sets carry
 215  	UseCarry                     // uses carry
 216  	KeepCarry                    // must preserve carry
 217  	SmashCarry                   // can modify carry or not, whatever is easiest
 218  
 219  	AltCarry // use the secondary carry flag
 220  	AddCarry // use add carry flag semantics (for ClearCarry, ConvertCarry)
 221  	SubCarry // use sub carry flag semantics (for ClearCarry, ConvertCarry)
 222  )
 223  
 224  // An Option denotes an optional CPU feature that can be tested at runtime.
 225  type Option int
 226  
 227  const (
 228  	_ Option = iota
 229  
 230  	// OptionAltCarry checks whether there is an add instruction
 231  	// that uses a secondary carry flag, so that two different sums
 232  	// can be accumulated in parallel with independent carry flags.
 233  	// Some architectures (MIPS, Loong64, RISC-V) provide this
 234  	// functionality natively, indicated by asm.Carry().Valid() being true.
 235  	OptionAltCarry
 236  )
 237