diff --git a/examples/fnv1a/asm.go b/examples/fnv1a/asm.go new file mode 100644 index 00000000..8cd99fee --- /dev/null +++ b/examples/fnv1a/asm.go @@ -0,0 +1,41 @@ +// +build ignore + +package main + +import ( + . "github.com/mmcloughlin/avo/build" + "github.com/mmcloughlin/avo/operand" + "github.com/mmcloughlin/avo/reg" +) + +const ( + OffsetBasis = 0xcbf29ce484222325 + Prime = 0x100000001b3 +) + +func main() { + TEXT("Hash64", "func(data []byte) uint64") + ptr := Load(Param("data").Base(), GP64v()) + n := Load(Param("data").Len(), GP64v()) + + h := reg.RAX + MOVQ(operand.Imm(OffsetBasis), h) + p := GP64v() + MOVQ(operand.Imm(Prime), p) + + LABEL("loop") + CMPQ(n, operand.Imm(0)) + JE(operand.LabelRef("done")) + b := GP64v() + MOVBQZX(operand.Mem{Base: ptr}, b) + XORQ(b, h) + MULQ(p) + INCQ(ptr) + DECQ(n) + + JMP(operand.LabelRef("loop")) + LABEL("done") + Store(h, ReturnIndex(0)) + RET() + Generate() +} diff --git a/examples/fnv1a/fnv1a.s b/examples/fnv1a/fnv1a.s new file mode 100644 index 00000000..aea3e76b --- /dev/null +++ b/examples/fnv1a/fnv1a.s @@ -0,0 +1,21 @@ + +#include "textflag.h" + +// func Hash64(data []byte) uint64 +TEXT ·Hash64(SB),0,$0-32 + MOVQ data_base(FP), CX + MOVQ data_len+8(FP), BX + MOVQ $0xcbf29ce484222325, AX + MOVQ $0x100000001b3, BP +loop: + CMPQ BX, $0x0 + JE done + MOVBQZX (CX), DX + XORQ DX, AX + MULQ BP + INCQ CX + DECQ BX + JMP loop +done: + MOVQ AX, ret+24(FP) + RET diff --git a/examples/fnv1a/fnv_test.go b/examples/fnv1a/fnv_test.go new file mode 100644 index 00000000..dceb1aa3 --- /dev/null +++ b/examples/fnv1a/fnv_test.go @@ -0,0 +1,20 @@ +package fnv1a + +import ( + "hash/fnv" + "testing" + "testing/quick" +) + +//go:generate go run asm.go -out fnv1a.s -stubs stub.go + +func TestHash64(t *testing.T) { + expect := func(data []byte) uint64 { + h := fnv.New64a() + h.Write(data) + return h.Sum64() + } + if err := quick.CheckEqual(Hash64, expect, nil); err != nil { + t.Fatal(err) + } +} diff --git a/examples/fnv1a/stub.go b/examples/fnv1a/stub.go new file mode 100644 index 00000000..11e554a6 --- /dev/null +++ b/examples/fnv1a/stub.go @@ -0,0 +1,3 @@ +package fnv1a + +func Hash64(data []byte) uint64 diff --git a/operand/checks.go b/operand/checks.go index aa56c456..493ec592 100644 --- a/operand/checks.go +++ b/operand/checks.go @@ -111,7 +111,7 @@ func IsR64(op Op) bool { // IsPseudo returns true if op is a pseudo register. func IsPseudo(op Op) bool { - return IsRegisterKindSize(op, reg.Internal, 0) + return IsRegisterKind(op, reg.Internal) } // IsGP returns true if op is a general-purpose register of size n bytes. @@ -140,6 +140,12 @@ func IsRegisterKindSize(op Op, k reg.Kind, n uint) bool { return ok && r.Kind() == k && r.Bytes() == n } +// IsRegisterKind returns true if op is a register of the given kind. +func IsRegisterKind(op Op, k reg.Kind) bool { + r, ok := op.(reg.Register) + return ok && r.Kind() == k +} + // IsM returns true if op is a 16-, 32- or 64-bit memory operand. func IsM(op Op) bool { // TODO(mbm): confirm "m" check is defined correctly @@ -176,12 +182,12 @@ func IsM64(op Op) bool { func IsMSize(op Op, n uint) bool { // TODO(mbm): should memory operands have a size attribute as well? m, ok := op.(Mem) - return ok && IsMRegSize(m.Base, n) && (m.Index == nil || IsMRegSize(m.Index, n)) + return ok && IsMReg(m.Base) && (m.Index == nil || IsMReg(m.Index)) } -// IsMRegSize returns true if op is a register that can be used in a memory operand of size n bytes. -func IsMRegSize(op Op, n uint) bool { - return IsPseudo(op) || IsGP(op, n) +// IsMReg returns true if op is a register that can be used in a memory operand. +func IsMReg(op Op) bool { + return IsPseudo(op) || IsRegisterKind(op, reg.GP) } // IsM128 returns true if op is a 128-bit memory operand. diff --git a/pass/alloc.go b/pass/alloc.go index b5170a51..8bef0778 100644 --- a/pass/alloc.go +++ b/pass/alloc.go @@ -60,7 +60,7 @@ func (a *Allocator) Add(r reg.Register) { if _, found := a.possible[v]; found { return } - a.possible[v] = a.registersofsize(v.Bytes()) + a.possible[v] = a.possibleregisters(v.Bytes()) } func (a *Allocator) Allocate() (reg.Allocation, error) { @@ -146,11 +146,11 @@ func (a *Allocator) remaining() int { return len(a.possible) } -// registersofsize returns all registers of the given size. -func (a *Allocator) registersofsize(n uint) []reg.Physical { +// possibleregisters returns all allocate-able registers of the given size. +func (a *Allocator) possibleregisters(n uint) []reg.Physical { var rs []reg.Physical for _, r := range a.registers { - if r.Bytes() == n { + if r.Bytes() == n && (r.Info()®.Restricted) == 0 { rs = append(rs, r) } } diff --git a/reg/types.go b/reg/types.go index 78768045..da9baca3 100644 --- a/reg/types.go +++ b/reg/types.go @@ -26,17 +26,26 @@ type Family struct { registers []Physical } -func (f *Family) define(s Spec, id PID, name string) Physical { +func (f *Family) add(s Spec, id PID, name string, info Info) Physical { r := register{ id: id, kind: f.Kind, name: name, + info: info, Spec: s, } f.registers = append(f.registers, r) return r } +func (f *Family) define(s Spec, id PID, name string) Physical { + return f.add(s, id, name, None) +} + +func (f *Family) restricted(s Spec, id PID, name string) Physical { + return f.add(s, id, name, Restricted) +} + func (f *Family) Virtual(id VID, s Size) Virtual { return NewVirtual(id, f.Kind, s) } @@ -110,9 +119,17 @@ func (v virtual) Asm() string { func (v virtual) register() {} +type Info uint8 + +const ( + None Info = 0 + Restricted Info = 1 << iota +) + type Physical interface { PhysicalID() PID Mask() uint16 + Info() Info Register } @@ -128,6 +145,7 @@ type register struct { id PID kind Kind name string + info Info Spec } @@ -135,6 +153,7 @@ func (r register) PhysicalID() PID { return r.id } func (r register) ID() ID { return (ID(r.Mask()) << 16) | ID(r.id) } func (r register) Kind() Kind { return r.kind } func (r register) Asm() string { return r.name } +func (r register) Info() Info { return r.info } func (r register) register() {} type Spec uint16 diff --git a/reg/x86.go b/reg/x86.go index 3f91baf5..8f6a4e39 100644 --- a/reg/x86.go +++ b/reg/x86.go @@ -54,7 +54,7 @@ var ( BH = GeneralPurpose.define(S8H, 3, "BH") // 8-bit - SPB = GeneralPurpose.define(S8, 4, "SP") + SPB = GeneralPurpose.restricted(S8, 4, "SP") BPB = GeneralPurpose.define(S8, 5, "BP") SIB = GeneralPurpose.define(S8, 6, "SI") DIB = GeneralPurpose.define(S8, 7, "DI") @@ -72,7 +72,7 @@ var ( CX = GeneralPurpose.define(S16, 1, "CX") DX = GeneralPurpose.define(S16, 2, "DX") BX = GeneralPurpose.define(S16, 3, "BX") - SP = GeneralPurpose.define(S16, 4, "SP") + SP = GeneralPurpose.restricted(S16, 4, "SP") BP = GeneralPurpose.define(S16, 5, "BP") SI = GeneralPurpose.define(S16, 6, "SI") DI = GeneralPurpose.define(S16, 7, "DI") @@ -90,7 +90,7 @@ var ( ECX = GeneralPurpose.define(S32, 1, "CX") EDX = GeneralPurpose.define(S32, 2, "DX") EBX = GeneralPurpose.define(S32, 3, "BX") - ESP = GeneralPurpose.define(S32, 4, "SP") + ESP = GeneralPurpose.restricted(S32, 4, "SP") EBP = GeneralPurpose.define(S32, 5, "BP") ESI = GeneralPurpose.define(S32, 6, "SI") EDI = GeneralPurpose.define(S32, 7, "DI") @@ -108,7 +108,7 @@ var ( RCX = GeneralPurpose.define(S64, 1, "CX") RDX = GeneralPurpose.define(S64, 2, "DX") RBX = GeneralPurpose.define(S64, 3, "BX") - RSP = GeneralPurpose.define(S64, 4, "SP") + RSP = GeneralPurpose.restricted(S64, 4, "SP") RBP = GeneralPurpose.define(S64, 5, "BP") RSI = GeneralPurpose.define(S64, 6, "SI") RDI = GeneralPurpose.define(S64, 7, "DI")