diff options
Diffstat (limited to 'autogen/spirv_parse.rs')
-rw-r--r-- | autogen/spirv_parse.rs | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/autogen/spirv_parse.rs b/autogen/spirv_parse.rs new file mode 100644 index 0000000..e2d37b4 --- /dev/null +++ b/autogen/spirv_parse.rs @@ -0,0 +1,686 @@ +// 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(¶m.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(¶m.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() +} |