// Copyright 2024 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 riscv64asm

import (
	"encoding/binary"
	"errors"
)

type argTypeList [6]argType

// An instFormat describes the format of an instruction encoding.
type instFormat struct {
	mask  uint32
	value uint32
	op    Op
	// args describe how to decode the instruction arguments.
	// args is stored as a fixed-size array.
	// if there are fewer than len(args) arguments, args[i] == 0 marks
	// the end of the argument list.
	args argTypeList
}

var (
	errShort   = errors.New("truncated instruction")
	errUnknown = errors.New("unknown instruction")
)

var decoderCover []bool

func init() {
	decoderCover = make([]bool, len(instFormats))
}

// Decode decodes the 4 bytes in src as a single instruction.
func Decode(src []byte) (Inst, error) {
	length := len(src)
	if length < 2 {
		return Inst{}, errShort
	}

	var x uint32
	// Non-RVC instructions always starts with 0x11
	// So check whether src[0] & 3 == 3
	if src[0]&3 == 3 {
		if length < 4 {
			return Inst{}, errShort
		}
		length = 4
		x = binary.LittleEndian.Uint32(src)
	} else {
		length = 2
		x = uint32(binary.LittleEndian.Uint16(src))
	}

Search:
	for i, f := range instFormats {
		if (x & f.mask) != f.value {
			continue
		}

		// Decode args.
		var args Args
		for j, aop := range f.args {
			if aop == 0 {
				break
			}
			arg := decodeArg(aop, x, i)
			if arg == nil && f.op != C_NOP {
				// Cannot decode argument.
				continue Search
			}
			args[j] = arg
		}

		if length == 2 {
			args = convertCompressedIns(&f, args)
		}

		decoderCover[i] = true
		inst := Inst{
			Op:   f.op,
			Args: args,
			Enc:  x,
			Len:  length,
		}
		return inst, nil
	}
	return Inst{}, errUnknown
}

