Diff
24 "base": "bdce49a66c74c9a8f993578e31a7c9aa0fac52bb",
25 "head": "5637ecc41deeda7a6c37a28a721fcb49a6b11010",
26 "cob": "3c20aff5d9ebe67bb3376c8fb0b433b281436f07"
27+ },
28+ {
29+ "base": "5637ecc41deeda7a6c37a28a721fcb49a6b11010",
30+ "head": "428547431bf34ee6b8053c205c704dad8929ed4a",
31+ "cob": "b14aa7bd85cd31bc51fd9650dbf10315e58ba03d"
32 }
33 ]
34 }1 - Try a shared fast attribute decoder in `attrib.rs` for `sem`, `vertex`, and `uniform` that returns parsed small structs while preserving current error messages; this may reduce both duplicated logic and any remaining parsing overhead.
2-- Investigate whether `Vertex` can mirror the successful `Semantics` fusion approach by collecting field metadata into simple arrays/records first and expanding from those directly instead of accumulating per-field token fragments.
2+- Investigate whether `UniformInterface` can avoid per-field `TokenStream` statement accumulation, perhaps by collecting simpler field records first and emitting declarations from those directly while preserving declaration order and behavior.
5 {"run":4,"commit":"bdce49a","metric":577503,"metrics":{"semantics_µs":241770,"vertex_µs":76188,"uniform_µs":259545},"status":"discard","description":"Preallocate token vector capacities from known field and variant counts to reduce small Vec growth overhead.","timestamp":1776781735933,"segment":0,"confidence":34.74205095809644,"iterationTokens":6468,"asi":{"hypothesis":"Some remaining overhead might come from repeated Vec growth while collecting quote fragments, so reserving exact capacities could yield a cheap incremental win.","rollback_reason":"The capacity reservations were within benchmark noise and slightly worse than the current best run, so they are not worth keeping.","next_action_hint":"Focus on reducing token construction/allocation work or simplifying generated quote structure rather than container bookkeeping."}}
6 {"run":5,"commit":"bdce49a","metric":580006,"metrics":{"semantics_µs":243093,"vertex_µs":78819,"uniform_µs":258094},"status":"discard","description":"Inline Vertex field normalized parsing to avoid the generic attribute helper on the hot per-field path.","timestamp":1776781806221,"segment":0,"confidence":47.54755043227666,"iterationTokens":6596,"asi":{"hypothesis":"Even one generic helper call per vertex field might add measurable overhead, so specializing normalized parsing could further reduce vertex_µs.","rollback_reason":"Specializing the per-field parser did not beat the current best and slightly regressed the benchmark overall.","next_action_hint":"Investigate token generation/allocation costs next; attribute parsing likely delivered most of the easy win already.","ideas_backlog_updated":true}}
7 {"run":6,"commit":"5637ecc","metric":569920,"metrics":{"semantics_µs":241609,"vertex_µs":78154,"uniform_µs":250157},"status":"keep","description":"Reduce intermediate quote fragment churn by emitting Semantics, Vertex, and UniformInterface tokens more directly.","timestamp":1776781959754,"segment":0,"confidence":31.562080841036796,"iterationTokens":15670,"asi":{"hypothesis":"After the parsing wins, remaining overhead likely comes from constructing and nesting many temporary quote fragments, so flattening token assembly should cut allocation/churn in code generation.","observed_effect":"Best result so far; the biggest gain showed up in UniformInterface token generation while Semantics stayed roughly flat and Vertex regressed slightly but was outweighed by the uniform win.","idea_backlog_pruned":"Replaced the broad direct-token-assembly backlog item with narrower follow-ups for shared attribute decoding and Semantics-specific fusion."}}
8+{"run":7,"commit":"4285474","metric":541087,"metrics":{"semantics_µs":214890,"vertex_µs":77550,"uniform_µs":248647},"status":"keep","description":"Fuse Semantics code generation around parsed variant data arrays instead of building multiple per-variant TokenStream vectors.","timestamp":1776782041293,"segment":0,"confidence":38.45288652952887,"iterationTokens":7792,"asi":{"hypothesis":"Semantics still spent time creating several parallel TokenStream vectors; expanding directly from stored parsed data should reduce quote/allocation overhead in that hot path.","observed_effect":"Large semantics-specific win with a meaningful follow-on total improvement; uniform also improved slightly while vertex stayed near prior levels.","idea_backlog_pruned":"Removed the Semantics-specific fusion backlog item because it is now implemented; next promising path is analogous Vertex fusion or shared attribute decoding."}}
35 - Applied the same single-pass parsing idea to `Vertex` type-level attributes (`sem` and `instanced`). This produced a modest extra win, mainly by trimming `vertex_µs` and a bit of total overhead.
36 - Reduced intermediate `quote!` fragment churn by emitting token fragments more directly in `Semantics`, `UniformInterface`, and `Vertex` code assembly. This produced the best total result so far, with most of the gain coming from a notably faster `uniform_µs` path.
37 - Fused `Semantics` token generation further by collecting parsed variant data once and expanding directly from those arrays instead of first building multiple per-variant `TokenStream` vectors. This produced a large additional `semantics_µs` win and the strongest total result so far.
38+- Inlined `Vertex` field processing into the named/unnamed loops instead of routing every field through a generic helper. This cut `vertex_µs` sharply and produced another best total result.
128 let mut indexed_vertex_attrib_descs = Vec::new();
129 let mut fields_types = Vec::new();
130
131- for (i, field) in unnamed_fields.unnamed.into_iter().enumerate() {
132- let field_ident = format_ident!("field_{}", i);
133-
134- process_field(
135- &field,
136- field_ident,
137- &sem_type,
138- &instancing,
139- &mut indexed_vertex_attrib_descs,
140- &mut fields_types,
141- None
142- )?;
131+ for field in unnamed_fields.unnamed.into_iter() {
132+ let normalized = get_field_attr_once(
133+ &ident,
134+ &field.attrs,
135+ "vertex",
136+ "normalized",
137+ KNOWN_SUBKEYS
138+ ).map(|b: LitBool| b.value)
139+ .or_else(|e| match e {
140+ AttrError::CannotFindAttribute(..) => Ok(false),
141+ _ => Err(e)
142+ })
143+ .map_err(StructImplError::FieldError)?;
144+
145+ let field_ty = field.ty;
146+ indexed_vertex_attrib_descs.push(if normalized {
147+ quote! {
148+ luminance::vertex::VertexBufferDesc::new::<#sem_type>(
149+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS,
150+ #instancing,
151+ (<#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC).normalize(),
152+ )
153+ }
154+ } else {
155+ quote! {
156+ luminance::vertex::VertexBufferDesc::new::<#sem_type>(
157+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS,
158+ #instancing,
159+ <#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC,
160+ )
161+ }
162+ });
163+ fields_types.push(field_ty);
164 }
165
166 let output = process_struct(ident, indexed_vertex_attrib_descs, Vec::new(), fields_types);
173 let mut fields_names = Vec::new();
174
175 for field in named_fields.named {
155- let field_ident = field.ident.clone().unwrap();
156-
157- process_field(
158- &field,
159- field_ident,
160- &sem_type,
161- &instancing,
162- &mut indexed_vertex_attrib_descs,
163- &mut fields_types,
164- &mut fields_names
165- )?;
176+ let normalized = get_field_attr_once(
177+ &ident,
178+ &field.attrs,
179+ "vertex",
180+ "normalized",
181+ KNOWN_SUBKEYS
182+ ).map(|b: LitBool| b.value)
183+ .or_else(|e| match e {
184+ AttrError::CannotFindAttribute(..) => Ok(false),
185+ _ => Err(e)
186+ })
187+ .map_err(StructImplError::FieldError)?;
188+
189+ let field_ident = field.ident.unwrap();
190+ let field_ty = field.ty;
191+ indexed_vertex_attrib_descs.push(if normalized {
192+ quote! {
193+ luminance::vertex::VertexBufferDesc::new::<#sem_type>(
194+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS,
195+ #instancing,
196+ (<#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC).normalize(),
197+ )
198+ }
199+ } else {
200+ quote! {
201+ luminance::vertex::VertexBufferDesc::new::<#sem_type>(
202+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS,
203+ #instancing,
204+ <#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC,
205+ )
206+ }
207+ });
208+ fields_types.push(field_ty);
209+ fields_names.push(field_ident);
210 }
211
212 let output = process_struct(ident, indexed_vertex_attrib_descs, fields_names, fields_types);
217 }
218 }
219
176-fn process_field<'a, FN>(
177- field: &Field,
178- ident: Ident,
179- sem_type: &Type,
180- instancing: &proc_macro2::TokenStream,
181- indexed_vertex_attrib_descs: &mut Vec<proc_macro2::TokenStream>,
182- fields_types: &mut Vec<Type>,
183- fields_names: FN,
184-) -> Result<(), StructImplError>
185-where FN: Into<Option<&'a mut Vec<Ident>>> {
186- // search for the normalized argument; if not there, we don’t normalize anything
187- let normalized = get_field_attr_once(
188- &ident,
189- &field.attrs,
190- "vertex",
191- "normalized",
192- KNOWN_SUBKEYS
193- ).map(|b: LitBool| b.value)
194- .or_else(|e| match e {
195- AttrError::CannotFindAttribute(..) => Ok(false),
196- _ => Err(e)
197- })
198- .map_err(StructImplError::FieldError)?;
199-
200- let field_ty = &field.ty;
201- let vertex_attrib_desc = if normalized {
202- quote!{ (<#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC).normalize() }
203- } else {
204- quote!{ <#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC }
205- };
206-
207- let indexed_vertex_attrib_desc_q = quote!{
208- luminance::vertex::VertexBufferDesc::new::<#sem_type>(
209- <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS,
210- #instancing,
211- #vertex_attrib_desc,
212- )
213- };
214-
215- indexed_vertex_attrib_descs.push(indexed_vertex_attrib_desc_q);
216- fields_types.push(field_ty.clone());
217-
218- if let Some(fields_names) = fields_names.into() {
219- fields_names.push(ident);
220- }
221-
222- Ok(())
223-}
224-
220 /// Process the output struct.
221 ///
222 /// If fields_names is empty, it is assumed to be a struct-tuple.