blob: e2d37b4e8962792eddb79115f6c4545393eae243 [file] [log] [blame]
// Copyright (c) 2021 The Vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use super::{write_file, SpirvGrammar};
use ahash::{HashMap, HashSet};
use heck::ToSnakeCase;
use once_cell::sync::Lazy;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
static SPEC_CONSTANT_OP: Lazy<HashSet<&'static str>> = Lazy::new(|| {
HashSet::from_iter([
"SConvert",
"FConvert",
"SNegate",
"Not",
"IAdd",
"ISub",
"IMul",
"UDiv",
"SDiv",
"UMod",
"SRem",
"SMod",
"ShiftRightLogical",
"ShiftRightArithmetic",
"ShiftLeftLogical",
"BitwiseOr",
"BitwiseXor",
"BitwiseAnd",
"VectorShuffle",
"CompositeExtract",
"CompositeInsert",
"LogicalOr",
"LogicalAnd",
"LogicalNot",
"LogicalEqual",
"LogicalNotEqual",
"Select",
"IEqual",
"INotEqual",
"ULessThan",
"SLessThan",
"UGreaterThan",
"SGreaterThan",
"ULessThanEqual",
"SLessThanEqual",
"UGreaterThanEqual",
"SGreaterThanEqual",
"QuantizeToF16",
"ConvertFToS",
"ConvertSToF",
"ConvertFToU",
"ConvertUToF",
"UConvert",
"ConvertPtrToU",
"ConvertUToPtr",
"GenericCastToPtr",
"PtrCastToGeneric",
"Bitcast",
"FNegate",
"FAdd",
"FSub",
"FMul",
"FDiv",
"FRem",
"FMod",
"AccessChain",
"InBoundsAccessChain",
"PtrAccessChain",
"InBoundsPtrAccessChain",
])
});
pub fn write(grammar: &SpirvGrammar) {
let mut instr_members = instruction_members(grammar);
let instr_output = instruction_output(&instr_members, false);
instr_members.retain(|member| SPEC_CONSTANT_OP.contains(member.name.to_string().as_str()));
instr_members.iter_mut().for_each(|member| {
if member.has_result_type_id {
member.operands.remove(0);
}
if member.has_result_id {
member.operands.remove(0);
}
});
let spec_constant_instr_output = instruction_output(&instr_members, true);
let bit_enum_output = bit_enum_output(&bit_enum_members(grammar));
let value_enum_output = value_enum_output(&value_enum_members(grammar));
write_file(
"spirv_parse.rs",
format!(
"SPIR-V grammar version {}.{}.{}",
grammar.major_version, grammar.minor_version, grammar.revision
),
quote! {
#instr_output
#spec_constant_instr_output
#bit_enum_output
#value_enum_output
},
);
}
#[derive(Clone, Debug)]
struct InstructionMember {
name: Ident,
has_result_id: bool,
has_result_type_id: bool,
opcode: u16,
operands: Vec<OperandMember>,
}
#[derive(Clone, Debug)]
struct OperandMember {
name: Ident,
ty: TokenStream,
parse: TokenStream,
}
fn instruction_output(members: &[InstructionMember], spec_constant: bool) -> TokenStream {
let struct_items = members
.iter()
.map(|InstructionMember { name, operands, .. }| {
if operands.is_empty() {
quote! { #name, }
} else {
let operands = operands.iter().map(|OperandMember { name, ty, .. }| {
quote! { #name: #ty, }
});
quote! {
#name {
#(#operands)*
},
}
}
});
let parse_items = members.iter().map(
|InstructionMember {
name,
opcode,
operands,
..
}| {
if operands.is_empty() {
quote! {
#opcode => Self::#name,
}
} else {
let operands_items =
operands.iter().map(|OperandMember { name, parse, .. }| {
quote! {
#name: #parse,
}
});
quote! {
#opcode => Self::#name {
#(#operands_items)*
},
}
}
},
);
let doc = if spec_constant {
"An instruction that is used as the operand of the `SpecConstantOp` instruction."
} else {
"A parsed SPIR-V instruction."
};
let enum_name = if spec_constant {
format_ident!("SpecConstantInstruction")
} else {
format_ident!("Instruction")
};
let result_fns = if spec_constant {
quote! {}
} else {
let result_id_items = members.iter().filter_map(
|InstructionMember {
name,
has_result_id,
..
}| {
if *has_result_id {
Some(quote! { Self::#name { result_id, .. } })
} else {
None
}
},
);
quote! {
/// Returns the `Id` that is assigned by this instruction, if any.
pub fn result_id(&self) -> Option<Id> {
match self {
#(#result_id_items)|* => Some(*result_id),
_ => None
}
}
}
};
let opcode_error = if spec_constant {
format_ident!("UnknownSpecConstantOpcode")
} else {
format_ident!("UnknownOpcode")
};
quote! {
#[derive(Clone, Debug, PartialEq, Eq)]
#[doc=#doc]
pub enum #enum_name {
#(#struct_items)*
}
impl #enum_name {
#[allow(dead_code)]
fn parse(reader: &mut InstructionReader<'_>) -> Result<Self, ParseError> {
let opcode = (reader.next_u32()? & 0xffff) as u16;
Ok(match opcode {
#(#parse_items)*
opcode => return Err(reader.map_err(ParseErrors::#opcode_error(opcode))),
})
}
#result_fns
}
}
}
fn instruction_members(grammar: &SpirvGrammar) -> Vec<InstructionMember> {
let operand_kinds = kinds_to_types(grammar);
grammar
.instructions
.iter()
.map(|instruction| {
let name = format_ident!("{}", instruction.opname.strip_prefix("Op").unwrap());
let mut has_result_id = false;
let mut has_result_type_id = false;
let mut operand_names = HashMap::default();
let mut operands = instruction
.operands
.iter()
.map(|operand| {
let name = if operand.kind == "IdResult" {
has_result_id = true;
format_ident!("result_id")
} else if operand.kind == "IdResultType" {
has_result_type_id = true;
format_ident!("result_type_id")
} else {
to_member_name(&operand.kind, operand.name.as_deref())
};
*operand_names.entry(name.clone()).or_insert(0) += 1;
let (ty, parse) = &operand_kinds[operand.kind.as_str()];
let ty = match operand.quantifier {
Some('?') => quote! { Option<#ty> },
Some('*') => quote! { Vec<#ty> },
_ => ty.clone(),
};
let parse = match operand.quantifier {
Some('?') => quote! {
if !reader.is_empty() {
Some(#parse)
} else {
None
}
},
Some('*') => quote! {{
let mut vec = Vec::new();
while !reader.is_empty() {
vec.push(#parse);
}
vec
}},
_ => parse.clone(),
};
OperandMember { name, ty, parse }
})
.collect::<Vec<_>>();
// Add number to operands with identical names
for name in operand_names
.into_iter()
.filter_map(|(n, c)| if c > 1 { Some(n) } else { None })
{
let mut num = 1;
for operand in operands.iter_mut().filter(|o| o.name == name) {
operand.name = format_ident!("{}{}", name, format!("{}", num));
num += 1;
}
}
InstructionMember {
name,
has_result_id,
has_result_type_id,
opcode: instruction.opcode,
operands,
}
})
.collect()
}
#[derive(Clone, Debug)]
struct KindEnumMember {
name: Ident,
value: u32,
parameters: Vec<OperandMember>,
}
fn bit_enum_output(enums: &[(Ident, Vec<KindEnumMember>)]) -> TokenStream {
let enum_items = enums.iter().map(|(name, members)| {
let members_items = members.iter().map(
|KindEnumMember {
name, parameters, ..
}| {
if parameters.is_empty() {
quote! {
pub #name: bool,
}
} else if let [OperandMember { ty, .. }] = parameters.as_slice() {
quote! {
pub #name: Option<#ty>,
}
} else {
let params = parameters.iter().map(|OperandMember { ty, .. }| {
quote! { #ty }
});
quote! {
pub #name: Option<(#(#params),*)>,
}
}
},
);
let parse_items = members.iter().map(
|KindEnumMember {
name,
value,
parameters,
..
}| {
if parameters.is_empty() {
quote! {
#name: value & #value != 0,
}
} else {
let some = if let [OperandMember { parse, .. }] = parameters.as_slice() {
quote! { #parse }
} else {
let parse = parameters.iter().map(|OperandMember { parse, .. }| parse);
quote! { (#(#parse),*) }
};
quote! {
#name: if value & #value != 0 {
Some(#some)
} else {
None
},
}
}
},
);
quote! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub struct #name {
#(#members_items)*
}
impl #name {
#[allow(dead_code)]
fn parse(reader: &mut InstructionReader<'_>) -> Result<#name, ParseError> {
let value = reader.next_u32()?;
Ok(Self {
#(#parse_items)*
})
}
}
}
});
quote! {
#(#enum_items)*
}
}
fn bit_enum_members(grammar: &SpirvGrammar) -> Vec<(Ident, Vec<KindEnumMember>)> {
let parameter_kinds = kinds_to_types(grammar);
grammar
.operand_kinds
.iter()
.filter(|operand_kind| operand_kind.category == "BitEnum")
.map(|operand_kind| {
let mut previous_value = None;
let members = operand_kind
.enumerants
.iter()
.filter_map(|enumerant| {
// Skip enumerants with the same value as the previous.
if previous_value == Some(&enumerant.value) {
return None;
}
previous_value = Some(&enumerant.value);
let value = enumerant
.value
.as_str()
.unwrap()
.strip_prefix("0x")
.unwrap();
let value = u32::from_str_radix(value, 16).unwrap();
if value == 0 {
return None;
}
let name = match enumerant.enumerant.to_snake_case().as_str() {
"const" => format_ident!("constant"),
"not_na_n" => format_ident!("not_nan"),
name => format_ident!("{}", name),
};
let parameters = enumerant
.parameters
.iter()
.map(|param| {
let name = to_member_name(&param.kind, param.name.as_deref());
let (ty, parse) = parameter_kinds[param.kind.as_str()].clone();
OperandMember { name, ty, parse }
})
.collect();
Some(KindEnumMember {
name,
value,
parameters,
})
})
.collect();
(format_ident!("{}", operand_kind.kind), members)
})
.collect()
}
fn value_enum_output(enums: &[(Ident, Vec<KindEnumMember>)]) -> TokenStream {
let enum_items = enums.iter().map(|(name, members)| {
let members_items = members.iter().map(
|KindEnumMember {
name, parameters, ..
}| {
if parameters.is_empty() {
quote! {
#name,
}
} else {
let params = parameters.iter().map(|OperandMember { name, ty, .. }| {
quote! { #name: #ty, }
});
quote! {
#name {
#(#params)*
},
}
}
},
);
let parse_items = members.iter().map(
|KindEnumMember {
name,
value,
parameters,
..
}| {
if parameters.is_empty() {
quote! {
#value => Self::#name,
}
} else {
let params_items =
parameters.iter().map(|OperandMember { name, parse, .. }| {
quote! {
#name: #parse,
}
});
quote! {
#value => Self::#name {
#(#params_items)*
},
}
}
},
);
let name_string = name.to_string();
let derives = match name_string.as_str() {
"ExecutionModel" => quote! { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] },
"Decoration" => quote! { #[derive(Clone, Debug, PartialEq, Eq)] },
_ => quote! { #[derive(Clone, Copy, Debug, PartialEq, Eq)] },
};
quote! {
#derives
#[allow(non_camel_case_types)]
pub enum #name {
#(#members_items)*
}
impl #name {
#[allow(dead_code)]
fn parse(reader: &mut InstructionReader<'_>) -> Result<#name, ParseError> {
Ok(match reader.next_u32()? {
#(#parse_items)*
value => return Err(reader.map_err(ParseErrors::UnknownEnumerant(#name_string, value))),
})
}
}
}
});
quote! {
#(#enum_items)*
}
}
fn value_enum_members(grammar: &SpirvGrammar) -> Vec<(Ident, Vec<KindEnumMember>)> {
let parameter_kinds = kinds_to_types(grammar);
grammar
.operand_kinds
.iter()
.filter(|operand_kind| operand_kind.category == "ValueEnum")
.map(|operand_kind| {
let mut previous_value = None;
let members = operand_kind
.enumerants
.iter()
.filter_map(|enumerant| {
// Skip enumerants with the same value as the previous.
if previous_value == Some(&enumerant.value) {
return None;
}
previous_value = Some(&enumerant.value);
let name = match enumerant.enumerant.as_str() {
"1D" => format_ident!("Dim1D"),
"2D" => format_ident!("Dim2D"),
"3D" => format_ident!("Dim3D"),
name => format_ident!("{}", name),
};
let parameters = enumerant
.parameters
.iter()
.map(|param| {
let name = to_member_name(&param.kind, param.name.as_deref());
let (ty, parse) = parameter_kinds[param.kind.as_str()].clone();
OperandMember { name, ty, parse }
})
.collect();
Some(KindEnumMember {
name,
value: enumerant.value.as_u64().unwrap() as u32,
parameters,
})
})
.collect();
(format_ident!("{}", operand_kind.kind), members)
})
.collect()
}
fn to_member_name(kind: &str, name: Option<&str>) -> Ident {
if let Some(name) = name {
let name = name.to_snake_case();
// Fix some weird names
match name.as_str() {
"argument_0_argument_1" => format_ident!("arguments"),
"member_0_type_member_1_type" => format_ident!("member_types"),
"operand_1_operand_2" => format_ident!("operands"),
"parameter_0_type_parameter_1_type" => format_ident!("parameter_types"),
"the_name_of_the_opaque_type" => format_ident!("name"),
"d_ref" => format_ident!("dref"),
"type" => format_ident!("ty"), // type is a keyword
_ => format_ident!("{}", name.replace("operand_", "operand")),
}
} else {
format_ident!("{}", kind.to_snake_case())
}
}
fn kinds_to_types(grammar: &SpirvGrammar) -> HashMap<&str, (TokenStream, TokenStream)> {
grammar
.operand_kinds
.iter()
.map(|k| {
let (ty, parse) = match k.kind.as_str() {
"LiteralContextDependentNumber" => {
(quote! { Vec<u32> }, quote! { reader.remainder() })
}
"LiteralExtInstInteger" | "LiteralInteger" | "LiteralInt32" => {
(quote! { u32 }, quote! { reader.next_u32()? })
}
"LiteralInt64" => (quote! { u64 }, quote! { reader.next_u64()? }),
"LiteralFloat32" => (
quote! { f32 },
quote! { f32::from_bits(reader.next_u32()?) },
),
"LiteralFloat64" => (
quote! { f64 },
quote! { f64::from_bits(reader.next_u64()?) },
),
"LiteralSpecConstantOpInteger" => (
quote! { SpecConstantInstruction },
quote! { SpecConstantInstruction::parse(reader)? },
),
"LiteralString" => (quote! { String }, quote! { reader.next_string()? }),
"PairIdRefIdRef" => (
quote! { (Id, Id) },
quote! {
(
Id(reader.next_u32()?),
Id(reader.next_u32()?),
)
},
),
"PairIdRefLiteralInteger" => (
quote! { (Id, u32) },
quote! {
(
Id(reader.next_u32()?),
reader.next_u32()?
)
},
),
"PairLiteralIntegerIdRef" => (
quote! { (u32, Id) },
quote! {
(
reader.next_u32()?,
Id(reader.next_u32()?)),
},
),
_ if k.kind.starts_with("Id") => (quote! { Id }, quote! { Id(reader.next_u32()?) }),
ident => {
let ident = format_ident!("{}", ident);
(quote! { #ident }, quote! { #ident::parse(reader)? })
}
};
(k.kind.as_str(), (ty, parse))
})
.collect()
}