Files
avo/ir/ir.go

366 lines
7.9 KiB
Go
Raw Normal View History

package ir
2018-11-06 21:10:54 -05:00
2018-12-02 12:28:33 -08:00
import (
2018-12-27 11:57:46 -08:00
"errors"
2026-03-06 20:14:02 +00:00
"sources.truenas.cloud/code/avo/attr"
"sources.truenas.cloud/code/avo/buildtags"
"sources.truenas.cloud/code/avo/gotypes"
"sources.truenas.cloud/code/avo/operand"
"sources.truenas.cloud/code/avo/reg"
2018-12-02 12:28:33 -08:00
)
2019-01-05 00:31:46 -08:00
// Node is a part of a Function.
2018-11-30 21:37:17 -08:00
type Node interface {
node()
}
2019-01-05 00:31:46 -08:00
// Label within a function.
2018-11-30 21:37:17 -08:00
type Label string
func (l Label) node() {}
// Comment represents a multi-line comment.
type Comment struct {
Lines []string
}
func (c *Comment) node() {}
// NewComment builds a Comment consisting of the provided lines.
func NewComment(lines ...string) *Comment {
return &Comment{
Lines: lines,
}
}
2018-11-06 21:10:54 -05:00
// Instruction is a single instruction in a function.
type Instruction struct {
2018-11-27 22:38:53 -08:00
Opcode string
all: AVX-512 (#217) Extends avo to support most AVX-512 instruction sets. The instruction type is extended to support suffixes. The K family of opmask registers is added to the register package, and the operand package is updated to support the new operand types. Move instruction deduction in `Load` and `Store` is extended to support KMOV* and VMOV* forms. Internal code generation packages were overhauled. Instruction database loading required various messy changes to account for the additional complexities of the AVX-512 instruction sets. The internal/api package was added to introduce a separation between instruction forms in the database, and the functions avo provides to create them. This was required since with instruction suffixes there is no longer a one-to-one mapping between instruction constructors and opcodes. AVX-512 bloated generated source code size substantially, initially increasing compilation and CI test times to an unacceptable level. Two changes were made to address this: 1. Instruction constructors in the `x86` package moved to an optab-based approach. This compiles substantially faster than the verbose code generation we had before. 2. The most verbose code-generated tests are moved under build tags and limited to a stress test mode. Stress test builds are run on schedule but not in regular CI. An example of AVX-512 accelerated 16-lane MD5 is provided to demonstrate and test the new functionality. Updates #20 #163 #229 Co-authored-by: Vaughn Iverson <vsivsi@yahoo.com>
2021-11-12 18:35:36 -08:00
Suffixes []string
2018-12-02 12:28:33 -08:00
Operands []operand.Op
Inputs []operand.Op
Outputs []operand.Op
IsTerminal bool
IsBranch bool
IsConditional bool
CancellingInputs bool
2018-12-02 12:28:33 -08:00
// ISA is the list of required instruction set extensions.
ISA []string
2018-12-02 12:28:33 -08:00
// CFG.
Pred []*Instruction
Succ []*Instruction
2018-12-02 23:59:29 -08:00
// LiveIn/LiveOut are sets of live register IDs pre/post execution.
LiveIn reg.MaskSet
LiveOut reg.MaskSet
2018-12-02 12:28:33 -08:00
}
2018-12-02 23:59:29 -08:00
func (i *Instruction) node() {}
2018-12-02 22:29:30 -08:00
all: AVX-512 (#217) Extends avo to support most AVX-512 instruction sets. The instruction type is extended to support suffixes. The K family of opmask registers is added to the register package, and the operand package is updated to support the new operand types. Move instruction deduction in `Load` and `Store` is extended to support KMOV* and VMOV* forms. Internal code generation packages were overhauled. Instruction database loading required various messy changes to account for the additional complexities of the AVX-512 instruction sets. The internal/api package was added to introduce a separation between instruction forms in the database, and the functions avo provides to create them. This was required since with instruction suffixes there is no longer a one-to-one mapping between instruction constructors and opcodes. AVX-512 bloated generated source code size substantially, initially increasing compilation and CI test times to an unacceptable level. Two changes were made to address this: 1. Instruction constructors in the `x86` package moved to an optab-based approach. This compiles substantially faster than the verbose code generation we had before. 2. The most verbose code-generated tests are moved under build tags and limited to a stress test mode. Stress test builds are run on schedule but not in regular CI. An example of AVX-512 accelerated 16-lane MD5 is provided to demonstrate and test the new functionality. Updates #20 #163 #229 Co-authored-by: Vaughn Iverson <vsivsi@yahoo.com>
2021-11-12 18:35:36 -08:00
// OpcodeWithSuffixes returns the full opcode, including dot-separated suffixes.
func (i *Instruction) OpcodeWithSuffixes() string {
opcode := i.Opcode
for _, s := range i.Suffixes {
opcode += "." + s
}
return opcode
}
// IsUnconditionalBranch reports whether i is an unconditional branch.
func (i Instruction) IsUnconditionalBranch() bool {
return i.IsBranch && !i.IsConditional
}
2019-01-05 00:31:46 -08:00
// TargetLabel returns the label referenced by this instruction. Returns nil if
// no label is referenced.
2018-12-02 12:28:33 -08:00
func (i Instruction) TargetLabel() *Label {
if !i.IsBranch {
return nil
}
2018-12-02 13:50:55 -08:00
if len(i.Operands) == 0 {
return nil
}
2018-12-02 12:28:33 -08:00
if ref, ok := i.Operands[0].(operand.LabelRef); ok {
lbl := Label(ref)
return &lbl
}
return nil
2018-11-20 11:44:44 -06:00
}
2019-01-05 00:31:46 -08:00
// Registers returns all registers involved in the instruction.
func (i Instruction) Registers() []reg.Register {
var rs []reg.Register
for _, op := range i.Operands {
rs = append(rs, operand.Registers(op)...)
}
return rs
}
2019-01-05 00:31:46 -08:00
// InputRegisters returns all registers read by this instruction.
2018-12-02 22:29:30 -08:00
func (i Instruction) InputRegisters() []reg.Register {
var rs []reg.Register
for _, op := range i.Inputs {
rs = append(rs, operand.Registers(op)...)
}
if i.CancellingInputs && rs[0] == rs[1] {
rs = []reg.Register{}
}
2018-12-02 22:29:30 -08:00
for _, op := range i.Outputs {
if operand.IsMem(op) {
rs = append(rs, operand.Registers(op)...)
}
}
return rs
}
2019-01-05 00:31:46 -08:00
// OutputRegisters returns all registers written by this instruction.
2018-12-02 22:29:30 -08:00
func (i Instruction) OutputRegisters() []reg.Register {
var rs []reg.Register
for _, op := range i.Outputs {
if r, ok := op.(reg.Register); ok {
rs = append(rs, r)
}
}
return rs
}
2018-11-30 21:37:17 -08:00
2019-01-05 00:31:46 -08:00
// Section is a part of a file.
type Section interface {
section()
}
2018-11-06 21:10:54 -05:00
// File represents an assembly file.
type File struct {
Constraints buildtags.Constraints
Includes []string
Sections []Section
2018-11-30 20:43:31 -08:00
}
2019-01-05 00:31:46 -08:00
// NewFile initializes an empty file.
2018-11-30 20:43:31 -08:00
func NewFile() *File {
return &File{}
2018-11-06 21:10:54 -05:00
}
2019-01-05 00:31:46 -08:00
// AddSection appends a Section to the file.
2018-12-27 11:57:46 -08:00
func (f *File) AddSection(s Section) {
f.Sections = append(f.Sections, s)
}
2019-01-05 00:31:46 -08:00
// Functions returns all functions in the file.
func (f *File) Functions() []*Function {
var fns []*Function
for _, s := range f.Sections {
if fn, ok := s.(*Function); ok {
fns = append(fns, fn)
}
}
return fns
}
// Pragma represents a function compiler directive.
type Pragma struct {
Directive string
Arguments []string
}
2018-11-06 21:10:54 -05:00
// Function represents an assembly function.
type Function struct {
Name string
Attributes attr.Attribute
Pragmas []Pragma
Doc []string
Signature *gotypes.Signature
LocalSize int
2018-12-06 21:58:51 -08:00
2018-12-05 00:27:42 -08:00
Nodes []Node
2018-12-02 12:28:33 -08:00
// LabelTarget maps from label name to the following instruction.
LabelTarget map[Label]*Instruction
// Register allocation.
Allocation reg.Allocation
// ISA is the list of required instruction set extensions.
ISA []string
2018-11-06 21:10:54 -05:00
}
2018-12-27 11:57:46 -08:00
func (f *Function) section() {}
2019-01-05 00:31:46 -08:00
// NewFunction builds an empty function of the given name.
2018-11-06 21:10:54 -05:00
func NewFunction(name string) *Function {
return &Function{
Name: name,
Signature: gotypes.NewSignatureVoid(),
2018-11-06 21:10:54 -05:00
}
}
// AddPragma adds a pragma to this function.
func (f *Function) AddPragma(directive string, args ...string) {
f.Pragmas = append(f.Pragmas, Pragma{
Directive: directive,
Arguments: args,
})
}
2019-01-05 00:31:46 -08:00
// SetSignature sets the function signature.
func (f *Function) SetSignature(s *gotypes.Signature) {
f.Signature = s
}
2019-01-05 00:31:46 -08:00
// AllocLocal allocates size bytes in this function's stack.
// Returns a reference to the base pointer for the newly allocated region.
2018-12-21 00:30:59 -08:00
func (f *Function) AllocLocal(size int) operand.Mem {
ptr := operand.NewStackAddr(f.LocalSize)
f.LocalSize += size
return ptr
}
2019-01-05 00:31:46 -08:00
// AddInstruction appends an instruction to f.
2018-12-02 12:28:33 -08:00
func (f *Function) AddInstruction(i *Instruction) {
2018-11-30 21:37:17 -08:00
f.AddNode(i)
}
2019-01-05 00:31:46 -08:00
// AddLabel appends a label to f.
2018-11-30 21:37:17 -08:00
func (f *Function) AddLabel(l Label) {
f.AddNode(l)
}
// AddComment adds comment lines to f.
func (f *Function) AddComment(lines ...string) {
f.AddNode(NewComment(lines...))
}
2019-01-05 00:31:46 -08:00
// AddNode appends a Node to f.
2018-11-30 21:37:17 -08:00
func (f *Function) AddNode(n Node) {
2018-12-02 12:28:33 -08:00
f.Nodes = append(f.Nodes, n)
}
// Instructions returns just the list of instruction nodes.
func (f *Function) Instructions() []*Instruction {
var is []*Instruction
for _, n := range f.Nodes {
i, ok := n.(*Instruction)
if ok {
is = append(is, i)
}
}
return is
2018-11-06 21:10:54 -05:00
}
// Labels returns just the list of label nodes.
func (f *Function) Labels() []Label {
var lbls []Label
for _, n := range f.Nodes {
lbl, ok := n.(Label)
if ok {
lbls = append(lbls, lbl)
}
}
return lbls
}
// Stub returns the Go function declaration.
func (f *Function) Stub() string {
return "func " + f.Name + f.Signature.String()
}
2018-11-06 21:10:54 -05:00
// FrameBytes returns the size of the stack frame in bytes.
func (f *Function) FrameBytes() int {
2018-12-21 00:30:59 -08:00
return f.LocalSize
2018-11-06 21:10:54 -05:00
}
// ArgumentBytes returns the size of the arguments in bytes.
func (f *Function) ArgumentBytes() int {
return f.Signature.Bytes()
2018-11-06 21:10:54 -05:00
}
2018-12-27 11:57:46 -08:00
2019-01-05 00:31:46 -08:00
// Datum represents a data element at a particular offset of a data section.
2018-12-27 11:57:46 -08:00
type Datum struct {
Offset int
Value operand.Constant
}
2019-01-05 00:31:46 -08:00
// NewDatum builds a Datum from the given constant.
2018-12-27 11:57:46 -08:00
func NewDatum(offset int, v operand.Constant) Datum {
return Datum{
Offset: offset,
Value: v,
}
}
// Interval returns the range of bytes this datum will occupy within its section.
func (d Datum) Interval() (int, int) {
return d.Offset, d.Offset + d.Value.Bytes()
}
// Overlaps returns whether d overlaps with other.
2018-12-27 11:57:46 -08:00
func (d Datum) Overlaps(other Datum) bool {
s, e := d.Interval()
so, eo := other.Interval()
return !(eo <= s || e <= so)
}
2019-01-05 00:31:46 -08:00
// Global represents a DATA section.
2018-12-27 11:57:46 -08:00
type Global struct {
Symbol operand.Symbol
Attributes attr.Attribute
Data []Datum
Size int
2018-12-27 11:57:46 -08:00
}
2019-01-05 00:31:46 -08:00
// NewGlobal constructs an empty DATA section.
2018-12-27 11:57:46 -08:00
func NewGlobal(sym operand.Symbol) *Global {
return &Global{
Symbol: sym,
2018-12-27 11:57:46 -08:00
}
}
2019-01-05 00:31:46 -08:00
// NewStaticGlobal is a convenience for building a static DATA section.
2018-12-27 11:57:46 -08:00
func NewStaticGlobal(name string) *Global {
return NewGlobal(operand.NewStaticSymbol(name))
}
func (g *Global) section() {}
2019-01-05 00:31:46 -08:00
// Base returns a pointer to the start of the data section.
2018-12-27 11:57:46 -08:00
func (g *Global) Base() operand.Mem {
return operand.NewDataAddr(g.Symbol, 0)
}
2019-01-05 00:31:46 -08:00
// Grow ensures that the data section has at least the given size.
2018-12-27 11:57:46 -08:00
func (g *Global) Grow(size int) {
if g.Size < size {
g.Size = size
}
}
2019-01-05 00:31:46 -08:00
// AddDatum adds d to this data section, growing it if necessary. Errors if the datum overlaps with existing data.
2018-12-27 11:57:46 -08:00
func (g *Global) AddDatum(d Datum) error {
for _, other := range g.Data {
if d.Overlaps(other) {
return errors.New("overlaps existing datum")
}
}
g.add(d)
return nil
}
2019-01-05 00:31:46 -08:00
// Append the constant to the end of the data section.
2018-12-27 11:57:46 -08:00
func (g *Global) Append(v operand.Constant) {
g.add(Datum{
Offset: g.Size,
Value: v,
})
}
func (g *Global) add(d Datum) {
_, end := d.Interval()
g.Grow(end)
g.Data = append(g.Data, d)
}