383 lines
9.3 KiB
Go
383 lines
9.3 KiB
Go
|
// 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 x86
|
||
|
|
||
|
import (
|
||
|
"github.com/twitchyliquid64/golang-asm/obj"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// evexBits stores EVEX prefix info that is used during instruction encoding.
|
||
|
type evexBits struct {
|
||
|
b1 byte // [W1mmLLpp]
|
||
|
b2 byte // [NNNbbZRS]
|
||
|
|
||
|
// Associated instruction opcode.
|
||
|
opcode byte
|
||
|
}
|
||
|
|
||
|
// newEVEXBits creates evexBits object from enc bytes at z position.
|
||
|
func newEVEXBits(z int, enc *opBytes) evexBits {
|
||
|
return evexBits{
|
||
|
b1: enc[z+0],
|
||
|
b2: enc[z+1],
|
||
|
opcode: enc[z+2],
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// P returns EVEX.pp value.
|
||
|
func (evex evexBits) P() byte { return (evex.b1 & evexP) >> 0 }
|
||
|
|
||
|
// L returns EVEX.L'L value.
|
||
|
func (evex evexBits) L() byte { return (evex.b1 & evexL) >> 2 }
|
||
|
|
||
|
// M returns EVEX.mm value.
|
||
|
func (evex evexBits) M() byte { return (evex.b1 & evexM) >> 4 }
|
||
|
|
||
|
// W returns EVEX.W value.
|
||
|
func (evex evexBits) W() byte { return (evex.b1 & evexW) >> 7 }
|
||
|
|
||
|
// BroadcastEnabled reports whether BCST suffix is permitted.
|
||
|
func (evex evexBits) BroadcastEnabled() bool {
|
||
|
return evex.b2&evexBcst != 0
|
||
|
}
|
||
|
|
||
|
// ZeroingEnabled reports whether Z suffix is permitted.
|
||
|
func (evex evexBits) ZeroingEnabled() bool {
|
||
|
return (evex.b2&evexZeroing)>>2 != 0
|
||
|
}
|
||
|
|
||
|
// RoundingEnabled reports whether RN_SAE, RZ_SAE, RD_SAE and RU_SAE suffixes
|
||
|
// are permitted.
|
||
|
func (evex evexBits) RoundingEnabled() bool {
|
||
|
return (evex.b2&evexRounding)>>1 != 0
|
||
|
}
|
||
|
|
||
|
// SaeEnabled reports whether SAE suffix is permitted.
|
||
|
func (evex evexBits) SaeEnabled() bool {
|
||
|
return (evex.b2&evexSae)>>0 != 0
|
||
|
}
|
||
|
|
||
|
// DispMultiplier returns displacement multiplier that is calculated
|
||
|
// based on tuple type, EVEX.W and input size.
|
||
|
// If embedded broadcast is used, bcst should be true.
|
||
|
func (evex evexBits) DispMultiplier(bcst bool) int32 {
|
||
|
if bcst {
|
||
|
switch evex.b2 & evexBcst {
|
||
|
case evexBcstN4:
|
||
|
return 4
|
||
|
case evexBcstN8:
|
||
|
return 8
|
||
|
}
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
switch evex.b2 & evexN {
|
||
|
case evexN1:
|
||
|
return 1
|
||
|
case evexN2:
|
||
|
return 2
|
||
|
case evexN4:
|
||
|
return 4
|
||
|
case evexN8:
|
||
|
return 8
|
||
|
case evexN16:
|
||
|
return 16
|
||
|
case evexN32:
|
||
|
return 32
|
||
|
case evexN64:
|
||
|
return 64
|
||
|
case evexN128:
|
||
|
return 128
|
||
|
}
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// EVEX is described by using 2-byte sequence.
|
||
|
// See evexBits for more details.
|
||
|
const (
|
||
|
evexW = 0x80 // b1[W... ....]
|
||
|
evexWIG = 0 << 7
|
||
|
evexW0 = 0 << 7
|
||
|
evexW1 = 1 << 7
|
||
|
|
||
|
evexM = 0x30 // b2[..mm ...]
|
||
|
evex0F = 1 << 4
|
||
|
evex0F38 = 2 << 4
|
||
|
evex0F3A = 3 << 4
|
||
|
|
||
|
evexL = 0x0C // b1[.... LL..]
|
||
|
evexLIG = 0 << 2
|
||
|
evex128 = 0 << 2
|
||
|
evex256 = 1 << 2
|
||
|
evex512 = 2 << 2
|
||
|
|
||
|
evexP = 0x03 // b1[.... ..pp]
|
||
|
evex66 = 1 << 0
|
||
|
evexF3 = 2 << 0
|
||
|
evexF2 = 3 << 0
|
||
|
|
||
|
// Precalculated Disp8 N value.
|
||
|
// N acts like a multiplier for 8bit displacement.
|
||
|
// Note that some N are not used, but their bits are reserved.
|
||
|
evexN = 0xE0 // b2[NNN. ....]
|
||
|
evexN1 = 0 << 5
|
||
|
evexN2 = 1 << 5
|
||
|
evexN4 = 2 << 5
|
||
|
evexN8 = 3 << 5
|
||
|
evexN16 = 4 << 5
|
||
|
evexN32 = 5 << 5
|
||
|
evexN64 = 6 << 5
|
||
|
evexN128 = 7 << 5
|
||
|
|
||
|
// Disp8 for broadcasts.
|
||
|
evexBcst = 0x18 // b2[...b b...]
|
||
|
evexBcstN4 = 1 << 3
|
||
|
evexBcstN8 = 2 << 3
|
||
|
|
||
|
// Flags that permit certain AVX512 features.
|
||
|
// It's semantically illegal to combine evexZeroing and evexSae.
|
||
|
evexZeroing = 0x4 // b2[.... .Z..]
|
||
|
evexZeroingEnabled = 1 << 2
|
||
|
evexRounding = 0x2 // b2[.... ..R.]
|
||
|
evexRoundingEnabled = 1 << 1
|
||
|
evexSae = 0x1 // b2[.... ...S]
|
||
|
evexSaeEnabled = 1 << 0
|
||
|
)
|
||
|
|
||
|
// compressedDisp8 calculates EVEX compressed displacement, if applicable.
|
||
|
func compressedDisp8(disp, elemSize int32) (disp8 byte, ok bool) {
|
||
|
if disp%elemSize == 0 {
|
||
|
v := disp / elemSize
|
||
|
if v >= -128 && v <= 127 {
|
||
|
return byte(v), true
|
||
|
}
|
||
|
}
|
||
|
return 0, false
|
||
|
}
|
||
|
|
||
|
// evexZcase reports whether given Z-case belongs to EVEX group.
|
||
|
func evexZcase(zcase uint8) bool {
|
||
|
return zcase > Zevex_first && zcase < Zevex_last
|
||
|
}
|
||
|
|
||
|
// evexSuffixBits carries instruction EVEX suffix set flags.
|
||
|
//
|
||
|
// Examples:
|
||
|
// "RU_SAE.Z" => {rounding: 3, zeroing: true}
|
||
|
// "Z" => {zeroing: true}
|
||
|
// "BCST" => {broadcast: true}
|
||
|
// "SAE.Z" => {sae: true, zeroing: true}
|
||
|
type evexSuffix struct {
|
||
|
rounding byte
|
||
|
sae bool
|
||
|
zeroing bool
|
||
|
broadcast bool
|
||
|
}
|
||
|
|
||
|
// Rounding control values.
|
||
|
// Match exact value for EVEX.L'L field (with exception of rcUnset).
|
||
|
const (
|
||
|
rcRNSAE = 0 // Round towards nearest
|
||
|
rcRDSAE = 1 // Round towards -Inf
|
||
|
rcRUSAE = 2 // Round towards +Inf
|
||
|
rcRZSAE = 3 // Round towards zero
|
||
|
rcUnset = 4
|
||
|
)
|
||
|
|
||
|
// newEVEXSuffix returns proper zero value for evexSuffix.
|
||
|
func newEVEXSuffix() evexSuffix {
|
||
|
return evexSuffix{rounding: rcUnset}
|
||
|
}
|
||
|
|
||
|
// evexSuffixMap maps obj.X86suffix to its decoded version.
|
||
|
// Filled during init().
|
||
|
var evexSuffixMap [255]evexSuffix
|
||
|
|
||
|
func init() {
|
||
|
// Decode all valid suffixes for later use.
|
||
|
for i := range opSuffixTable {
|
||
|
suffix := newEVEXSuffix()
|
||
|
parts := strings.Split(opSuffixTable[i], ".")
|
||
|
for j := range parts {
|
||
|
switch parts[j] {
|
||
|
case "Z":
|
||
|
suffix.zeroing = true
|
||
|
case "BCST":
|
||
|
suffix.broadcast = true
|
||
|
case "SAE":
|
||
|
suffix.sae = true
|
||
|
|
||
|
case "RN_SAE":
|
||
|
suffix.rounding = rcRNSAE
|
||
|
case "RD_SAE":
|
||
|
suffix.rounding = rcRDSAE
|
||
|
case "RU_SAE":
|
||
|
suffix.rounding = rcRUSAE
|
||
|
case "RZ_SAE":
|
||
|
suffix.rounding = rcRZSAE
|
||
|
}
|
||
|
}
|
||
|
evexSuffixMap[i] = suffix
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// toDisp8 tries to convert disp to proper 8-bit displacement value.
|
||
|
func toDisp8(disp int32, p *obj.Prog, asmbuf *AsmBuf) (disp8 byte, ok bool) {
|
||
|
if asmbuf.evexflag {
|
||
|
bcst := evexSuffixMap[p.Scond].broadcast
|
||
|
elemSize := asmbuf.evex.DispMultiplier(bcst)
|
||
|
return compressedDisp8(disp, elemSize)
|
||
|
}
|
||
|
return byte(disp), disp >= -128 && disp < 128
|
||
|
}
|
||
|
|
||
|
// EncodeRegisterRange packs [reg0-reg1] list into 64-bit value that
|
||
|
// is intended to be stored inside obj.Addr.Offset with TYPE_REGLIST.
|
||
|
func EncodeRegisterRange(reg0, reg1 int16) int64 {
|
||
|
return (int64(reg0) << 0) |
|
||
|
(int64(reg1) << 16) |
|
||
|
obj.RegListX86Lo
|
||
|
}
|
||
|
|
||
|
// decodeRegisterRange unpacks [reg0-reg1] list from 64-bit value created by EncodeRegisterRange.
|
||
|
func decodeRegisterRange(list int64) (reg0, reg1 int) {
|
||
|
return int((list >> 0) & 0xFFFF),
|
||
|
int((list >> 16) & 0xFFFF)
|
||
|
}
|
||
|
|
||
|
// ParseSuffix handles the special suffix for the 386/AMD64.
|
||
|
// Suffix bits are stored into p.Scond.
|
||
|
//
|
||
|
// Leading "." in cond is ignored.
|
||
|
func ParseSuffix(p *obj.Prog, cond string) error {
|
||
|
cond = strings.TrimPrefix(cond, ".")
|
||
|
|
||
|
suffix := newOpSuffix(cond)
|
||
|
if !suffix.IsValid() {
|
||
|
return inferSuffixError(cond)
|
||
|
}
|
||
|
|
||
|
p.Scond = uint8(suffix)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// inferSuffixError returns non-nil error that describes what could be
|
||
|
// the cause of suffix parse failure.
|
||
|
//
|
||
|
// At the point this function is executed there is already assembly error,
|
||
|
// so we can burn some clocks to construct good error message.
|
||
|
//
|
||
|
// Reported issues:
|
||
|
// - duplicated suffixes
|
||
|
// - illegal rounding/SAE+broadcast combinations
|
||
|
// - unknown suffixes
|
||
|
// - misplaced suffix (e.g. wrong Z suffix position)
|
||
|
func inferSuffixError(cond string) error {
|
||
|
suffixSet := make(map[string]bool) // Set for duplicates detection.
|
||
|
unknownSet := make(map[string]bool) // Set of unknown suffixes.
|
||
|
hasBcst := false
|
||
|
hasRoundSae := false
|
||
|
var msg []string // Error message parts
|
||
|
|
||
|
suffixes := strings.Split(cond, ".")
|
||
|
for i, suffix := range suffixes {
|
||
|
switch suffix {
|
||
|
case "Z":
|
||
|
if i != len(suffixes)-1 {
|
||
|
msg = append(msg, "Z suffix should be the last")
|
||
|
}
|
||
|
case "BCST":
|
||
|
hasBcst = true
|
||
|
case "SAE", "RN_SAE", "RZ_SAE", "RD_SAE", "RU_SAE":
|
||
|
hasRoundSae = true
|
||
|
default:
|
||
|
if !unknownSet[suffix] {
|
||
|
msg = append(msg, fmt.Sprintf("unknown suffix %q", suffix))
|
||
|
}
|
||
|
unknownSet[suffix] = true
|
||
|
}
|
||
|
|
||
|
if suffixSet[suffix] {
|
||
|
msg = append(msg, fmt.Sprintf("duplicate suffix %q", suffix))
|
||
|
}
|
||
|
suffixSet[suffix] = true
|
||
|
}
|
||
|
|
||
|
if hasBcst && hasRoundSae {
|
||
|
msg = append(msg, "can't combine rounding/SAE and broadcast")
|
||
|
}
|
||
|
|
||
|
if len(msg) == 0 {
|
||
|
return errors.New("bad suffix combination")
|
||
|
}
|
||
|
return errors.New(strings.Join(msg, "; "))
|
||
|
}
|
||
|
|
||
|
// opSuffixTable is a complete list of possible opcode suffix combinations.
|
||
|
// It "maps" uint8 suffix bits to their string representation.
|
||
|
// With the exception of first and last elements, order is not important.
|
||
|
var opSuffixTable = [...]string{
|
||
|
"", // Map empty suffix to empty string.
|
||
|
|
||
|
"Z",
|
||
|
|
||
|
"SAE",
|
||
|
"SAE.Z",
|
||
|
|
||
|
"RN_SAE",
|
||
|
"RZ_SAE",
|
||
|
"RD_SAE",
|
||
|
"RU_SAE",
|
||
|
"RN_SAE.Z",
|
||
|
"RZ_SAE.Z",
|
||
|
"RD_SAE.Z",
|
||
|
"RU_SAE.Z",
|
||
|
|
||
|
"BCST",
|
||
|
"BCST.Z",
|
||
|
|
||
|
"<bad suffix>",
|
||
|
}
|
||
|
|
||
|
// opSuffix represents instruction opcode suffix.
|
||
|
// Compound (multi-part) suffixes expressed with single opSuffix value.
|
||
|
//
|
||
|
// uint8 type is used to fit obj.Prog.Scond.
|
||
|
type opSuffix uint8
|
||
|
|
||
|
// badOpSuffix is used to represent all invalid suffix combinations.
|
||
|
const badOpSuffix = opSuffix(len(opSuffixTable) - 1)
|
||
|
|
||
|
// newOpSuffix returns opSuffix object that matches suffixes string.
|
||
|
//
|
||
|
// If no matching suffix is found, special "invalid" suffix is returned.
|
||
|
// Use IsValid method to check against this case.
|
||
|
func newOpSuffix(suffixes string) opSuffix {
|
||
|
for i := range opSuffixTable {
|
||
|
if opSuffixTable[i] == suffixes {
|
||
|
return opSuffix(i)
|
||
|
}
|
||
|
}
|
||
|
return badOpSuffix
|
||
|
}
|
||
|
|
||
|
// IsValid reports whether suffix is valid.
|
||
|
// Empty suffixes are valid.
|
||
|
func (suffix opSuffix) IsValid() bool {
|
||
|
return suffix != badOpSuffix
|
||
|
}
|
||
|
|
||
|
// String returns suffix printed representation.
|
||
|
//
|
||
|
// It matches the string that was used to create suffix with NewX86Suffix()
|
||
|
// for valid suffixes.
|
||
|
// For all invalid suffixes, special marker is returned.
|
||
|
func (suffix opSuffix) String() string {
|
||
|
return opSuffixTable[suffix]
|
||
|
}
|