Skip to content

Commit

Permalink
cmd/asm: enable AVX512
Browse files Browse the repository at this point in the history
- Uncomment tests for AVX512 encoder
- Permit instruction suffixes for x86
- Permit limited reg list [reg-reg] syntax for x86 for multi-source ops
- EVEX encoding support in obj/x86 (Z-cases, asmevex, etc.)
- optabs and ytabs generated by x86avxgen (https://golang.org/cl/107216)

Note: suffix formatting implemented with updated CConv function.
Now arch asm backend should register formatting function by
calling RegisterOpSuffix.

Updates #22779

Change-Id: I076a167ee49582700e058c56ad74e6696710c8c8
Reviewed-on: https://go-review.googlesource.com/113315
Run-TryBot: Iskander Sharipov <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Cherry Zhang <[email protected]>
  • Loading branch information
isharipo authored and TocarIP committed May 22, 2018
1 parent 8a85bce commit 5437cde
Show file tree
Hide file tree
Showing 36 changed files with 22,096 additions and 15,838 deletions.
13 changes: 13 additions & 0 deletions src/cmd/asm/internal/asm/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"cmd/asm/internal/flags"
"cmd/asm/internal/lex"
"cmd/internal/obj"
"cmd/internal/obj/x86"
"cmd/internal/objabi"
"cmd/internal/sys"
)
Expand All @@ -38,6 +39,12 @@ func (p *Parser) append(prog *obj.Prog, cond string, doLabel bool) {
return
}

case sys.AMD64, sys.I386:
if err := x86.ParseSuffix(prog, cond); err != nil {
p.errorf("%v", err)
return
}

default:
p.errorf("unrecognized suffix .%q", cond)
return
Expand Down Expand Up @@ -740,6 +747,12 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) {
prog.To = a[4]
break
}
if p.arch.Family == sys.AMD64 {
prog.From = a[0]
prog.RestArgs = []obj.Addr{a[1], a[2], a[3]}
prog.To = a[4]
break
}
p.errorf("can't handle %s instruction with 5 operands", op)
return
case 6:
Expand Down
16 changes: 3 additions & 13 deletions src/cmd/asm/internal/asm/expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ var badExprTests = []badExprTest{
}

