// 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]
}
