golang.org/x/arch@v0.17.0/riscv64/riscv64spec/spec.go (about)

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // riscv64spec reads the files contained in riscv-opcodes repo
     6  // to collect instruction encoding details.
     7  // repo url: https://github.com/riscv/riscv-opcodes
     8  // usage: go run spec.go <opcodes-repo-path>
     9  
    10  package main
    11  
    12  import (
    13  	"bufio"
    14  	"fmt"
    15  	"log"
    16  	"os"
    17  	"path/filepath"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  )
    22  
    23  // RV64GC_zba_zbb_zbs Extensions Listing
    24  // Reference: $GOROOT/src/src/cmd/internal/obj/riscv/inst.go
    25  var extensions = []string{
    26  	"rv_a",
    27  	"rv_c",
    28  	"rv_c_d",
    29  	"rv_d",
    30  	"rv_f",
    31  	"rv_i",
    32  	"rv_m",
    33  	"rv_q",
    34  	"rv_zba",
    35  	"rv_zbb",
    36  	"rv_zbs",
    37  	"rv_zfh",
    38  	"rv_zicsr",
    39  	"rv_zifencei",
    40  	"rv64_a",
    41  	"rv64_c",
    42  	"rv64_d",
    43  	"rv64_f",
    44  	"rv64_i",
    45  	"rv64_m",
    46  	"rv64_q",
    47  	"rv64_zba",
    48  	"rv64_zbb",
    49  	"rv64_zbs",
    50  	"rv64_zfh",
    51  }
    52  
    53  const (
    54  	prologueSec    = "// Code generated by riscv64spec riscv-opcodes\n// DO NOT EDIT\n\n// Copyright 2024 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage riscv64asm\n\n"
    55  	opSec          = "const (\n\t_ Op = iota\n"
    56  	opstrSec       = "var opstr = [...]string{\n"
    57  	instFormatsSec = "var instFormats = [...]instFormat{\n"
    58  )
    59  
    60  var (
    61  	ops                []string
    62  	opstrs             = make(map[string]string)
    63  	instFormatComments = make(map[string]string)
    64  	instFormats        = make(map[string]string)
    65  )
    66  
    67  func main() {
    68  	log.SetFlags(0)
    69  	log.SetPrefix("riscv64spec: ")
    70  
    71  	var repoPath string
    72  	if len(os.Args) < 1 {
    73  		log.Fatal("usage: go run spec.go <opcodes-repo-path>")
    74  	}
    75  	repoPath = os.Args[1]
    76  
    77  	fileTables, err := os.Create("tables.go")
    78  	if err != nil {
    79  		log.Fatal(err)
    80  	}
    81  
    82  	buf := bufio.NewWriter(fileTables)
    83  	_, err = buf.Write([]byte(prologueSec))
    84  	if err != nil {
    85  		log.Fatal(err)
    86  	}
    87  
    88  	for _, ext := range extensions {
    89  		f, err := os.Open(filepath.Join(repoPath, ext))
    90  		if err != nil {
    91  			log.Fatal(err)
    92  		}
    93  		defer f.Close()
    94  
    95  		buf := bufio.NewScanner(f)
    96  		for buf.Scan() {
    97  			line := buf.Text()
    98  			if len(line) == 0 {
    99  				continue
   100  			}
   101  			words := strings.Fields(line)
   102  			if len(words) == 0 || words[0][0] == '#' {
   103  				continue
   104  			}
   105  
   106  			// skip $pseudo_op except rv_zbb/rv64_zbb
   107  			if words[0][0] == '$' {
   108  				if ext != "rv_zbb" && ext != "rv64_zbb" {
   109  					continue
   110  				}
   111  				words = words[2:]
   112  			}
   113  
   114  			genInst(words)
   115  		}
   116  	}
   117  
   118  	// c.unimp wasn't in riscv-opcodes, so add it there
   119  	c_unimp := "c.unimp 15..0=0"
   120  	genInst(strings.Fields(c_unimp))
   121  
   122  	sort.Strings(ops)
   123  
   124  	// 1. write op
   125  	if _, err := buf.Write([]byte(opSec)); err != nil {
   126  		log.Fatal(err)
   127  	}
   128  	for _, op := range ops {
   129  		if _, err := fmt.Fprintf(buf, "\t%s\n", op); err != nil {
   130  			log.Fatal(err)
   131  		}
   132  	}
   133  	if _, err := buf.Write([]byte(")\n\n")); err != nil {
   134  		log.Fatal(err)
   135  	}
   136  
   137  	// 2. write opstr
   138  	if _, err := buf.Write([]byte(opstrSec)); err != nil {
   139  		log.Fatal(err)
   140  	}
   141  	for _, op := range ops {
   142  		if _, err := fmt.Fprintf(buf, "\t%s\n", opstrs[op]); err != nil {
   143  			log.Fatal(err)
   144  		}
   145  	}
   146  	if _, err := buf.Write([]byte("}\n\n")); err != nil {
   147  		log.Fatal(err)
   148  	}
   149  
   150  	// 3. write instFormatComment and instFormat
   151  	if _, err := buf.Write([]byte(instFormatsSec)); err != nil {
   152  		log.Fatal(err)
   153  	}
   154  	for _, op := range ops {
   155  		if _, err := fmt.Fprintf(buf, "\t%s\n\t%s\n", instFormatComments[op], instFormats[op]); err != nil {
   156  			log.Fatal(err)
   157  		}
   158  	}
   159  	if _, err = buf.Write([]byte("}\n")); err != nil {
   160  		log.Fatal(err)
   161  	}
   162  
   163  	if err := buf.Flush(); err != nil {
   164  		log.Fatal(err)
   165  	}
   166  
   167  	if err := fileTables.Close(); err != nil {
   168  		log.Fatal(err)
   169  	}
   170  }
   171  
   172  func genInst(words []string) {
   173  	op := strings.ToUpper(strings.Replace(words[0], ".", "_", -1))
   174  	opstr := fmt.Sprintf("%s:\t\"%s\",", op, strings.ToUpper(words[0]))
   175  
   176  	var value uint32
   177  	var mask uint32
   178  	var argTypeList []string
   179  
   180  	for i := 1; i < len(words); i++ {
   181  		if strings.Contains(words[i], "=") {
   182  			val := strings.Split(words[i], "=")
   183  			sec := strings.Split(val[0], "..")
   184  			if len(sec) < 2 {
   185  				sec[0] = val[0]
   186  			}
   187  			subval, submsk := genValueAndMask(val, sec)
   188  			value |= subval
   189  			mask |= submsk
   190  		} else if len(words[i]) > 0 {
   191  			argTypeList = append(argTypeList, words[i])
   192  		}
   193  	}
   194  
   195  	instArgsStr := inferFormats(argTypeList, op)
   196  	instFormatComment := "// " + strings.Replace(op, "_", ".", -1) + " " + strings.Replace(instArgsStr, "arg_", "", -1)
   197  	instFormat := fmt.Sprintf("{mask: %#08x, value: %#08x, op: %s, args: argTypeList{%s}},", mask, value, op, instArgsStr)
   198  
   199  	// Handle the suffix of atomic instruction.
   200  	if isAtomic(op) {
   201  		suffix := []string{"", ".RL", ".AQ", ".AQRL"}
   202  		// Re-generate the opcode string, opcode value and mask.
   203  		for i, suf := range suffix {
   204  			aop := op + strings.Replace(suf, ".", "_", -1)
   205  			aopstr := fmt.Sprintf("%s:\t\"%s\",", aop, strings.ToUpper(words[0])+suf)
   206  			avalue := value | (uint32(i) << 25)
   207  			amask := mask | 0x06000000
   208  			ainstFormatComment := "// " + strings.Replace(aop, "_", ".", -1) + " " + strings.Replace(instArgsStr, "arg_", "", -1)
   209  			ainstFormat := fmt.Sprintf("{mask: %#08x, value: %#08x, op: %s, args: argTypeList{%s}},", amask, avalue, aop, instArgsStr)
   210  			ops = append(ops, aop)
   211  			opstrs[aop] = aopstr
   212  			instFormats[aop] = ainstFormat
   213  			instFormatComments[aop] = ainstFormatComment
   214  		}
   215  	} else {
   216  		ops = append(ops, op)
   217  		opstrs[op] = opstr
   218  		instFormats[op] = instFormat
   219  		instFormatComments[op] = instFormatComment
   220  	}
   221  }
   222  
   223  // inferFormats identifies inst format:
   224  // R-Type (inst rd, rs1, rs2),
   225  // I-Type (inst rd, rs1, imm / inst rd, offset(rs1)),
   226  // UJ-Type (inst rd, imm),
   227  // U-Type (inst rd, imm),
   228  // SB-Type (inst rs1, rs2, offset)
   229  // S-Type (inst rs2, offset(rs1))
   230  func inferFormats(argTypeList []string, op string) string {
   231  	switch {
   232  	case strings.Contains(op, "AMO") || strings.Contains(op, "SC_"):
   233  		return "arg_rd, arg_rs2, arg_rs1_amo"
   234  
   235  	case strings.Contains(op, "LR_"):
   236  		return "arg_rd, arg_rs1_amo"
   237  
   238  	case op == "LB" || op == "LBU" || op == "LD" ||
   239  		op == "LH" || op == "LHU" || op == "LW" || op == "LWU":
   240  		return "arg_rd, arg_rs1_mem"
   241  
   242  	case op == "FLD" || op == "FLW" || op == "FLH" || op == "FLQ":
   243  		return "arg_fd, arg_rs1_mem"
   244  
   245  	case op == "FSD" || op == "FSW" || op == "FSH" || op == "FSQ":
   246  		return "arg_fs2, arg_rs1_store"
   247  
   248  	case op == "SD" || op == "SB" || op == "SW" || op == "SH":
   249  		return "arg_rs2, arg_rs1_store"
   250  
   251  	case op == "CSRRW" || op == "CSRRS" || op == "CSRRC":
   252  		return "arg_rd, arg_csr, arg_rs1"
   253  
   254  	case op == "CSRRWI" || op == "CSRRSI" || op == "CSRRCI":
   255  		return "arg_rd, arg_csr, arg_zimm"
   256  
   257  	case op == "JALR":
   258  		return "arg_rd, arg_rs1_mem"
   259  
   260  	case op == "FENCE_I":
   261  		return ""
   262  
   263  	case op == "FENCE":
   264  		return "arg_pred, arg_succ"
   265  
   266  	default:
   267  		var instStr []string
   268  		for _, arg := range argTypeList {
   269  			if decodeArgs(arg, op) != "" {
   270  				instStr = append(instStr, decodeArgs(arg, op))
   271  			}
   272  		}
   273  		return strings.Join(instStr, ", ")
   274  	}
   275  }
   276  
   277  // decodeArgs turns the args into formats defined in arg.go
   278  func decodeArgs(arg string, op string) string {
   279  	switch {
   280  	case strings.Contains("arg_rd", arg):
   281  		if isFloatReg(op, "rd") || strings.Contains(op, "C_FLDSP") {
   282  			return "arg_fd"
   283  		}
   284  		return "arg_rd"
   285  
   286  	case strings.Contains("arg_rs1", arg):
   287  		if isFloatReg(op, "rs") {
   288  			return "arg_fs1"
   289  		}
   290  		return "arg_rs1"
   291  
   292  	case strings.Contains("arg_rs2", arg):
   293  		if isFloatReg(op, "rs") {
   294  			return "arg_fs2"
   295  		}
   296  		return "arg_rs2"
   297  
   298  	case strings.Contains("arg_rs3", arg):
   299  		if isFloatReg(op, "rs") {
   300  			return "arg_fs3"
   301  		}
   302  		return "arg_rs3"
   303  
   304  	case arg == "imm12":
   305  		return "arg_imm12"
   306  
   307  	case arg == "imm20":
   308  		return "arg_imm20"
   309  
   310  	case arg == "jimm20":
   311  		return "arg_jimm20"
   312  
   313  	case arg == "bimm12lo":
   314  		return "arg_bimm12"
   315  
   316  	case arg == "imm12lo":
   317  		return "arg_simm12"
   318  
   319  	case arg == "shamtw":
   320  		return "arg_shamt5"
   321  
   322  	case arg == "shamtd":
   323  		return "arg_shamt6"
   324  
   325  	case arg == "rd_p":
   326  		if strings.Contains(op, "C_FLD") {
   327  			return "arg_fd_p"
   328  		}
   329  		return "arg_rd_p"
   330  
   331  	case arg == "rs1_p":
   332  		return "arg_rs1_p"
   333  
   334  	case arg == "rd_rs1_p":
   335  		return "arg_rd_rs1_p"
   336  
   337  	case arg == "rs2_p":
   338  		if strings.Contains(op, "C_FSD") {
   339  			return "arg_fs2_p"
   340  		}
   341  		return "arg_rs2_p"
   342  
   343  	case arg == "rd_n0":
   344  		return "arg_rd_n0"
   345  
   346  	case arg == "rs1_n0":
   347  		return "arg_rs1_n0"
   348  
   349  	case arg == "rd_rs1_n0":
   350  		return "arg_rd_rs1_n0"
   351  
   352  	case arg == "c_rs1_n0":
   353  		return "arg_c_rs1_n0"
   354  
   355  	case arg == "c_rs2_n0":
   356  		return "arg_c_rs2_n0"
   357  
   358  	case arg == "c_rs2":
   359  		if strings.Contains(op, "C_FSDSP") {
   360  			return "arg_c_fs2"
   361  		}
   362  		return "arg_c_rs2"
   363  
   364  	case arg == "rd_n2":
   365  		return "arg_rd_n2"
   366  
   367  	case arg == "c_imm6lo":
   368  		return "arg_c_imm6"
   369  
   370  	case arg == "c_nzimm6lo":
   371  		return "arg_c_nzimm6"
   372  
   373  	case arg == "c_nzuimm6lo":
   374  		return "arg_c_nzuimm6"
   375  
   376  	case arg == "c_uimm7lo":
   377  		return "arg_c_uimm7"
   378  
   379  	case arg == "c_uimm8lo":
   380  		return "arg_c_uimm8"
   381  
   382  	case arg == "c_uimm8sp_s":
   383  		return "arg_c_uimm8sp_s"
   384  
   385  	case arg == "c_uimm8splo":
   386  		return "arg_c_uimm8sp"
   387  
   388  	case arg == "c_uimm9sp_s":
   389  		return "arg_c_uimm9sp_s"
   390  
   391  	case arg == "c_uimm9splo":
   392  		return "arg_c_uimm9sp"
   393  
   394  	case arg == "c_bimm9lo":
   395  		return "arg_c_bimm9"
   396  
   397  	case arg == "c_nzimm10lo":
   398  		return "arg_c_nzimm10"
   399  
   400  	case arg == "c_nzuimm10":
   401  		return "arg_c_nzuimm10"
   402  
   403  	case arg == "c_imm12":
   404  		return "arg_c_imm12"
   405  
   406  	case arg == "c_nzimm18lo":
   407  		return "arg_c_nzimm18"
   408  	}
   409  	return ""
   410  }
   411  
   412  // genValueAndMask generates instruction value and relative mask.
   413  func genValueAndMask(valStr []string, secStr []string) (uint32, uint32) {
   414  	var val int64
   415  
   416  	val, err := strconv.ParseInt(valStr[1], 0, 32)
   417  	if err != nil {
   418  		log.Fatal(err)
   419  	}
   420  
   421  	l, err := strconv.Atoi(secStr[0])
   422  	if err != nil {
   423  		log.Fatal(err)
   424  	}
   425  	var r int
   426  	if len(secStr) == 1 {
   427  		r = l
   428  	} else {
   429  		r, err = strconv.Atoi(secStr[1])
   430  		if err != nil {
   431  			log.Fatal(err)
   432  		}
   433  	}
   434  
   435  	subval := uint32(val << r)
   436  	submsk := ^uint32(0) << (31 - l) >> (31 - l + r) << r
   437  	return subval, submsk
   438  }
   439  
   440  // isAtomic reports whether the instruction is atomic.
   441  func isAtomic(op string) bool {
   442  	return strings.HasPrefix(op, "AMO") || strings.HasPrefix(op, "LR_") || strings.HasPrefix(op, "SC_")
   443  }
   444  
   445  // isFloatReg reports whether the register of a floating point instruction is a floating point register.
   446  func isFloatReg(op string, reg string) bool {
   447  	switch {
   448  	case strings.Contains(op, "FADD") || strings.Contains(op, "FSUB") ||
   449  		strings.Contains(op, "FDIV") || strings.Contains(op, "FMUL") ||
   450  		strings.Contains(op, "FMIN") || strings.Contains(op, "FMAX") ||
   451  		strings.Contains(op, "FMADD") || strings.Contains(op, "FMSUB") ||
   452  		strings.Contains(op, "FCVT_D_S") || strings.Contains(op, "FCVT_S_D") ||
   453  		strings.Contains(op, "FCVT_D_Q") || strings.Contains(op, "FCVT_Q_D") ||
   454  		strings.Contains(op, "FCVT_S_Q") || strings.Contains(op, "FCVT_Q_S") ||
   455  		strings.Contains(op, "FCVT_H_S") || strings.Contains(op, "FCVT_S_H") ||
   456  		strings.Contains(op, "FNM") || strings.Contains(op, "FNEG") ||
   457  		strings.Contains(op, "FSQRT") || strings.Contains(op, "FSGNJ"):
   458  		return true
   459  
   460  	case strings.Contains(op, "FCLASS") || strings.Contains(op, "FCVT_L") ||
   461  		strings.Contains(op, "FCVT_W") || strings.Contains(op, "FEQ") ||
   462  		strings.Contains(op, "FLE") || strings.Contains(op, "FLT") ||
   463  		strings.Contains(op, "FMV_X_H") || strings.Contains(op, "FMV_X_D") ||
   464  		strings.Contains(op, "FMV_X_W"):
   465  		return reg != "rd"
   466  
   467  	case strings.Contains(op, "FCVT_D") || strings.Contains(op, "FCVT_S") ||
   468  		strings.Contains(op, "FCVT_H") || strings.Contains(op, "FCVT_Q") ||
   469  		strings.Contains(op, "FMV_H_X") || strings.Contains(op, "FMV_D_X") ||
   470  		strings.Contains(op, "FMV_W_X"):
   471  		return reg != "rs"
   472  
   473  	default:
   474  		return false
   475  	}
   476  }