Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion pkg/compiler/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ func genTextType(t *ast.Type) prog.TextKind {
case "ppc64":
return prog.TextPpc64
case "riscv64":
return prog.TextTarget
return prog.TextRiscv64
default:
panic(fmt.Sprintf("unknown text type %q", t.Ident))
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/ifuzz/ifuzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
_ "github.com/google/syzkaller/pkg/ifuzz/arm64/generated" // pull in generated instruction descriptions
"github.com/google/syzkaller/pkg/ifuzz/iset"
_ "github.com/google/syzkaller/pkg/ifuzz/powerpc/generated" // pull in generated instruction descriptions
_ "github.com/google/syzkaller/pkg/ifuzz/riscv64/generated" // pull in generated instruction descriptions
_ "github.com/google/syzkaller/pkg/ifuzz/x86/generated" // pull in generated instruction descriptions
)

Expand All @@ -22,6 +23,7 @@ const (
ArchX86 = iset.ArchX86
ArchPowerPC = iset.ArchPowerPC
ArchArm64 = iset.ArchArm64
ArchRiscv64 = iset.ArchRiscv64
ModeLong64 = iset.ModeLong64
ModeProt32 = iset.ModeProt32
ModeProt16 = iset.ModeProt16
Expand Down
6 changes: 5 additions & 1 deletion pkg/ifuzz/ifuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/google/syzkaller/pkg/testutil"
)

var allArches = []string{ArchX86, ArchPowerPC, ArchArm64}
var allArches = []string{ArchX86, ArchPowerPC, ArchArm64, ArchRiscv64}

func TestMode(t *testing.T) {
for _, arch := range allArches {
Expand Down Expand Up @@ -139,6 +139,10 @@ func testGenerate(t *testing.T, arch string) {
Exec: true,
Len: repeat,
}
if arch == ArchRiscv64 {
cfg.Priv = false
cfg.Exec = false
}
text := Generate(cfg, r)
for len(text) != 0 {
size, err := insnset.Decode(mode, text)
Expand Down
1 change: 1 addition & 0 deletions pkg/ifuzz/iset/iset.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
ArchX86 = "x86"
ArchPowerPC = "powerpc"
ArchArm64 = "arm64"
ArchRiscv64 = "riscv64"
)

var Arches = make(map[string]InsnSet)
Expand Down
199 changes: 199 additions & 0 deletions pkg/ifuzz/riscv64/gen/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// gen generates riscv64 instruction tables from riscv-unified-db YAML.
// https://github.com/riscv-software-src/riscv-unified-db/tree/main/spec/std/isa/inst
package main

import (
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"

"github.com/google/syzkaller/pkg/ifuzz/riscv64"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/serializer"
"github.com/google/syzkaller/pkg/tool"
)

type instYAML struct {
Kind string `yaml:"kind"`
Name string `yaml:"name"`
Encoding struct {
Match string `yaml:"match"`
Variables []struct {
Name string `yaml:"name"`
Location string `yaml:"location"` // e.g. "24-20"
} `yaml:"variables"`
} `yaml:"encoding"`
Access struct {
U string `yaml:"u"`
VU string `yaml:"vu"`
} `yaml:"access"`
}

func main() {
if len(os.Args) != 3 {
tool.Failf("usage: go run gen.go <riscv-unified-db/spec/std/isa/inst> <output.go>")
}
root := os.Args[1]
outFile := os.Args[2]

insns := []*riscv64.Insn{}

err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".yaml") {
return nil
}

data, err := os.ReadFile(path)
if err != nil {
return nil
}

var inst instYAML
if err := yaml.Unmarshal(data, &inst); err != nil {
return nil
}
if inst.Kind != "instruction" {
return nil
}

match := inst.Encoding.Match
if len(match) != 32 {
return nil
}

insn, ok := buildInsn(inst)
if ok {
insns = append(insns, insn)
}
return nil
})
if err != nil {
tool.Fail(err)
}

var out bytes.Buffer
fmt.Fprintf(&out, `// Code generated by pkg/ifuzz/riscv64/gen. DO NOT EDIT.

// go:build !codeanalysis

package generated

import (
. "github.com/google/syzkaller/pkg/ifuzz/riscv64"
)

func init() {
Register(insns_riscv64)
}

var insns_riscv64 =
`)
serializer.Write(&out, insns)

if err := osutil.WriteFileAtomically(outFile, out.Bytes()); err != nil {
tool.Fail(err)
}

fmt.Fprintf(os.Stderr, "generated %d instructions\n", len(insns))
}

func buildInsn(inst instYAML) (*riscv64.Insn, bool) {
match := inst.Encoding.Match

var opcode uint32
var mask uint32

for i, ch := range match {
bit := uint(31 - i)
switch ch {
case '0':
mask |= 1 << bit
case '1':
mask |= 1 << bit
opcode |= 1 << bit
case '-':
default:
return nil, false
}
}

fields := []riscv64.InsnField{}
for _, v := range inst.Encoding.Variables {
subFields, ok := parseLocations(v.Name, v.Location)
if !ok {
return nil, false
}
fields = append(fields, subFields...)
}

priv := inst.Access.U == "never" || inst.Access.VU == "never"

return &riscv64.Insn{
Name: inst.Name,
OpcodeMask: mask,
Opcode: opcode,
Fields: fields,
AsUInt32: opcode,
Generator: nil,
Priv: priv,
}, true
}

