| // 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() |
| } |