#[macro_use]
extern crate pest_derive;
use anyhow::{anyhow, Error};
use pest::Parser;
#[allow(dead_code, clippy::type_complexity)]
mod opcodes;
mod asm_parser {
#[derive(Parser)]
#[grammar = "asm.pest"]
pub(crate) struct AsmParser;
}
use asm_parser::{AsmParser, Rule};
const V0_TAIL_INSTS: [&str; 14] = [
"vfmerge.vfm",
"vadc.vxm",
"vmadc.vxm",
"vsbc.vxm",
"vmsbc.vxm",
"vmerge.vxm",
"vadc.vvm",
"vmadc.vvm",
"vsbc.vvm",
"vmsbc.vvm",
"vmerge.vvm",
"vadc.vim",
"vmadc.vim",
"vmerge.vim",
];
const VV_TERNARY_INSTS: [&str; 19] = [
"vmacc.vv",
"vnmsac.vv",
"vmadd.vv",
"vnmsub.vv",
"vwmaccu.vv",
"vwmacc.vv",
"vwmaccsu.vv",
"vfmacc.vv",
"vfnmacc.vv",
"vfmsac.vv",
"vfnmsac.vv",
"vfmadd.vv",
"vfnmadd.vv",
"vfmsub.vv",
"vfnmsub.vv",
"vfwmacc.vv",
"vfwnmacc.vv",
"vfwmsac.vv",
"vfwnmsac.vv",
];
const VX_VF_TERNARY_INSTS: [&str; 20] = [
"vmacc.vx",
"vnmsac.vx",
"vmadd.vx",
"vnmsub.vx",
"vwmaccu.vx",
"vwmacc.vx",
"vwmaccsu.vx",
"vwmaccus.vx",
"vfmacc.vf",
"vfnmacc.vf",
"vfmsac.vf",
"vfnmsac.vf",
"vfmadd.vf",
"vfnmadd.vf",
"vfmsub.vf",
"vfnmsub.vf",
"vfwmacc.vf",
"vfwnmacc.vf",
"vfwmsac.vf",
"vfwnmsac.vf",
];
pub fn encode(inst: &str, reserved_only: bool) -> Result<Option<u32>, Error> {
let pairs = if let Ok(result) = AsmParser::parse(Rule::inst, inst.trim()) {
result
} else {
let _ = AsmParser::parse(Rule::label, inst.trim())
.map_err(|err| anyhow!("parse asm error: {}", err))?;
return Ok(None);
};
let mut name = "unknown";
let mut args = Vec::new();
for pair in pairs.clone() {
for inner1_pair in pair.into_inner() {
match inner1_pair.as_rule() {
Rule::inst_name => {
name = inner1_pair.as_str();
}
Rule::inst_arg => {
for inner2_pair in inner1_pair.into_inner() {
match inner2_pair.as_rule() {
Rule::inst_arg_mask | Rule::inst_arg_simple | Rule::integer => {
args.push(inner2_pair.as_str());
}
_ => {
return Ok(None);
}
}
}
}
_ => {
return Ok(None);
}
}
}
}
if reserved_only {
let mut is_reserved = false;
'outer: for width in [128, 256, 512, 1024] {
for prefix in ["i", "e"] {
let part = format!("{}{}", prefix, width);
if name.contains(part.as_str()) {
is_reserved = true;
break 'outer;
}
for arg in &args {
if arg.contains(part.as_str()) {
is_reserved = true;
break 'outer;
}
}
}
}
if !is_reserved {
return Ok(None);
}
}
opcodes::INSTRUCTIONS
.iter()
.find(|(inst_name, _, _)| *inst_name == name)
.map(|(_, base, args_cfg)| gen_inst_code(name, &args, *base, args_cfg))
.transpose()
}
fn gen_inst_code(
name: &str,
args: &[&str],
mut base: u32,
arg_cfg: &[(&str, usize)],
) -> Result<u32, Error> {
let simm5_idx = arg_cfg.iter().position(|(name, _)| *name == "simm5");
let vs1_idx = arg_cfg.iter().position(|(name, _)| *name == "vs1");
let rs1_idx = arg_cfg.iter().position(|(name, _)| *name == "rs1");
let vs2_idx = arg_cfg.iter().position(|(name, _)| *name == "vs2");
let last_7bits = base & 0b1111111;
let mut arg_cfg_vec = arg_cfg.iter().collect::<Vec<_>>();
if let (Some(simm5_idx), Some(vs2_idx), 0x57) = (simm5_idx, vs2_idx, last_7bits) {
arg_cfg_vec.swap(simm5_idx, vs2_idx);
}
if let (Some(vs1_idx), Some(vs2_idx), 0x57) = (vs1_idx, vs2_idx, last_7bits) {
if !VV_TERNARY_INSTS.contains(&name) {
arg_cfg_vec.swap(vs1_idx, vs2_idx);
}
}
if let (Some(rs1_idx), Some(vs2_idx), 0x57) = (rs1_idx, vs2_idx, last_7bits) {
if !VX_VF_TERNARY_INSTS.contains(&name) {
arg_cfg_vec.swap(rs1_idx, vs2_idx);
}
}
let arg_cfg_final = &arg_cfg_vec;
let has_vm = arg_cfg_final.iter().any(|(arg_name, _)| *arg_name == "vm");
let has_nf = arg_cfg_final.iter().any(|(arg_name, _)| *arg_name == "nf");
let number = if has_nf && has_vm {
arg_cfg_final.len() - 2
} else if has_vm {
arg_cfg_final.len() - 1
} else {
arg_cfg_final.len()
};
check_args(name, args, number, has_vm)?;
for (idx, (arg_name, arg_pos)) in arg_cfg_final.iter().rev().enumerate() {
let value = match *arg_name {
"rs1" | "rs2" | "rd" => map_x_reg(args[idx], arg_name)?,
"vs1" | "vs2" | "vs3" | "vd" => map_v_reg(args[idx], arg_name)?,
"simm5" => {
let arg_current = args[idx].to_lowercase();
let value = if arg_current.starts_with('-') {
let value = if let Some(content) = arg_current.strip_prefix("0x") {
i8::from_str_radix(content, 16)
.map_err(|_| anyhow!("Parse simm5 value failed: {}", arg_current))?
} else {
arg_current
.parse::<i8>()
.map_err(|_| anyhow!("Parse simm5 value failed: {}", arg_current))?
};
if value < -16 || value > 15 {
return Err(anyhow!(
"Simm5 value out of range: {} expected: [-16, 15]",
value
));
}
value as u8
} else {
let value = if let Some(content) = arg_current.strip_prefix("0x") {
u8::from_str_radix(content, 16)
.map_err(|_| anyhow!("Parse uimm5 value failed: {}", arg_current))?
} else {
arg_current
.parse::<u8>()
.map_err(|_| anyhow!("Parse uimm5 value failed: {}", arg_current))?
};
if value > 31 {
return Err(anyhow!(
"Uimm5 value out of range: {} expected: [0, 31]",
value
));
}
value
};
(value & 0b00011111) as u32
}
"zimm" => {
let value = args[idx]
.parse::<u8>()
.map_err(|_| anyhow!("Parse zimm5 value failed: {}", args[idx]))?;
if value > 31 {
return Err(anyhow!(
"zimm5 value out of range: {} expected: [0, 31]",
value
));
}
(value as u8 & 0b00011111) as u32
}
"zimm10" | "zimm11" => {
let mut vsew: u8 = 0;
let mut lmul = Vlmul::M1;
let mut ta = false;
let mut ma = false;
let mut tu = false;
let mut mu = false;
for arg_str in &args[idx..] {
if *arg_str == "ta" {
ta = true;
} else if *arg_str == "ma" {
ma = true;
} else if *arg_str == "tu" {
tu = true;
} else if *arg_str == "mu" {
mu = true;
} else if arg_str.as_bytes()[0] == b'e' {
let sew = arg_str[1..]
.parse::<u16>()
.map_err(|_| anyhow!("Invalid SEW value format: {}", arg_str))?;
vsew = match sew {
8 => 0,
16 => 1,
32 => 2,
64 => 3,
128 => 4,
256 => 5,
512 => 6,
1024 => 7,
_ => {
return Err(anyhow!(
"Invalid SEW value for vtypei: {}, arg: {}",
sew,
arg_str
))
}
};
} else if arg_str.as_bytes()[0] == b'm' {
lmul = Vlmul::from_str(arg_str)
.ok_or_else(|| anyhow!("Invalid LMUL value format: {}", arg_str))?;
} else {
return Err(anyhow!(
"Invalid argument for {}, expected: e{{n}}/m{{n}}/ta/ma/tu/mu, got: {}",
name,
arg_str
));
}
}
if ta && tu {
return Err(anyhow!("ta/tu can not both exists"));
}
if ma && mu {
return Err(anyhow!("ma/mu can not both exists"));
}
let mut value = lmul as u8;
value |= vsew << 3;
if ta {
value |= 1 << 6;
}
if ma {
value |= 1 << 7;
}
value as u32
}
"vm" => {
if args.get(idx) == Some(&"v0.t") {
0
} else {
1
}
}
"nf" => 0,
"wd" => return Err(anyhow!("Zvamo instructions are not supported.")),
_ => unreachable!(),
};
base |= value << arg_pos;
}
Ok(base)
}
#[repr(u8)]
enum Vlmul {
Mf8 = 0b101,
Mf4 = 0b110,
Mf2 = 0b111,
M1 = 0b000,
M2 = 0b001,
M4 = 0b010,
M8 = 0b011,
}
impl Vlmul {
fn from_str(name: &str) -> Option<Vlmul> {
match name {
"mf8" => Some(Vlmul::Mf8),
"mf4" => Some(Vlmul::Mf4),
"mf2" => Some(Vlmul::Mf2),
"m1" => Some(Vlmul::M1),
"m2" => Some(Vlmul::M2),
"m4" => Some(Vlmul::M4),
"m8" => Some(Vlmul::M8),
_ => None,
}
}
}
fn check_args(name: &str, args: &[&str], number: usize, vm: bool) -> Result<(), Error> {
if V0_TAIL_INSTS.contains(&name) {
if args.len() != number + 1 {
return Err(anyhow!(
"Invalid number of arguments for {}, expected: {}, got: {}",
name,
number + 1,
args.len()
));
}
if args[args.len() - 1] != "v0" {
return Err(anyhow!("The last argument of {} must be v0", name));
}
return Ok(());
}
let (expected, min, max) = if name == "vsetvli" || name == "vsetivli" {
("3 <= n <=6".to_string(), 3, 6)
} else if !vm {
(number.to_string(), number, number)
} else {
(format!("{} or {}", number, number + 1), number, number + 1)
};
if args.len() < min || args.len() > max {
Err(anyhow!(
"Invalid number of arguments for {}, expected: {}, got: {}",
name,
expected,
args.len()
))
} else {
Ok(())
}
}
fn map_v_reg(name: &str, label: &str) -> Result<u32, Error> {
if name.as_bytes()[0] != b'v' {
return Err(anyhow!("Invalid {} V register: {}", label, name));
}
let number = name[1..]
.parse::<u32>()
.map_err(|_| anyhow!("Invalid {} V register: {}", label, name))?;
if number > 31 {
return Err(anyhow!("Invalid {} V register: {}", label, name));
}
Ok(number)
}
fn map_x_reg(name: &str, label: &str) -> Result<u32, Error> {
match name {
"x0" | "zero" => Ok(0),
"x1" | "ra" => Ok(1),
"x2" | "sp" => Ok(2),
"x3" | "gp" => Ok(3),
"x4" | "tp" => Ok(4),
"x5" | "t0" => Ok(5),
"x6" | "t1" => Ok(6),
"x7" | "t2" => Ok(7),
"x8" | "s0" | "fp" => Ok(8),
"x9" | "s1" => Ok(9),
"x10" | "a0" => Ok(10),
"x11" | "a1" => Ok(11),
"x12" | "a2" => Ok(12),
"x13" | "a3" => Ok(13),
"x14" | "a4" => Ok(14),
"x15" | "a5" => Ok(15),
"x16" | "a6" => Ok(16),
"x17" | "a7" => Ok(17),
"x18" | "s2" => Ok(18),
"x19" | "s3" => Ok(19),
"x20" | "s4" => Ok(20),
"x21" | "s5" => Ok(21),
"x22" | "s6" => Ok(22),
"x23" | "s7" => Ok(23),
"x24" | "s8" => Ok(24),
"x25" | "s9" => Ok(25),
"x26" | "s10" => Ok(26),
"x27" | "s11" => Ok(27),
"x28" | "t3" => Ok(28),
"x29" | "t4" => Ok(29),
"x30" | "t5" => Ok(30),
"x31" | "t6" => Ok(31),
_ => Err(anyhow!("Invalid {} X register: {}", label, name)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_riscvgc() {
assert_eq!(encode("add {abc_t}, {abc}, 3", false).unwrap(), None);
assert_eq!(encode("add a1, a1, a2", false).unwrap(), None);
assert_eq!(encode("jr t0", false).unwrap(), None);
assert_eq!(encode("fence", false).unwrap(), None);
}
fn assert_inst(code: u32, inst: &str) {
let output_code = encode(inst, false).unwrap().unwrap();
assert_eq!(output_code, code, "0b{:032b} - {}", output_code, inst);
}
#[test]
fn test_vset() {
for (code, inst) in [
(0b10000001111110011111001011010111, "vsetvl x5, s3, t6"),
(0b00000000000010011111001011010111, "vsetvli x5, s3, e8"),
(
0b00000010101010011111001011010111,
"vsetvli x5, s3, e256, m4",
),
(
0b00001110101010011111001011010111,
"vsetvli x5, s3, e256, m4, ta, ma",
),
(
0b11000010101010011111001011010111,
"vsetivli x5, 19, e256, m4",
),
(
0b11000010101010011111001011010111,
"vsetivli x5, 19, e256, m4, tu, mu",
),
(
0b11001010101010011111001011010111,
"vsetivli x5, 19, e256, m4, tu, ma",
),
(
0b11000110101010011111001011010111,
"vsetivli x5, 19, e256, m4, ta, mu",
),
(
0b11001110101010011111001011010111,
"vsetivli x5, 19, e256, m4, ta, ma",
),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_simm5_range() {
for (code, inst) in [
(0b00000010001010000011000011010111, "vadd.vi v1, v2, -16"),
(0b00000010001001111011000011010111, "vadd.vi v1, v2, 15"),
] {
assert_inst(code, inst);
}
assert!(encode("vadd.vi v1, v2, -17", false).is_err());
}
#[test]
fn test_rs1_vd_0x07() {
for (code, inst) in [
(0b00000010101100101000000010000111, "vlm.v v1, (t0)"),
(0b00000010000000101000000010000111, "vle8.v v1, (t0)"),
(0b00000011000000101000000010000111, "vle8ff.v v1, (t0)"),
(0b00000010100000101000000010000111, "vl1re8.v v1, (t0)"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_rs1_vs3_0x27() {
for (code, inst) in [
(0b00000010101100101000000010100111, "vsm.v v1, (t0)"),
(0b00000010000000101000000010100111, "vse8.v v1, (t0)"),
(0b00000010100000101000000010100111, "vs1r.v v1, (t0)"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_vs2_rs1_vd_0x07() {
for (code, inst) in [
(0b00000110001000101000000010000111, "vluxei8.v v1, (t0), v2"),
(0b00001110001000101000000010000111, "vloxei8.v v1, (t0), v2"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_vs2_rs1_vs3_0x27() {
for (code, inst) in [
(0b00000110001000101000000010100111, "vsuxei8.v v1, (t0), v2"),
(0b00001110001000101000000010100111, "vsoxei8.v v1, (t0), v2"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_rs2_rs1_vd_0x07() {
assert_inst(0b00001010011000101000000010000111, "vlse8.v v1, (t0), t1");
}
#[test]
fn test_rs2_rs1_vs3_0x27() {
assert_inst(0b00001010011000101000000010100111, "vsse8.v v1, (t0), t1");
}
#[test]
fn test_vs2_rs1_vd_0x57() {
for (code, inst) in [
(0b00000010001000101101000011010111, "vfadd.vf v1, v2, t0"),
(
0b01011100001000101101000011010111,
"vfmerge.vfm v1, v2, t0, v0",
),
(0b10110110000101100110000111010111, "vmacc.vx v3, a2, v1"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_rs1_vd_0x57() {
for (code, inst) in [
(0b01000010000000101101000011010111, "vfmv.s.f v1, t0"),
(0b01011110000000101100000011010111, "vmv.v.x v1, t0"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_vs2_vs1_vd_0x57() {
for (code, inst) in [
(0b00000010001000011001000011010111, "vfadd.vv v1, v2, v3"),
(
0b01000000001000011000000011010111,
"vadc.vvm v1, v2, v3, v0",
),
(0b10110110001000001010000111010111, "vmacc.vv v3, v1, v2"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_vs2_rd_0x57() {
for (code, inst) in [
(0b01000010001000000001001011010111, "vfmv.f.s t0, v2"),
(0b01000010001010000010001011010111, "vcpop.m t0, v2"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_vs2_vd_0x57() {
for (code, inst) in [
(0b01001010001000000001000011010111, "vfcvt.xu.f.v v1, v2"),
(0b10011110001000000011000011010111, "vmv1r.v v1, v2"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_vs1_vd_0x57() {
assert_inst(0b01011110000000010000000011010111, "vmv.v.v v1, v2");
}
#[test]
fn test_vs2_simm5_vd_0x57() {
for (code, inst) in [
(0b00000010001011011011000011010111, "vadd.vi v1, v2, -5"),
(0b01000000001000101011000011010111, "vadc.vim v1, v2, 5, v0"),
] {
assert_inst(code, inst);
}
}
#[test]
fn test_simm5_vd_0x57() {
assert_inst(0b01011110000000101011000011010111, "vmv.v.i v1, 0x5");
}
#[test]
fn test_vd_0x57() {
assert_inst(0b01010010000010001010000011010111, "vid.v v1");
}
#[test]
fn test_vmandn_vmorn() {
assert_inst(0b01100010001000011010000011010111, "vmandn.mm v1, v2, v3");
assert_inst(0b01110010001000011010000011010111, "vmorn.mm v1, v2, v3");
}
}