@@ -28,7 +28,19 @@ export type TapeOp =
2828 | { type : "tan" ; a : number }
2929 | { type : "exp" ; a : number }
3030 | { type : "log" ; a : number }
31- | { type : "sqrt" ; a : number } ;
31+ | { type : "sqrt" ; a : number }
32+ // ── Vector ops: SIMD-style operations on contiguous blocks ──
33+ // These occupy `size` consecutive tape slots starting at the op's index.
34+ | { type : "vec_var" ; baseName : string ; size : number }
35+ | { type : "vec_const" ; vals : number [ ] ; size : number }
36+ | { type : "vec_add" ; a : number ; b : number ; size : number }
37+ | { type : "vec_sub" ; a : number ; b : number ; size : number }
38+ | { type : "vec_mul" ; a : number ; b : number ; size : number }
39+ | { type : "vec_neg" ; a : number ; size : number }
40+ // Extracts scalar t[a + offset] from a vector block starting at tape index `a`
41+ | { type : "vec_subscript" ; a : number ; offset : number }
42+ // No-op placeholder for vector padding slots (evaluators skip these)
43+ | { type : "nop" } ;
3244
3345function formatCDouble ( v : number ) : string {
3446 if ( ! isFinite ( v ) ) return v === Infinity ? "INFINITY" : v === - Infinity ? "(-INFINITY)" : "NAN" ;
@@ -187,6 +199,69 @@ export class StaticTapeBuilder {
187199 return [ this . walk ( expr ) ] ;
188200 }
189201
202+ /**
203+ * Push a vector op that reserves `size` consecutive tape slots.
204+ * The op itself occupies slot `idx`, and `size-1` placeholder slots follow.
205+ * Returns the starting index of the vector block.
206+ */
207+ public pushVecOp ( op : TapeOp & { size : number } ) : number {
208+ const idx = this . ops . length ;
209+ this . ops . push ( op ) ;
210+ // Reserve remaining size-1 slots with nop placeholders
211+ for ( let i = 1 ; i < op . size ; i ++ ) {
212+ this . ops . push ( { type : "nop" } ) ; // Placeholder; evaluators skip these
213+ }
214+ return idx ;
215+ }
216+
217+ /**
218+ * Walk an array expression as a single vectorized block.
219+ * If `expr` is a `ModelicaArray` of named variables, emits `vec_var` etc.
220+ * Returns the starting tape index and the block size.
221+ *
222+ * For array binary ops like `{a,b} + {c,d}`, emits `vec_add`.
223+ * Falls back to element-wise scalar tapes for complex expressions.
224+ */
225+ public walkArrayVectorized ( expr : ModelicaExpression ) : { startIdx : number ; size : number } {
226+ if ( ! ( expr instanceof ModelicaArray ) ) {
227+ // Scalar: single tape slot
228+ const idx = this . walk ( expr ) ;
229+ return { startIdx : idx , size : 1 } ;
230+ }
231+
232+ const flatElems = [ ...expr . flatElements ] ;
233+ const size = flatElems . length ;
234+ if ( size === 0 ) return { startIdx : this . pushOp ( { type : "const" , val : 0 } ) , size : 1 } ;
235+
236+ // Check if all elements are simple named variables with a common base
237+ const allNamed = flatElems . every ( ( e ) => e instanceof ModelicaNameExpression ) ;
238+ if ( allNamed && size > 1 ) {
239+ // Extract common base name (e.g., "x[1]", "x[2]" → base = "x")
240+ const firstName = ( flatElems [ 0 ] as ModelicaNameExpression ) . name ;
241+ const bracketPos = firstName . indexOf ( "[" ) ;
242+ const baseName = bracketPos >= 0 ? firstName . substring ( 0 , bracketPos ) : firstName ;
243+ const startIdx = this . pushVecOp ( { type : "vec_var" , baseName, size } ) ;
244+ return { startIdx, size } ;
245+ }
246+
247+ // Check if all elements are constants
248+ const allConst = flatElems . every ( ( e ) => e instanceof ModelicaRealLiteral || e instanceof ModelicaIntegerLiteral ) ;
249+ if ( allConst && size > 1 ) {
250+ const vals = flatElems . map ( ( e ) =>
251+ e instanceof ModelicaRealLiteral ? e . value : e instanceof ModelicaIntegerLiteral ? e . value : 0 ,
252+ ) ;
253+ const startIdx = this . pushVecOp ( { type : "vec_const" , vals, size } ) ;
254+ return { startIdx, size } ;
255+ }
256+
257+ // Fallback: walk each element as scalar, pack into contiguous block
258+ const startIdx = this . ops . length ;
259+ for ( const elem of flatElems ) {
260+ this . walk ( elem ) ;
261+ }
262+ return { startIdx, size } ;
263+ }
264+
190265 /**
191266 * Emit the C-code evaluating the expressions step-by-step.
192267 * `varResolver` maps the Modelica variable name into a valid C-code getter string
@@ -264,6 +339,32 @@ export class StaticTapeBuilder {
264339 case "sqrt" :
265340 rhs = `sqrt(t[${ op . a } ])` ;
266341 break ;
342+ // ── Vector ops: emit for-loops ──
343+ case "vec_var" :
344+ lines . push ( ` for (int _k = 0; _k < ${ op . size } ; _k++) t[${ i } +_k] = ${ varResolver ( `${ op . baseName } [_k+1]` ) } ;` ) ;
345+ continue ; // Skip default t[i] = rhs line
346+ case "vec_const" :
347+ for ( let k = 0 ; k < op . size ; k ++ ) {
348+ lines . push ( ` t[${ i + k } ] = ${ formatCDouble ( op . vals [ k ] ?? 0 ) } ;` ) ;
349+ }
350+ continue ;
351+ case "vec_add" :
352+ lines . push ( ` for (int _k = 0; _k < ${ op . size } ; _k++) t[${ i } +_k] = t[${ op . a } +_k] + t[${ op . b } +_k];` ) ;
353+ continue ;
354+ case "vec_sub" :
355+ lines . push ( ` for (int _k = 0; _k < ${ op . size } ; _k++) t[${ i } +_k] = t[${ op . a } +_k] - t[${ op . b } +_k];` ) ;
356+ continue ;
357+ case "vec_mul" :
358+ lines . push ( ` for (int _k = 0; _k < ${ op . size } ; _k++) t[${ i } +_k] = t[${ op . a } +_k] * t[${ op . b } +_k];` ) ;
359+ continue ;
360+ case "vec_neg" :
361+ lines . push ( ` for (int _k = 0; _k < ${ op . size } ; _k++) t[${ i } +_k] = -t[${ op . a } +_k];` ) ;
362+ continue ;
363+ case "vec_subscript" :
364+ rhs = `t[${ op . a + op . offset } ]` ;
365+ break ;
366+ case "nop" :
367+ continue ; // Skip padding slots
267368 }
268369 lines . push ( ` t[${ i } ] = ${ rhs } ;` ) ;
269370 }
0 commit comments