1+ // Copyright 2026
2+ // SPDX-License-Identifier: Apache-2.0
3+
4+ // gen generates riscv64 instruction tables from riscv-unified-db YAML.
5+ // https://github.com/riscv-software-src/riscv-unified-db/tree/main/spec/std/isa/inst, commit:a4fa3844350bb54dee9a6137c6eba77d0e3bf9c1
6+ package main
7+
8+ import (
9+ "bytes"
10+ "fmt"
11+ "io/fs"
12+ "os"
13+ "path/filepath"
14+ "strings"
15+
16+ "gopkg.in/yaml.v3"
17+
18+ "github.com/google/syzkaller/pkg/ifuzz/riscv64"
19+ "github.com/google/syzkaller/pkg/osutil"
20+ "github.com/google/syzkaller/pkg/serializer"
21+ "github.com/google/syzkaller/pkg/tool"
22+ )
23+
24+ type instYAML struct {
25+ Kind string `yaml:"kind"`
26+ Name string `yaml:"name"`
27+ Encoding struct {
28+ Match string `yaml:"match"`
29+ Variables []struct {
30+ Name string `yaml:"name"`
31+ Location string `yaml:"location"` // e.g. "24-20"
32+ } `yaml:"variables"`
33+ } `yaml:"encoding"`
34+ }
35+
36+ func main () {
37+ if len (os .Args ) != 3 {
38+ tool .Failf ("usage: go run gen.go <riscv-unified-db/spec/std/isa/inst> <output.go>" )
39+ }
40+ root := os .Args [1 ]
41+ outFile := os .Args [2 ]
42+
43+ insns := []* riscv64.Insn {}
44+
45+ err := filepath .WalkDir (root , func (path string , d fs.DirEntry , err error ) error {
46+ if err != nil {
47+ return err
48+ }
49+ if d .IsDir () || ! strings .HasSuffix (path , ".yaml" ) {
50+ return nil
51+ }
52+
53+ data , err := os .ReadFile (path )
54+ if err != nil {
55+ return nil
56+ }
57+
58+ var inst instYAML
59+ if err := yaml .Unmarshal (data , & inst ); err != nil {
60+ return nil
61+ }
62+ if inst .Kind != "instruction" {
63+ return nil
64+ }
65+
66+ match := inst .Encoding .Match
67+ if len (match ) != 32 {
68+ // Skip compressed / variable-length / weird encodings
69+ return nil
70+ }
71+
72+ insn , ok := buildInsn (inst )
73+ if ok {
74+ insns = append (insns , insn )
75+ }
76+ return nil
77+ })
78+ if err != nil {
79+ tool .Fail (err )
80+ }
81+
82+ var out bytes.Buffer
83+ fmt .Fprintf (& out , `// Code generated by pkg/ifuzz/riscv64/gen. DO NOT EDIT.
84+
85+ // go:build !codeanalysis
86+
87+ package generated
88+
89+ import (
90+ . "github.com/google/syzkaller/pkg/ifuzz/riscv64"
91+ )
92+
93+ func init() {
94+ Register(insns_riscv64)
95+ }
96+
97+ var insns_riscv64 =
98+ ` )
99+ serializer .Write (& out , insns )
100+
101+ if err := osutil .WriteFileAtomically (outFile , out .Bytes ()); err != nil {
102+ tool .Fail (err )
103+ }
104+
105+ fmt .Fprintf (os .Stderr , "generated %d instructions\n " , len (insns ))
106+ }
107+
108+ func buildInsn (inst instYAML ) (* riscv64.Insn , bool ) {
109+ match := inst .Encoding .Match
110+
111+ var opcode uint32
112+ var mask uint32
113+
114+ for i , ch := range match {
115+ bit := uint (31 - i )
116+ switch ch {
117+ case '0' :
118+ mask |= 1 << bit
119+ case '1' :
120+ mask |= 1 << bit
121+ opcode |= 1 << bit
122+ case '-' :
123+ // variable bit
124+ default :
125+ return nil , false
126+ }
127+ }
128+
129+ fields := []riscv64.InsnField {}
130+ for _ , v := range inst .Encoding .Variables {
131+ subFields , ok := parseLocations (v .Name , v .Location )
132+ if ! ok {
133+ return nil , false
134+ }
135+ fields = append (fields , subFields ... )
136+ }
137+
138+ return & riscv64.Insn {
139+ Name : inst .Name ,
140+ OpcodeMask : mask ,
141+ Opcode : opcode ,
142+ Fields : fields ,
143+ AsUInt32 : opcode ,
144+ Generator : nil ,
145+ }, true
146+ }
147+
148+ func parseLocations (name , loc string ) ([]riscv64.InsnField , bool ) {
149+ // Support multiple ranges separated by '|', e.g.:
150+ // "31-25|11-7"
151+ ranges := strings .Split (loc , "|" )
152+ fields := make ([]riscv64.InsnField , 0 , len (ranges ))
153+
154+ for _ , r := range ranges {
155+ start , length , hi , lo , ok := parseRange (r )
156+ if ! ok {
157+ return nil , false
158+ }
159+
160+ fieldName := name
161+ if len (ranges ) > 1 {
162+ fieldName = fmt .Sprintf ("%s_%d_%d" , name , hi , lo )
163+ }
164+
165+ fields = append (fields , riscv64.InsnField {
166+ Name : fieldName ,
167+ Start : start ,
168+ Length : length ,
169+ })
170+ }
171+
172+ return fields , true
173+ }
174+
175+ func parseRange (r string ) (start uint , length uint , hi uint , lo uint , ok bool ) {
176+ parts := strings .Split (r , "-" )
177+ if len (parts ) != 2 {
178+ return 0 , 0 , 0 , 0 , false
179+ }
180+
181+ if _ , err := fmt .Sscanf (parts [0 ], "%d" , & hi ); err != nil {
182+ return 0 , 0 , 0 , 0 , false
183+ }
184+ if _ , err := fmt .Sscanf (parts [1 ], "%d" , & lo ); err != nil {
185+ return 0 , 0 , 0 , 0 , false
186+ }
187+ if hi < lo {
188+ return 0 , 0 , 0 , 0 , false
189+ }
190+
191+ start = lo
192+ length = hi - lo + 1
193+ return start , length , hi , lo , true
194+ }
0 commit comments