Skip to content

Commit 168a9ea

Browse files
MarijnS95claude
andcommitted
grammar: Auto-discover all extended instruction sets from SPIRV-Headers
Instead of maintaining a hardcoded list of extended instruction sets, automatically glob for all `extinst.*.grammar.json` files in the SPIRV-Headers submodule. Module names, op enum names, and table names are derived systematically from the grammar filename. The `ext_inst_table()` lookup normalises its input (lowercasing and converting underscores to hyphens) so that any casing variant of the canonical import name resolves correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7543803 commit 168a9ea

20 files changed

Lines changed: 4816 additions & 391 deletions

autogen/src/main.rs

Lines changed: 126 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod table;
99
mod utils;
1010

1111
use std::{
12+
collections::BTreeMap,
1213
env, fs,
1314
io::Write,
1415
path::{Path, PathBuf},
@@ -128,78 +129,134 @@ fn main() {
128129
original
129130
};
130131

131-
// (import_name, file_key, op, url)
132-
// import_name: canonical OpExtInstImport string (case-sensitive, per SPIRV-Tools)
133-
// file_key: grammar filename stem (lowercase), may differ from import_name
134-
let extended_instruction_sets = [
135-
("GLSL.std.450", "glsl.std.450", "GLOp", "https://registry.khronos.org/SPIR-V/specs/unified1/GLSL.std.450.html"),
136-
("OpenCL.std", "opencl.std.100", "CLOp", "https://registry.khronos.org/SPIR-V/specs/unified1/OpenCL.ExtendedInstructionSet.100.html"),
137-
("NonSemantic.DebugPrintF", "nonsemantic.debugprintf", "DebugPrintFOp", "https://github.khronos.org/SPIRV-Registry/nonsemantic/NonSemantic.DebugPrintf.html"),
138-
];
139-
let extended_instruction_sets = extended_instruction_sets.map(|(ext, file_key, op, url)| {
140-
let grammar: structs::ExtInstSetGrammar = serde_json::from_str(
141-
&std::fs::read_to_string(autogen_src_dir.join(format!(
142-
"external/SPIRV-Headers/include/spirv/unified1/extinst.{file_key}.grammar.json",
143-
)))
144-
.unwrap(),
145-
)
146-
.unwrap();
147-
(ext, file_key, op, url, grammar)
148-
});
132+
// Automatically discover all extended instruction sets from SPIRV-Headers
133+
let extinst_dir = autogen_src_dir.join("external/SPIRV-Headers/include/spirv/unified1");
134+
let extended_instruction_sets: BTreeMap<String, structs::ExtInstSetGrammar> =
135+
fs::read_dir(&extinst_dir)
136+
.unwrap()
137+
.filter_map(|entry| {
138+
let entry = entry.unwrap();
139+
let filename = entry.file_name().to_string_lossy().into_owned();
140+
let key = filename
141+
.strip_prefix("extinst.")
142+
.and_then(|s| s.strip_suffix(".grammar.json"))?;
143+
let grammar: structs::ExtInstSetGrammar =
144+
serde_json::from_str(&fs::read_to_string(entry.path()).unwrap()).unwrap();
145+
Some((key.to_owned(), grammar))
146+
})
147+
.collect();
149148

150-
// SPIR-V header
151-
write_formatted(&autogen_src_dir.join("../spirv/autogen_spirv.rs"), {
152-
let core = header::gen_spirv_header(&grammar);
153-
let extended_instruction_sets =
154-
extended_instruction_sets
155-
.iter()
156-
.map(|(ext, _file_key, op, url, grammar)| {
157-
header::gen_opcodes(
158-
op,
159-
grammar,
160-
&format!("[{}]({}) extended instruction opcode", ext, url),
161-
)
162-
.to_string()
163-
});
164-
format!(
165-
"{}\n{}",
166-
core,
167-
extended_instruction_sets.collect::<Vec<_>>().join("\n")
168-
)
169-
});
149+
// Canonical OpExtInstImport name strings for known extended instruction sets.
150+
// Grammar filenames don't carry these, and the casing/separators are not
151+
// derivable (e.g. "OpenCL.std" vs filename "opencl.std.100"). Names are
152+
// case-sensitive and must match exactly what SPIR-V binaries contain.
153+
// Reference: https://github.com/KhronosGroup/SPIRV-Tools/blob/main/source/ext_inst.cpp
154+
let canonical_import_names: BTreeMap<&str, &str> = BTreeMap::from([
155+
("arm.motion-engine.100", "Arm.MotionEngine.100"),
156+
("debuginfo", "DebugInfo"),
157+
("glsl.std.450", "GLSL.std.450"),
158+
("nonsemantic.clspvreflection", "NonSemantic.ClspvReflection"),
159+
("nonsemantic.debugbreak", "NonSemantic.DebugBreak"),
160+
("nonsemantic.debugprintf", "NonSemantic.DebugPrintf"),
161+
(
162+
"nonsemantic.shader.debuginfo.100",
163+
"NonSemantic.Shader.DebugInfo.100",
164+
),
165+
("nonsemantic.vkspreflection", "NonSemantic.VkspReflection"),
166+
("opencl.debuginfo.100", "OpenCL.DebugInfo.100"),
167+
("opencl.std.100", "OpenCL.std"),
168+
("spv-amd-gcn-shader", "SPV_AMD_gcn_shader"),
169+
("spv-amd-shader-ballot", "SPV_AMD_shader_ballot"),
170+
(
171+
"spv-amd-shader-explicit-vertex-parameter",
172+
"SPV_AMD_shader_explicit_vertex_parameter",
173+
),
174+
(
175+
"spv-amd-shader-trinary-minmax",
176+
"SPV_AMD_shader_trinary_minmax",
177+
),
178+
("tosa.001000.1", "TOSA.001000.1"),
179+
]);
170180

171-
// Derive module and variant names for extensions with operand kinds
172-
let ext_info: Vec<_> = extended_instruction_sets
173-
.iter()
174-
.map(|(_ext, file_key, _, _, grammar)| {
175-
let module_name = file_key.replace(['.', '-'], "_");
176-
let variant_name: String = file_key
181+
// Derive module, variant, and op names for each extended instruction set
182+
struct ExtInstInfo {
183+
/// Lowercased grammar filename key, e.g. "glsl.std.450"
184+
file_key: String,
185+
/// Canonical OpExtInstImport name, e.g. "GLSL.std.450"
186+
import_name: String,
187+
/// Module name: dots/hyphens replaced with underscores, e.g. "glsl_std_450"
188+
module_name: String,
189+
/// PascalCase variant name, e.g. "GlslStd450", "NonsemanticDebugprintf"
190+
variant_name: String,
191+
/// Op enum name in the spirv crate, e.g. "GlslStd450Op"
192+
op_name: String,
193+
/// UPPER_CASE table name prefix, e.g. "GLSL_STD_450_INSTRUCTION"
194+
table_name: String,
195+
/// Whether this extension defines its own operand kinds
196+
has_operand_kinds: bool,
197+
grammar: structs::ExtInstSetGrammar,
198+
}
199+
let extended_instruction_sets: Vec<ExtInstInfo> = extended_instruction_sets
200+
.into_iter()
201+
.map(|(key, grammar)| {
202+
let import_name = canonical_import_names
203+
.get(key.as_str())
204+
.unwrap_or_else(|| panic!("unknown extended instruction set {:?}, add its canonical OpExtInstImport name to the mapping", key))
205+
.to_string();
206+
let module_name = key.replace(['.', '-'], "_");
207+
let variant_name: String = key
177208
.split(['.', '-'])
178209
.flat_map(|part| {
179210
let mut chars = part.chars();
180-
// Uppercase the first character
181211
chars.next().unwrap().to_uppercase().chain(chars)
182212
})
183213
.collect();
214+
let op_name = format!("{variant_name}Op");
215+
let table_name = format!("{}_INSTRUCTION", module_name.to_uppercase());
184216
let has_operand_kinds = !grammar.operand_kinds.is_empty();
185-
(module_name, variant_name, has_operand_kinds)
217+
ExtInstInfo {
218+
file_key: key,
219+
import_name,
220+
module_name,
221+
variant_name,
222+
op_name,
223+
table_name,
224+
has_operand_kinds,
225+
grammar,
226+
}
186227
})
187228
.collect();
188229

230+
// SPIR-V header
231+
write_formatted(&autogen_src_dir.join("../spirv/autogen_spirv.rs"), {
232+
let core = header::gen_spirv_header(&grammar);
233+
let ext_opcodes = extended_instruction_sets
234+
.iter()
235+
.map(|ext| {
236+
header::gen_opcodes(
237+
&ext.op_name,
238+
&ext.grammar,
239+
&format!(
240+
"[{}](https://github.com/KhronosGroup/SPIRV-Headers/blob/main/include/spirv/unified1/extinst.{}.grammar.json) extended instruction opcode",
241+
ext.import_name, ext.file_key,
242+
),
243+
)
244+
.to_string()
245+
});
246+
format!("{}\n{}", core, ext_opcodes.collect::<Vec<_>>().join("\n"))
247+
});
248+
189249
// Wrapper variants for core OperandKind: only extensions with operand kinds
190-
let ext_wrapper_variants: Vec<(&str, &str)> = ext_info
250+
let ext_wrapper_variants: Vec<(&str, &str)> = extended_instruction_sets
191251
.iter()
192-
.filter(|(_, _, has)| *has)
193-
.map(|(module, variant, _)| (variant.as_str(), module.as_str()))
252+
.filter(|ext| ext.has_operand_kinds)
253+
.map(|ext| (ext.variant_name.as_str(), ext.module_name.as_str()))
194254
.collect();
195255

196-
// Collect ExtInstOp variant info: (variant_name, op_name) for all extended sets
256+
// All extension variants for ExtInstOp enum
197257
let ext_inst_variants: Vec<(&str, &str)> = extended_instruction_sets
198258
.iter()
199-
.map(|(_, _, op, _, _)| {
200-
let variant = op.strip_suffix("Op").unwrap_or(op);
201-
(variant, *op)
202-
})
259+
.map(|ext| (ext.variant_name.as_str(), ext.op_name.as_str()))
203260
.collect();
204261

205262
// Instruction table
@@ -219,30 +276,28 @@ fn main() {
219276
),
220277
);
221278
// Extended instruction sets
222-
for (
223-
(ext, _file_key, spirv_op, _, ext_grammar),
224-
(module_name, variant_name, _has_operand_kinds),
225-
) in extended_instruction_sets.iter().zip(&ext_info)
226-
{
227-
let autogen_file = format!("autogen_{module_name}.rs");
228-
let table_name = format!("{}_INSTRUCTION", module_name.to_uppercase());
229-
let ext_variant_name = spirv_op.strip_suffix("Op").unwrap_or(spirv_op);
279+
for ext in &extended_instruction_sets {
280+
let autogen_file = format!("autogen_{}.rs", ext.module_name);
230281
write_formatted(
231282
&autogen_src_dir.join(format!("../rspirv/grammar/{autogen_file}")),
232283
table::gen_ext_instruction_file(
233-
&ext_grammar.operand_kinds,
234-
&ext_grammar.instructions,
235-
spirv_op,
236-
ext_variant_name,
237-
&table_name,
238-
variant_name,
284+
&ext.grammar.operand_kinds,
285+
&ext.grammar.instructions,
286+
&ext.op_name,
287+
&ext.variant_name,
288+
&ext.table_name,
289+
&ext.variant_name,
239290
),
240291
);
241292
tables.push_str(&format!(
242-
"pub mod {module_name} {{ use super::*; include!(\"{autogen_file}\"); }}\n\
243-
pub use {module_name}::{table_name}_TABLE;\n"
293+
"pub mod {} {{ use super::*; include!(\"{autogen_file}\"); }}\n\
294+
pub use {}::{}_TABLE;\n",
295+
ext.module_name, ext.module_name, ext.table_name,
296+
));
297+
table_lookup.push_str(&format!(
298+
"\"{}\" => &{}_TABLE,\n",
299+
ext.import_name, ext.table_name,
244300
));
245-
table_lookup.push_str(&format!("\"{ext}\" => &{table_name}_TABLE,\n"));
246301
}
247302
write_formatted(
248303
&autogen_src_dir.join("../rspirv/grammar/autogen_tables.rs"),

rspirv/binary/autogen_parse_operand.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ impl Parser<'_, '_> {
209209
GOpKind::LiteralContextDependentNumber => panic!(),
210210
GOpKind::LiteralSpecConstantOpInteger => panic!(),
211211
GOpKind::PairLiteralIntegerIdRef => panic!(),
212+
GOpKind::Debuginfo(_) => {
213+
todo!("extended instruction operand kind not yet supported for parsing")
214+
}
215+
GOpKind::NonsemanticClspvreflection(_) => {
216+
todo!("extended instruction operand kind not yet supported for parsing")
217+
}
218+
GOpKind::NonsemanticShaderDebuginfo100(_) => {
219+
todo!("extended instruction operand kind not yet supported for parsing")
220+
}
221+
GOpKind::OpenclDebuginfo100(_) => {
222+
todo!("extended instruction operand kind not yet supported for parsing")
223+
}
212224
})
213225
}
214226
fn parse_image_operands_arguments(
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// AUTOMATICALLY GENERATED from the SPIR-V JSON grammar:
2+
// external/spirv.core.grammar.json.
3+
// DO NOT MODIFY!
4+
5+
static ARM_MOTION_ENGINE_100_INSTRUCTIONS: &[ExtendedInstruction<'static>] = &[
6+
ext_inst!(
7+
ArmMotionEngine100,
8+
ArmMotionEngine100Op,
9+
MIN_SAD,
10+
[],
11+
[],
12+
[
13+
(IdRef, One),
14+
(IdRef, One),
15+
(IdRef, One),
16+
(IdRef, One),
17+
(IdRef, One),
18+
(IdRef, One),
19+
(IdRef, One),
20+
(IdRef, One),
21+
(IdRef, One)
22+
]
23+
),
24+
ext_inst!(
25+
ArmMotionEngine100,
26+
ArmMotionEngine100Op,
27+
MIN_SAD_COST,
28+
[],
29+
[],
30+
[
31+
(IdRef, One),
32+
(IdRef, One),
33+
(IdRef, One),
34+
(IdRef, One),
35+
(IdRef, One),
36+
(IdRef, One),
37+
(IdRef, One),
38+
(IdRef, One),
39+
(IdRef, One)
40+
]
41+
),
42+
ext_inst!(
43+
ArmMotionEngine100,
44+
ArmMotionEngine100Op,
45+
RAW_SAD,
46+
[],
47+
[],
48+
[
49+
(IdRef, One),
50+
(IdRef, One),
51+
(IdRef, One),
52+
(IdRef, One),
53+
(IdRef, One),
54+
(IdRef, One),
55+
(IdRef, One),
56+
(IdRef, One)
57+
]
58+
),
59+
];
60+
pub static ARM_MOTION_ENGINE_100_INSTRUCTION_TABLE: InstructionTable<ExtInstOp> =
61+
InstructionTable(ARM_MOTION_ENGINE_100_INSTRUCTIONS, std::marker::PhantomData);

0 commit comments

Comments
 (0)