// decodeArg decodes the arg described by aop from the instruction bits x.
// It returns nil if x cannot be decoded according to aop.
func decodeArg(aop argType, x uint32, index int) Arg {
	switch aop {
	case arg_rd:
		return X0 + Reg((x>>7)&((1<<5)-1))

	case arg_rs1:
		return X0 + Reg((x>>15)&((1<<5)-1))

	case arg_rs2:
		return X0 + Reg((x>>20)&((1<<5)-1))

	case arg_rs3:
		return X0 + Reg((x>>27)&((1<<5)-1))

	case arg_fd:
		return F0 + Reg((x>>7)&((1<<5)-1))

	case arg_fs1:
		return F0 + Reg((x>>15)&((1<<5)-1))

	case arg_fs2:
		return F0 + Reg((x>>20)&((1<<5)-1))

	case arg_fs3:
		return F0 + Reg((x>>27)&((1<<5)-1))

	case arg_rs1_amo:
		return AmoReg{X0 + Reg((x>>15)&((1<<5)-1))}

	case arg_rs1_mem:
		imm := x >> 20
		// Sign-extend
		if imm>>uint32(12-1) == 1 {
			imm |= 0xfffff << 12
		}
		return RegOffset{X0 + Reg((x>>15)&((1<<5)-1)), Simm{int32(imm), true, 12}}

	case arg_rs1_store:
		imm := (x<<20)>>27 | (x>>25)<<5
		// Sign-extend
		if imm>>uint32(12-1) == 1 {
			imm |= 0xfffff << 12
		}
		return RegOffset{X0 + Reg((x>>15)&((1<<5)-1)), Simm{int32(imm), true, 12}}

	case arg_pred:
		imm := x << 4 >> 28
		return MemOrder(uint8(imm))

	case arg_succ:
		imm := x << 8 >> 28
		return MemOrder(uint8(imm))

	case arg_csr:
		imm := x >> 20
		return CSR(imm)

	case arg_zimm:
		imm := x << 12 >> 27
		return Uimm{imm, true}

	case arg_shamt5:
		imm := x << 7 >> 27
		return Uimm{imm, false}

	case arg_shamt6:
		imm := x << 6 >> 26
		return Uimm{imm, false}

	case arg_imm12:
		imm := x >> 20
		// Sign-extend
		if imm>>uint32(12-1) == 1 {
			imm |= 0xfffff << 12
		}
		return Simm{int32(imm), true, 12}

	case arg_imm20:
		imm := x >> 12
		return Uimm{imm, false}

	case arg_jimm20:
		imm := (x>>31)<<20 | (x<<1)>>22<<1 | (x<<11)>>31<<11 | (x<<12)>>24<<12
		// Sign-extend
		if imm>>uint32(21-1) == 1 {
			imm |= 0x7ff << 21
		}
		return Simm{int32(imm), true, 21}

	case arg_simm12:
		imm := (x<<20)>>27 | (x>>25)<<5
		// Sign-extend
		if imm>>uint32(12-1) == 1 {
			imm |= 0xfffff << 12
		}
		return Simm{int32(imm), true, 12}

	case arg_bimm12:
		imm := (x<<20)>>28<<1 | (x<<1)>>26<<5 | (x<<24)>>31<<11 | (x>>31)<<12
		// Sign-extend
		if imm>>uint32(13-1) == 1 {
			imm |= 0x7ffff << 13
		}
		return Simm{int32(imm), true, 13}

	case arg_rd_p, arg_rs2_p:
		return X8 + Reg((x>>2)&((1<<3)-1))

	case arg_fd_p, arg_fs2_p:
		return F8 + Reg((x>>2)&((1<<3)-1))

	case arg_rs1_p, arg_rd_rs1_p:
		return X8 + Reg((x>>7)&((1<<3)-1))

	case arg_rd_n0, arg_rs1_n0, arg_rd_rs1_n0, arg_c_rs1_n0:
		if X0+Reg((x>>7)&((1<<5)-1)) == X0 {
			return nil
		}
		return X0 + Reg((x>>7)&((1<<5)-1))

	case arg_c_rs2_n0:
		if X0+Reg((x>>2)&((1<<5)-1)) == X0 {
			return nil
		}
		return X0 + Reg((x>>2)&((1<<5)-1))

	case arg_c_fs2:
		return F0 + Reg((x>>2)&((1<<5)-1))

	case arg_c_rs2:
		return X0 + Reg((x>>2)&((1<<5)-1))

	case arg_rd_n2:
		if X0+Reg((x>>7)&((1<<5)-1)) == X0 || X0+Reg((x>>7)&((1<<5)-1)) == X2 {
			return nil
		}
		return X0 + Reg((x>>7)&((1<<5)-1))

	case arg_c_imm6:
		imm := (x<<25)>>27 | (x<<19)>>31<<5
		// Sign-extend
		if imm>>uint32(6-1) == 1 {
			imm |= 0x3ffffff << 6
		}
		return Simm{int32(imm), true, 6}

	case arg_c_nzimm6:
		imm := (x<<25)>>27 | (x<<19)>>31<<5
		// Sign-extend
		if imm>>uint32(6-1) == 1 {
			imm |= 0x3ffffff << 6
		}
		if int32(imm) == 0 {
			return nil
		}
		return Simm{int32(imm), true, 6}

	case arg_c_nzuimm6:
		imm := (x<<25)>>27 | (x<<19)>>31<<5
		if int32(imm) == 0 {
			return nil
		}
		return Uimm{imm, false}

	case arg_c_uimm7:
		imm := (x<<26)>>31<<6 | (x<<25)>>31<<2 | (x<<19)>>29<<3
		return Uimm{imm, false}

	case arg_c_uimm8:
		imm := (x<<25)>>30<<6 | (x<<19)>>29<<3
		return Uimm{imm, false}

	case arg_c_uimm8sp_s:
		imm := (x<<23)>>30<<6 | (x<<19)>>28<<2
		return Uimm{imm, false}

	case arg_c_uimm8sp:
		imm := (x<<25)>>29<<2 | (x<<19)>>31<<5 | (x<<28)>>30<<6
		return Uimm{imm, false}

	case arg_c_uimm9sp_s:
		imm := (x<<22)>>29<<6 | (x<<19)>>29<<3
		return Uimm{imm, false}

	case arg_c_uimm9sp:
		imm := (x<<25)>>30<<3 | (x<<19)>>31<<5 | (x<<27)>>29<<6
		return Uimm{imm, false}

	case arg_c_bimm9:
		imm := (x<<29)>>31<<5 | (x<<27)>>30<<1 | (x<<25)>>30<<6 | (x<<19)>>31<<8 | (x<<20)>>30<<3
		// Sign-extend
		if imm>>uint32(9-1) == 1 {
			imm |= 0x7fffff << 9
		}
		return Simm{int32(imm), true, 9}

	case arg_c_nzimm10:
		imm := (x<<29)>>31<<5 | (x<<27)>>30<<7 | (x<<26)>>31<<6 | (x<<25)>>31<<4 | (x<<19)>>31<<9
		// Sign-extend
		if imm>>uint32(10-1) == 1 {
			imm |= 0x3fffff << 10
		}
		if int32(imm) == 0 {
			return nil
		}
		return Simm{int32(imm), true, 10}

	case arg_c_nzuimm10:
		imm := (x<<26)>>31<<3 | (x<<25)>>31<<2 | (x<<21)>>28<<6 | (x<<19)>>30<<4
		if int32(imm) == 0 {
			return nil
		}
		return Uimm{imm, false}

	case arg_c_imm12:
		imm := (x<<29)>>31<<5 | (x<<26)>>28<<1 | (x<<25)>>31<<7 | (x<<24)>>31<<6 | (x<<23)>>31<<10 | (x<<21)>>30<<8 | (x<<20)>>31<<4 | (x<<19)>>31<<11
		// Sign-extend
		if imm>>uint32(12-1) == 1 {
			imm |= 0xfffff << 12
		}
		return Simm{int32(imm), true, 12}

	case arg_c_nzimm18:
		imm := (x<<25)>>27<<12 | (x<<19)>>31<<17
		// Sign-extend
		if imm>>uint32(18-1) == 1 {
			imm |= 0x3fff << 18
		}
		if int32(imm) == 0 {
			return nil
		}
		return Simm{int32(imm), true, 18}

	default:
		return nil
	}
}