func parseLocations(name, loc string) ([]riscv64.InsnField, bool) {
// Support multiple ranges separated by '|', e.g.:
// "31-25|11-7"
ranges := strings.Split(loc, "|")
fields := make([]riscv64.InsnField, 0, len(ranges))

for _, r := range ranges {
start, length, hi, lo, ok := parseRange(r)
if !ok {
return nil, false
}

fieldName := name
if len(ranges) > 1 {
fieldName = fmt.Sprintf("%s_%d_%d", name, hi, lo)
}

fields = append(fields, riscv64.InsnField{
Name: fieldName,
Start: start,
Length: length,
})
}

return fields, true
}

func parseRange(r string) (start, length, hi, lo uint, ok bool) {
parts := strings.Split(r, "-")
if len(parts) != 2 {
return 0, 0, 0, 0, false
}

if _, err := fmt.Sscanf(parts[0], "%d", &hi); err != nil {
return 0, 0, 0, 0, false
}
if _, err := fmt.Sscanf(parts[1], "%d", &lo); err != nil {
return 0, 0, 0, 0, false
}
if hi < lo {
return 0, 0, 0, 0, false
}

start = hi
length = hi - lo + 1
return start, length, hi, lo, true
}
69 changes: 69 additions & 0 deletions pkg/ifuzz/riscv64/gen/inst/B/andn.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
# SPDX-License-Identifier: BSD-3-Clause-Clear

# yaml-language-server: $schema=../../../../schemas/inst_schema.json

$schema: "inst_schema.json#"
kind: instruction
name: andn
long_name: AND with inverted operand
description: |
Performs the bitwise logical AND operation between `xs1` and the
bitwise inversion of `xs2`.
definedBy:
extension:
anyOf:
- name: Zbb
- name: Zbkb
assembly: xd, xs1, xs2
format:
$inherits:
- inst_subtype/R/R-x.yaml#/data
opcodes:
funct7:
display_name: ANDN
value: 0b0100000
funct3:
display_name: ANDN
value: 0b111
opcode: { $inherits: inst_opcode/OP.yaml#/data }
access:
s: always
u: always
vs: always
vu: always
data_independent_timing: true
operation(): |
if (implemented?(ExtensionName::B) && (CSR[misa].B == 1'b0)) {
raise (ExceptionCode::IllegalInstruction, mode(), $encoding);
}

X[xd] = X[xs2] & ~X[xs1];

# SPDX-SnippetBegin
# SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model <https://github.com/riscv/sail-riscv/blob/master/LICENCE>
# SPDX-License-Identifier: BSD-2-Clause
sail(): |
{
let rs1_val = X(rs1);
let rs2_val = X(rs2);
let result : xlenbits = match op {
RISCV_ANDN => rs1_val & ~(rs2_val),
RISCV_ORN => rs1_val | ~(rs2_val),
RISCV_XNOR => ~(rs1_val ^ rs2_val),
RISCV_MAX => to_bits(sizeof(xlen), max(signed(rs1_val), signed(rs2_val))),
RISCV_MAXU => to_bits(sizeof(xlen), max(unsigned(rs1_val), unsigned(rs2_val))),
RISCV_MIN => to_bits(sizeof(xlen), min(signed(rs1_val), signed(rs2_val))),
RISCV_MINU => to_bits(sizeof(xlen), min(unsigned(rs1_val), unsigned(rs2_val))),
RISCV_ROL => if sizeof(xlen) == 32
then rs1_val <<< rs2_val[4..0]
else rs1_val <<< rs2_val[5..0],
RISCV_ROR => if sizeof(xlen) == 32
then rs1_val >>> rs2_val[4..0]
else rs1_val >>> rs2_val[5..0]
};
X(rd) = result;
RETIRE_SUCCESS
}

# SPDX-SnippetEnd
66 changes: 66 additions & 0 deletions pkg/ifuzz/riscv64/gen/inst/B/clmul.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
# SPDX-License-Identifier: BSD-3-Clause-Clear

# yaml-language-server: $schema=../../../../schemas/inst_schema.json

$schema: "inst_schema.json#"
kind: instruction
name: clmul
long_name: Carry-less multiply (low-part)
description: |
`clmul` produces the lower half of the 2*XLEN carry-less product
definedBy:
extension:
anyOf:
- name: Zbc
- name: Zbkc
assembly: xd, xs1, xs2
format:
$inherits:
- inst_subtype/R/R-x.yaml#/data
opcodes:
funct7:
display_name: CLMUL
value: 0b0000101
funct3:
display_name: CLMUL
value: 0b001
opcode: { $inherits: inst_opcode/OP.yaml#/data }
access:
s: always
u: always
vs: always
vu: always
data_independent_timing: true
operation(): |
if (implemented?(ExtensionName::B) && (CSR[misa].B == 1'b0)) {
raise (ExceptionCode::IllegalInstruction, mode(), $encoding);
}

XReg xs1_val = X[xs1];
XReg xs2_val = X[xs2];
XReg output = 0;

for (U32 i=0; i < xlen(); i++) {
output = (((xs2_val >> i) & 1) == 1)
? output ^ (xs1_val << i)
: output;
}

X[xd] = output;

# SPDX-SnippetBegin
# SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model <https://github.com/riscv/sail-riscv/blob/master/LICENCE>
# SPDX-License-Identifier: BSD-2-Clause
sail(): |
{
let rs1_val = X(rs1);
let rs2_val = X(rs2);
result : xlenbits = zeros();
foreach (i from 0 to (xlen_val - 1))
if rs2_val[i] == bitone then result = result ^ (rs1_val << i);
X(rd) = result;
RETIRE_SUCCESS
}

# SPDX-SnippetEnd
Loading
Loading