blob: dd9c802aab6e369d42a1ae2a09f52b7dced638ca [file] [edit]
// Copyright 2026 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 arm64
import (
"cmd/internal/obj"
"fmt"
"iter"
)
// instEncoder represents an instruction encoder.
type instEncoder struct {
goOp obj.As // Go opcode mnemonic
fixedBits uint32 // Known bits
args []operand // Operands, in Go order
}
type varBits struct {
// The low and high bit index in the binary encoding, exclusive on hi
lo, hi int
encoded bool // If true then its value is already encoded
bits uint32
}
// component is the component of an binary encoding.
// e.g. for operand <Zda>.<T>, <T>'s encoding function might be described as:
//
// For the "Byte and halfword" variant: is the size specifier,
// sz <T>
// 0 B
// 1 H
// bit range mappings:
// sz: [22:23)
//
// Then sz is the component of the binary encoding.
type component uint16
type elemEncoder struct {
fn func(uint32) (uint32, bool)
// comp is the component of the binary encoding.
comp component
}
// operand is the operand type of an instruction.
type operand struct {
class AClass // Operand class, register, constant, memory operation etc.
// The elements that this operand includes, this only includes the encoding-related parts
// They are represented as a list of pointers to the encoding functions.
// The first returned value is the encoded binary, the second is the ok signal.
// The encoding functions return the ok signal for deduplication purposes:
// For example:
// SDOT <Zda>.<T>, <Zn>.<Tb>, <Zm>.<Tb>
// SDOT <Zda>.H, <Zn>.B, <Zm>.B
// SDOT <Zda>.S, <Zn>.H, <Zm>.H
//
// <T> and <Tb> are specified in the encoding text, that there is a constraint "T = 4*Tb".
// We don't know this fact by looking at the encoding format solely, without this information
// the first encoding domain entails the other 2. And at instruction matching phase we simply
// cannot deduplicate them. So we defer this deduplication to the encoding phase.
// We need the ok signal with [elemEncoder.comp] field to deduplicate them.
elemEncoders []elemEncoder
}
// opsInProg returns an iterator over the operands ([Addr]) of p
func opsInProg(p *obj.Prog) iter.Seq[*obj.Addr] {
return func(yield func(*obj.Addr) bool) {
// Go order: From, Reg, RestArgs..., To
// For SVE, Reg is unused as it's so common that registers have arrangements.
if p.From.Type != obj.TYPE_NONE {
if !yield(&p.From) {
return
}
}
for j := range p.RestArgs {
if !yield(&p.RestArgs[j].Addr) {
return
}
}
if p.To.Type != obj.TYPE_NONE {
if !yield(&p.To) {
return
}
}
}
}
// aclass returns the AClass of an Addr.
func aclass(a *obj.Addr) AClass {
if a.Type == obj.TYPE_REG {
if a.Reg >= REG_Z0 && a.Reg <= REG_Z31 {
return AC_ZREG
}
if a.Reg >= REG_P0 && a.Reg <= REG_P15 {
return AC_PREG
}
if a.Reg >= REG_ARNG && a.Reg < REG_ELEM {
return AC_ARNG
}
if a.Reg >= REG_ZARNG && a.Reg < REG_ZARNGELEM {
return AC_ARNG
}
if a.Reg >= REG_ZARNGELEM && a.Reg < REG_PZELEM {
return AC_ARNGIDX
}
if a.Reg >= REG_PZELEM && a.Reg < REG_PARNGZM {
if a.Reg&(1<<5) == 0 {
return AC_ZREGIDX
} else {
return AC_PREGIDX
}
}
if a.Reg >= REG_PARNGZM && a.Reg < REG_PARNGZM_END {
switch (a.Reg >> 5) & 15 {
case PRED_M, PRED_Z:
return AC_PREGZM
default:
return AC_ARNG
}
}
if a.Reg >= REG_V0 && a.Reg <= REG_V31 {
return AC_VREG
}
if a.Reg >= REG_R0 && a.Reg <= REG_R31 || a.Reg == REG_RSP {
return AC_SPZGREG
}
}
panic("unknown AClass")
}
// addrComponent returns the binary (component) of the stored element in a at index, for operand
// of type aclass.
//
// For example, for operand of type AC_ARNG, it has 2 permissible components (identified by index)
// 0. register: <reg>
// 1. arrangement: <T>
//
// They are stored in a.Reg as:
//
// reg | (arrangement << 5)
//
// More details are in the comments in the switch cases of this function.
func addrComponent(a *obj.Addr, acl AClass, index int) uint32 {
switch acl {
// AClass: AC_ARNG, AC_PREG, AC_PREGZ, AC_PREGM, AC_ZREG
// GNU mnemonic: <reg>.<T> Or <reg>/<T> (T is M or Z)
// Go mnemonic:
// reg.<T>
// Encoding:
// Type = TYPE_REG
// Reg = reg | (arrangement or predication << 5)
case AC_ARNG, AC_PREG, AC_PREGZM, AC_ZREG:
switch index {
case 0:
return uint32(a.Reg & 31)
case 1:
return uint32((a.Reg >> 5) & 15)
default:
panic(fmt.Errorf("unknown elm index at %d in AClass %d", index, acl))
}
// AClass: AC_ARNGIDX, AC_PREGIDX, AC_ZREGIDX
// GNU mnemonic: <reg>.<T>[<index>]
// Go mnemonic:
// reg.T[index]
// Encoding:
// Type = TYPE_REG
// Reg = reg | (arrangement << 5)
// Index = index
case AC_ARNGIDX, AC_PREGIDX, AC_ZREGIDX:
switch index {
case 0:
return uint32(a.Reg & 31)
case 1:
// Arrangement
return uint32((a.Reg >> 5) & 15)
case 2:
// Index
return uint32(a.Index)
default:
panic(fmt.Errorf("unknown elm index at %d in AClass %d", index, acl))
}
// AClass: AC_SPZGREG, AC_VREG
// GNU mnemonic: <width><reg>
// Go mnemonic:
// reg (the width is already represented in the opcode)
// Encoding:
// Type = TYPE_REG
// Reg = reg
case AC_SPZGREG, AC_VREG:
switch index {
case 0:
// These are all width checks, they should map to no-op checks altogether.
return 0
case 1:
return uint32(a.Reg)
default:
panic(fmt.Errorf("unknown elm index at %d in AClass %d", index, acl))
}
}
// TODO: handle more AClasses.
panic(fmt.Errorf("unknown AClass %d", acl))
}
var codeI1Tsz uint32 = 0xffffffff
var codeImm2Tsz uint32 = 0xfffffffe
// encodeI1Tsz is the implementation of the following encoding logic:
// Is the immediate index, in the range 0 to one less than the number of elements in 128 bits, encoded in "i1:tsz".
// bit range mappings:
// i1: [20:21)
// tsz: [16:20)
// Note:
//
// arr is the arrangement.
// This encoding is aligned to the high bit of the box, according to the spec.
func encodeI1Tsz(v, arr uint32) (uint32, bool) {
switch arr {
case ARNG_B:
if v > 15 {
return 0, false
}
return v << 17, true
case ARNG_H:
if v > 7 {
return 0, false
}
return v << 18, true
case ARNG_S:
if v > 3 {
return 0, false
}
return v << 19, true
case ARNG_D:
if v > 1 {
return 0, false
}
return v << 20, true
case ARNG_Q:
if v > 0 {
return 0, false
}
return 0, true
default:
return 0, false
}
}
// encodeImm2Tsz is the implementation of the following encoding logic:
// Is the immediate index, in the range 0 to one less than the number of elements in 512 bits, encoded in "imm2:tsz".
// bit range mappings:
// imm2: [22:24)
// tsz: [16:21)
// Note:
//
// arr is the arrangement.
// This encoding is aligned to the high bit of the box, according to the spec.
func encodeImm2Tsz(v, arr uint32) (uint32, bool) {
switch arr {
case ARNG_B:
if v > 63 {
return 0, false
}
v <<= 1
return (v&31)<<16 | (v>>5)<<22, true
case ARNG_H:
if v > 31 {
return 0, false
}
v <<= 2
return (v&31)<<16 | (v>>5)<<22, true
case ARNG_S:
if v > 15 {
return 0, false
}
v <<= 3
return (v&31)<<16 | (v>>5)<<22, true
case ARNG_D:
if v > 7 {
return 0, false
}
v <<= 4
return (v&31)<<16 | (v>>5)<<22, true
case ARNG_Q:
if v > 3 {
return 0, false
}
v <<= 5
return (v&31)<<16 | (v>>5)<<22, true
default:
return 0, false
}
}
// tryEncode tries to encode p with i, it returns the encoded binary and ok signal.
func (i *instEncoder) tryEncode(p *obj.Prog) (uint32, bool) {
bin := i.fixedBits
// Some elements are encoded in the same component, they need to be equal.
// For example { <Zn1>.<Tb>-<Zn2>.<Tb> }.
// The 2 instances of <Tb> must encode to the same value.
encoded := map[component]uint32{}
opIdx := 0
for addr := range opsInProg(p) {
if opIdx >= len(i.args) {
return 0, false
}
op := i.args[opIdx]
opIdx++
acl := aclass(addr)
if acl != op.class {
return 0, false
}
for i, enc := range op.elemEncoders {
val := addrComponent(addr, acl, i)
if b, ok := enc.fn(val); ok || b != 0 {
if !ok {
switch b {
case codeI1Tsz:
b, ok = encodeI1Tsz(val, addrComponent(addr, acl, i-1))
case codeImm2Tsz:
b, ok = encodeImm2Tsz(val, addrComponent(addr, acl, i-1))
default:
panic(fmt.Errorf("unknown encoding function code %d", b))
}
}
if !ok {
return 0, false
}
bin |= b
if _, ok := encoded[enc.comp]; ok && b != encoded[enc.comp] {
return 0, false
}
if enc.comp != enc_NIL {
encoded[enc.comp] = b
}
} else {
return 0, false
}
}
}
if opIdx != len(i.args) {
return 0, false
}
return bin, true
}