// convertCompressedIns rewrites the RVC Instruction to regular Instructions
func convertCompressedIns(f *instFormat, args Args) Args {
	var newargs Args
	switch f.op {
	case C_ADDI4SPN:
		f.op = ADDI
		newargs[0] = args[0]
		newargs[1] = Reg(X2)
		newargs[2] = Simm{int32(args[1].(Uimm).Imm), true, 12}

	case C_LW:
		f.op = LW
		newargs[0] = args[0]
		newargs[1] = RegOffset{args[1].(Reg), Simm{int32(args[2].(Uimm).Imm), true, 12}}

	case C_SW:
		f.op = SW
		newargs[0] = args[1]
		newargs[1] = RegOffset{args[0].(Reg), Simm{int32(args[2].(Uimm).Imm), true, 12}}

	case C_NOP:
		f.op = ADDI
		newargs[0] = X0
		newargs[1] = X0
		newargs[2] = Simm{0, true, 12}

	case C_ADDI:
		f.op = ADDI
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = Simm{args[1].(Simm).Imm, true, 12}

	case C_LI:
		f.op = ADDI
		newargs[0] = args[0]
		newargs[1] = Reg(X0)
		newargs[2] = Simm{args[1].(Simm).Imm, true, 12}

	case C_ADDI16SP:
		f.op = ADDI
		newargs[0] = Reg(X2)
		newargs[1] = Reg(X2)
		newargs[2] = Simm{args[0].(Simm).Imm, true, 12}

	case C_LUI:
		f.op = LUI
		newargs[0] = args[0]
		newargs[1] = Uimm{uint32(args[1].(Simm).Imm >> 12), false}

	case C_ANDI:
		f.op = ANDI
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = Simm{args[1].(Simm).Imm, true, 12}

	case C_SUB:
		f.op = SUB
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_XOR:
		f.op = XOR
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_OR:
		f.op = OR
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_AND:
		f.op = AND
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_J:
		f.op = JAL
		newargs[0] = Reg(X0)
		newargs[1] = Simm{args[0].(Simm).Imm, true, 21}

	case C_BEQZ:
		f.op = BEQ
		newargs[0] = args[0]
		newargs[1] = Reg(X0)
		newargs[2] = Simm{args[1].(Simm).Imm, true, 13}

	case C_BNEZ:
		f.op = BNE
		newargs[0] = args[0]
		newargs[1] = Reg(X0)
		newargs[2] = Simm{args[1].(Simm).Imm, true, 13}

	case C_LWSP:
		f.op = LW
		newargs[0] = args[0]
		newargs[1] = RegOffset{Reg(X2), Simm{int32(args[1].(Uimm).Imm), true, 12}}

	case C_JR:
		f.op = JALR
		newargs[0] = Reg(X0)
		newargs[1] = RegOffset{args[0].(Reg), Simm{0, true, 12}}

	case C_MV:
		f.op = ADD
		newargs[0] = args[0]
		newargs[1] = Reg(X0)
		newargs[2] = args[1]

	case C_EBREAK:
		f.op = EBREAK

	case C_JALR:
		f.op = JALR
		newargs[0] = Reg(X1)
		newargs[1] = RegOffset{args[0].(Reg), Simm{0, true, 12}}

	case C_ADD:
		f.op = ADD
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_SWSP:
		f.op = SW
		newargs[0] = args[0]
		newargs[1] = RegOffset{Reg(X2), Simm{int32(args[1].(Uimm).Imm), true, 12}}

	// riscv64 compressed instructions
	case C_LD:
		f.op = LD
		newargs[0] = args[0]
		newargs[1] = RegOffset{args[1].(Reg), Simm{int32(args[2].(Uimm).Imm), true, 12}}

	case C_SD:
		f.op = SD
		newargs[0] = args[1]
		newargs[1] = RegOffset{args[0].(Reg), Simm{int32(args[2].(Uimm).Imm), true, 12}}

	case C_ADDIW:
		f.op = ADDIW
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = Simm{args[1].(Simm).Imm, true, 12}

	case C_SRLI:
		f.op = SRLI
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_SRAI:
		f.op = SRAI
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_SUBW:
		f.op = SUBW
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_ADDW:
		f.op = ADDW
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_SLLI:
		f.op = SLLI
		newargs[0] = args[0]
		newargs[1] = args[0]
		newargs[2] = args[1]

	case C_LDSP:
		f.op = LD
		newargs[0] = args[0]
		newargs[1] = RegOffset{Reg(X2), Simm{int32(args[1].(Uimm).Imm), true, 12}}

	case C_SDSP:
		f.op = SD
		newargs[0] = args[0]
		newargs[1] = RegOffset{Reg(X2), Simm{int32(args[1].(Uimm).Imm), true, 12}}

	// riscv double precision floating point compressed instructions
	case C_FLD:
		f.op = FLD
		newargs[0] = args[0]
		newargs[1] = RegOffset{args[1].(Reg), Simm{int32(args[2].(Uimm).Imm), true, 12}}

	case C_FSD:
		f.op = FSD
		newargs[0] = args[1]
		newargs[1] = RegOffset{args[0].(Reg), Simm{int32(args[2].(Uimm).Imm), true, 12}}

	case C_FLDSP:
		f.op = FLD
		newargs[0] = args[0]
		newargs[1] = RegOffset{Reg(X2), Simm{int32(args[1].(Uimm).Imm), true, 12}}

	case C_FSDSP:
		f.op = FSD
		newargs[0] = args[0]
		newargs[1] = RegOffset{Reg(X2), Simm{int32(args[1].(Uimm).Imm), true, 12}}

	case C_UNIMP:
		f.op = CSRRW
		newargs[0] = Reg(X0)
		newargs[1] = CSR(CYCLE)
		newargs[2] = Reg(X0)
	}
	return newargs
}