func TestBadExpr(t *testing.T) {
panicOnError = true
defer func() {
panicOnError = false
}()
for i, test := range badExprTests {
err := runBadTest(i, test, t)
if err == nil {
Expand All @@ -119,13 +115,7 @@ func TestBadExpr(t *testing.T) {
func runBadTest(i int, test badExprTest, t *testing.T) (err error) {
p := NewParser(nil, nil, nil) // Expression evaluation uses none of these fields of the parser.
p.start(lex.Tokenize(test.input))
defer func() {
e := recover()
var ok bool
if err, ok = e.(error); e != nil && !ok {
t.Fatal(e)
}
}()
p.expr()
return nil
return tryParse(t, func() {
p.expr()
})
}
52 changes: 52 additions & 0 deletions src/cmd/asm/internal/asm/line_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2018 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 asm

import (
"cmd/asm/internal/lex"
"strings"
"testing"
)

type badInstTest struct {
input, error string
}

func TestAMD64BadInstParser(t *testing.T) {
testBadInstParser(t, "amd64", []badInstTest{
// Test AVX512 suffixes.
{"VADDPD.A X0, X1, X2", `unknown suffix "A"`},
{"VADDPD.A.A X0, X1, X2", `unknown suffix "A"; duplicate suffix "A"`},
{"VADDPD.A.A.A X0, X1, X2", `unknown suffix "A"; duplicate suffix "A"`},
{"VADDPD.A.B X0, X1, X2", `unknown suffix "A"; unknown suffix "B"`},
{"VADDPD.Z.A X0, X1, X2", `Z suffix should be the last; unknown suffix "A"`},
{"VADDPD.Z.Z X0, X1, X2", `Z suffix should be the last; duplicate suffix "Z"`},
{"VADDPD.SAE.BCST X0, X1, X2", `can't combine rounding/SAE and broadcast`},
{"VADDPD.BCST.SAE X0, X1, X2", `can't combine rounding/SAE and broadcast`},
{"VADDPD.BCST.Z.SAE X0, X1, X2", `Z suffix should be the last; can't combine rounding/SAE and broadcast`},
{"VADDPD.SAE.SAE X0, X1, X2", `duplicate suffix "SAE"`},
{"VADDPD.RZ_SAE.SAE X0, X1, X2", `bad suffix combination`},
})
}

func testBadInstParser(t *testing.T, goarch string, tests []badInstTest) {
for i, test := range tests {
arch, ctxt := setArch(goarch)
tokenizer := lex.NewTokenizer("", strings.NewReader(test.input+"\n"), nil)
parser := NewParser(ctxt, arch, tokenizer)

err := tryParse(t, func() {
parser.start(lex.Tokenize(test.input))
parser.line()
})

switch {
case err == nil:
t.Errorf("#%d: %q: want error %q; have none", i, test.input, test.error)
case !strings.Contains(err.Error(), test.error):
t.Errorf("#%d: %q: want error %q; have %q", i, test.input, test.error, err)
}
}
}
64 changes: 64 additions & 0 deletions src/cmd/asm/internal/asm/operand_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package asm

import (
"strings"
"testing"

"cmd/asm/internal/arch"
Expand All @@ -30,6 +31,45 @@ func newParser(goarch string) *Parser {
return NewParser(ctxt, architecture, nil)
}

// tryParse executes parse func in panicOnError=true context.
// parse is expected to call any parsing methods that may panic.
// Returns error gathered from recover; nil if no parse errors occured.
//
// For unexpected panics, calls t.Fatal.
func tryParse(t *testing.T, parse func()) (err error) {
panicOnError = true
defer func() {
panicOnError = false

e := recover()
var ok bool
if err, ok = e.(error); e != nil && !ok {
t.Fatal(e)
}
}()

parse()

return nil
}

func testBadOperandParser(t *testing.T, parser *Parser, tests []badOperandTest) {
for _, test := range tests {
err := tryParse(t, func() {
parser.start(lex.Tokenize(test.input))
addr := obj.Addr{}
parser.operand(&addr)
})

switch {
case err == nil:
t.Errorf("fail at %s: got no errors; expected %s\n", test.input, test.error)
case !strings.Contains(err.Error(), test.error):
t.Errorf("fail at %s: got %s; expected %s", test.input, err, test.error)
}
}
}

func testOperandParser(t *testing.T, parser *Parser, tests []operandTest) {
for _, test := range tests {
parser.start(lex.Tokenize(test.input))
Expand All @@ -45,6 +85,7 @@ func testOperandParser(t *testing.T, parser *Parser, tests []operandTest) {
func TestAMD64OperandParser(t *testing.T) {
parser := newParser("amd64")
testOperandParser(t, parser, amd64OperandTests)
testBadOperandParser(t, parser, amd64BadOperandTests)
}

func Test386OperandParser(t *testing.T) {
Expand Down Expand Up @@ -85,6 +126,10 @@ type operandTest struct {
input, output string
}

type badOperandTest struct {
input, error string
}

// Examples collected by scanning all the assembly in the standard repo.

var amd64OperandTests = []operandTest{
Expand Down Expand Up @@ -202,9 +247,28 @@ var amd64OperandTests = []operandTest{
{"y+56(FP)", "y+56(FP)"},
{"·AddUint32(SB)", "\"\".AddUint32(SB)"},
{"·callReflect(SB)", "\"\".callReflect(SB)"},
{"[X0-X0]", "[X0-X0]"},
{"[ Z9 - Z12 ]", "[Z9-Z12]"},
{"[X0-AX]", "[X0-AX]"},
{"[AX-X0]", "[AX-X0]"},
{"[):[o-FP", ""}, // Issue 12469 - asm hung parsing the o-FP range on non ARM platforms.
}

var amd64BadOperandTests = []badOperandTest{
{"[", "register list: expected ']', found EOF"},
{"[4", "register list: bad low register in `[4`"},
{"[]", "register list: bad low register in `[]`"},
{"[f-x]", "register list: bad low register in `[f`"},
{"[r10-r13]", "register list: bad low register in `[r10`"},
{"[k3-k6]", "register list: bad low register in `[k3`"},
{"[X0]", "register list: expected '-' after `[X0`, found ']'"},
{"[X0-]", "register list: bad high register in `[X0-]`"},
{"[X0-x]", "register list: bad high register in `[X0-x`"},
{"[X0-X1-X2]", "register list: expected ']' after `[X0-X1`, found '-'"},
{"[X0,X3]", "register list: expected '-' after `[X0`, found ','"},
{"[X0,X1,X2,X3]", "register list: expected '-' after `[X0`, found ','"},
}

var x86OperandTests = []operandTest{
{"$(2.928932188134524e-01)", "$(0.29289321881345243)"},
{"$-1", "$-1"},
Expand Down
57 changes: 54 additions & 3 deletions src/cmd/asm/internal/asm/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"cmd/asm/internal/flags"
"cmd/asm/internal/lex"
"cmd/internal/obj"
"cmd/internal/obj/x86"
"cmd/internal/src"
"cmd/internal/sys"
)
Expand Down Expand Up @@ -134,12 +135,12 @@ func (p *Parser) line() bool {
for {
tok = p.lex.Next()
if len(operands) == 0 && len(items) == 0 {
if p.arch.InFamily(sys.ARM, sys.ARM64) && tok == '.' {
// ARM conditionals.
if p.arch.InFamily(sys.ARM, sys.ARM64, sys.AMD64, sys.I386) && tok == '.' {
// Suffixes: ARM conditionals or x86 modifiers.
tok = p.lex.Next()
str := p.lex.Text()
if tok != scanner.Ident {
p.errorf("ARM condition expected identifier, found %s", str)
p.errorf("instruction suffix expected identifier, found %s", str)
}
cond = cond + "." + str
continue
Expand Down Expand Up @@ -827,8 +828,25 @@ func (p *Parser) registerIndirect(a *obj.Addr, prefix rune) {
// registers, as in [R1,R3-R5] or [V1.S4, V2.S4, V3.S4, V4.S4].
// For ARM, only R0 through R15 may appear.
// For ARM64, V0 through V31 with arrangement may appear.
//
// For 386/AMD64 register list specifies 4VNNIW-style multi-source operand.
// For range of 4 elements, Intel manual uses "+3" notation, for example:
// VP4DPWSSDS zmm1{k1}{z}, zmm2+3, m128
// Given asm line:
// VP4DPWSSDS Z5, [Z10-Z13], (AX)
// zmm2 is Z10, and Z13 is the only valid value for it (Z10+3).
// Only simple ranges are accepted, like [Z0-Z3].
//
// The opening bracket has been consumed.
func (p *Parser) registerList(a *obj.Addr) {
if p.arch.InFamily(sys.I386, sys.AMD64) {
p.registerListX86(a)
} else {
p.registerListARM(a)
}
}

func (p *Parser) registerListARM(a *obj.Addr) {
// One range per loop.
var maxReg int
var bits uint16
Expand Down Expand Up @@ -923,6 +941,39 @@ ListLoop:
}
}

func (p *Parser) registerListX86(a *obj.Addr) {
// Accept only [RegA-RegB] syntax.
// Don't use p.get() to provide better error messages.

loName := p.next().String()
lo, ok := p.arch.Register[loName]
if !ok {
if loName == "EOF" {
p.errorf("register list: expected ']', found EOF")
} else {
p.errorf("register list: bad low register in `[%s`", loName)
}
return
}
if tok := p.next().ScanToken; tok != '-' {
p.errorf("register list: expected '-' after `[%s`, found %s", loName, tok)
return
}
hiName := p.next().String()
hi, ok := p.arch.Register[hiName]
if !ok {
p.errorf("register list: bad high register in `[%s-%s`", loName, hiName)
return
}
if tok := p.next().ScanToken; tok != ']' {
p.errorf("register list: expected ']' after `[%s-%s`, found %s", loName, hiName, tok)
}

a.Type = obj.TYPE_REGLIST
a.Reg = lo
a.Offset = x86.EncodeRegisterRange(lo, hi)
}

// register number is ARM-specific. It returns the number of the specified register.
func (p *Parser) registerNumber(name string) uint16 {
if p.arch.Family == sys.ARM && name == "g" {
Expand Down
62 changes: 62 additions & 0 deletions src/cmd/asm/internal/asm/testdata/amd64error.s
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ TEXT errors(SB),$0
// No VSIB for legacy instructions.
MOVL (AX)(X0*1), AX // ERROR "invalid instruction"
MOVL (AX)(Y0*1), AX // ERROR "invalid instruction"
// VSIB/VM is invalid without vector index.
// TODO(quasilyte): improve error message (#21860).
// "invalid VSIB address (missing vector index)"
VPGATHERQQ Y2, (BP), Y1 // ERROR "invalid instruction"
// AVX2GATHER mask/index/dest #UD cases.
VPGATHERQQ Y2, (BP)(X2*2), Y2 // ERROR "mask, index, and destination registers should be distinct"
VPGATHERQQ Y2, (BP)(X2*2), Y7 // ERROR "mask, index, and destination registers should be distinct"
Expand Down Expand Up @@ -70,4 +74,62 @@ TEXT errors(SB),$0
MOVQ (AX), DR3 // ERROR "invalid instruction"
MOVQ (AX), DR6 // ERROR "invalid instruction"
MOVQ (AX), DR7 // ERROR "invalid instruction"
// AVX512GATHER index/index #UD cases.
VPGATHERQQ (BP)(X2*2), K1, X2 // ERROR "index and destination registers should be distinct"
VPGATHERQQ (BP)(Y15*2), K1, Y15 // ERROR "index and destination registers should be distinct"
VPGATHERQQ (BP)(Z20*2), K1, Z20 // ERROR "index and destination registers should be distinct"
VPGATHERDQ (BP)(X2*2), K1, X2 // ERROR "index and destination registers should be distinct"
VPGATHERDQ (BP)(X15*2), K1, Y15 // ERROR "index and destination registers should be distinct"
VPGATHERDQ (BP)(Y20*2), K1, Z20 // ERROR "index and destination registers should be distinct"
// Instructions without EVEX variant can't use High-16 registers.
VADDSUBPD X20, X1, X2 // ERROR "invalid instruction"
VADDSUBPS X0, X20, X2 // ERROR "invalid instruction"
// Use of K0 for write mask (Yknot0).
// TODO(quasilyte): improve error message (#21860).
// "K0 can't be used for write mask"
VADDPD X0, X1, K0, X2 // ERROR "invalid instruction"
VADDPD Y0, Y1, K0, Y2 // ERROR "invalid instruction"
VADDPD Z0, Z1, K0, Z2 // ERROR "invalid instruction"
// VEX-encoded VSIB can't use High-16 registers as index (unlike EVEX).
// TODO(quasilyte): improve error message (#21860).
VPGATHERQQ X2, (BP)(X20*2), X3 // ERROR "invalid instruction"
VPGATHERQQ Y2, (BP)(Y20*2), Y3 // ERROR "invalid instruction"
// YzrMulti4 expects exactly 4 registers referenced by REG_LIST.
// TODO(quasilyte): improve error message (#21860).
V4FMADDPS (AX), [Z0-Z4], K1, Z7 // ERROR "invalid instruction"
V4FMADDPS (AX), [Z0-Z0], K1, Z7 // ERROR "invalid instruction"
// Invalid ranges in REG_LIST (low > high).
// TODO(quasilyte): improve error message (#21860).
V4FMADDPS (AX), [Z4-Z0], K1, Z7 // ERROR "invalid instruction"
V4FMADDPS (AX), [Z1-Z0], K1, Z7 // ERROR "invalid instruction"
// Mismatching registers in a range.
// TODO(quasilyte): improve error message (#21860).
V4FMADDPS (AX), [AX-Z3], K1, Z7 // ERROR "invalid instruction"
V4FMADDPS (AX), [Z0-AX], K1, Z7 // ERROR "invalid instruction"
// Usage of suffixes for non-EVEX instructions.
ADCB.Z $7, AL // ERROR "invalid instruction"
ADCB.RU_SAE $7, AL // ERROR "invalid instruction"
ADCB.RU_SAE.Z $7, AL // ERROR "invalid instruction"
// Usage of rounding with invalid operands.
VADDPD.RU_SAE X3, X2, K1, X1 // ERROR "unsupported rounding"
VADDPD.RD_SAE X3, X2, K1, X1 // ERROR "unsupported rounding"
VADDPD.RZ_SAE X3, X2, K1, X1 // ERROR "unsupported rounding"
VADDPD.RN_SAE X3, X2, K1, X1 // ERROR "unsupported rounding"
VADDPD.RU_SAE Y3, Y2, K1, Y1 // ERROR "unsupported rounding"
VADDPD.RD_SAE Y3, Y2, K1, Y1 // ERROR "unsupported rounding"
VADDPD.RZ_SAE Y3, Y2, K1, Y1 // ERROR "unsupported rounding"
VADDPD.RN_SAE Y3, Y2, K1, Y1 // ERROR "unsupported rounding"
// Unsupported SAE.
VMAXPD.SAE (AX), Z2, K1, Z1 // ERROR "illegal SAE with memory argument"
VADDPD.SAE X3, X2, K1, X1 // ERROR "unsupported SAE"
// Unsupported zeroing.
VFPCLASSPDX.Z $0, (AX), K2, K1 // ERROR "unsupported zeroing"
VFPCLASSPDY.Z $0, (AX), K2, K1 // ERROR "unsupported zeroing"
// Unsupported broadcast.
VFPCLASSSD.BCST $0, (AX), K2, K1 // ERROR "unsupported broadcast"
VFPCLASSSS.BCST $0, (AX), K2, K1 // ERROR "unsupported broadcast"
// Broadcast without memory operand.
VADDPD.BCST X3, X2, K1, X1 // ERROR "illegal broadcast without memory argument"
VADDPD.BCST X3, X2, K1, X1 // ERROR "illegal broadcast without memory argument"
VADDPD.BCST X3, X2, K1, X1 // ERROR "illegal broadcast without memory argument"
RET
Loading

0 comments on commit 5437cde

Please sign in to comment.