@@ -60,6 +60,21 @@ impl<'a> Gen<'a> {
6060 ) ) ;
6161 }
6262
63+ // Optionally emit an `a * b + c` helper for each float type. The call
64+ // exercises float argument/return-value bridging — i.e. saving and
65+ // restoring the relevant float window (f0..f3 / d0..d3) around a call.
66+ let mut has_fma = [ false ; FLOAT_KINDS . len ( ) ] ;
67+ for ( k, kind) in FLOAT_KINDS . iter ( ) . enumerate ( ) {
68+ if self . next ( ) % 3 == 0 {
69+ has_fma[ k] = true ;
70+ decls. push ( format ! (
71+ "{}(a: {t}, b: {t}, c: {t}) -> {t} {{\n return a * b + c\n }}" ,
72+ kind. fma,
73+ t = kind. ty
74+ ) ) ;
75+ }
76+ }
77+
6378 // 1-4 initial integer variables
6479 let n_vars = ( self . next ( ) % 4 + 1 ) as usize ;
6580 for i in 0 ..n_vars {
@@ -106,6 +121,24 @@ impl<'a> Gen<'a> {
106121 vars. push ( ( name, VarType :: Enum ( n_variants) ) ) ;
107122 }
108123
124+ // Optionally declare 1-2 variables of each float type. f32 drives the
125+ // stack VM's single float window (f0..f3, spilling through fsp); f64
126+ // drives the double window (d0..d3, spilling through dfsp). Values are
127+ // kept small so downstream `as i32` casts stay well inside i32 range.
128+ let mut has_floats = [ false ; FLOAT_KINDS . len ( ) ] ;
129+ for ( k, kind) in FLOAT_KINDS . iter ( ) . enumerate ( ) {
130+ if self . next ( ) % 2 == 0 {
131+ has_floats[ k] = true ;
132+ let n_fvars = ( self . next ( ) % 2 + 1 ) as usize ; // 1-2
133+ for i in 0 ..n_fvars {
134+ let name = format ! ( "{}{}" , kind. var_prefix, i) ;
135+ main_lines
136+ . push ( format ! ( " var {} = {}" , name, self . gen_float_literal( kind. ty) ) ) ;
137+ vars. push ( ( name, VarType :: Float ( kind. ty ) ) ) ;
138+ }
139+ }
140+ }
141+
109142 // 1-5 computed values, each printed
110143 let n_stmts = ( self . next ( ) % 5 + 1 ) as usize ;
111144 for i in 0 ..n_stmts {
@@ -116,6 +149,25 @@ impl<'a> Gen<'a> {
116149 vars. push ( ( name, VarType :: Int ) ) ;
117150 }
118151
152+ // Float computations, each printed as i32. Printing the truncated
153+ // integer (rather than the raw float) keeps output comparable across
154+ // backends — Rust's float Display ("3") and the C interpreter's printf
155+ // ("3.0") format integral floats differently, so raw float prints
156+ // would diverge on formatting alone. The float arithmetic itself is
157+ // still fully exercised before the cast.
158+ for ( k, kind) in FLOAT_KINDS . iter ( ) . enumerate ( ) {
159+ if !has_floats[ k] {
160+ continue ;
161+ }
162+ let n_fstmts = ( self . next ( ) % 3 + 1 ) as usize ; // 1-3
163+ for i in 0 ..n_fstmts {
164+ let name = format ! ( "{}r{}" , kind. var_prefix, i) ;
165+ let expr = self . gen_float_expr ( & vars, kind, has_fma[ k] , 2 ) ;
166+ main_lines. push ( format ! ( " let {} = {}" , name, expr) ) ;
167+ main_lines. push ( format ! ( " print({} as i32)" , name) ) ;
168+ }
169+ }
170+
119171 main_lines. push ( "}" . to_string ( ) ) ;
120172
121173 let mut parts = decls;
@@ -289,11 +341,101 @@ impl<'a> Gen<'a> {
289341 let val = ( self . next ( ) % 200 ) as i32 - 100 ;
290342 self . format_int ( val)
291343 }
344+
345+ /// A small float literal in [0.0, 9.9], typed as `ty` ("f32"/"f64").
346+ /// Bare float literals are already f32, so an `as f32` cast would be an
347+ /// unsupported identity conversion — emit the bare literal for f32 and an
348+ /// explicit `as f64` conversion for f64. Values are bounded so that
349+ /// products of a few of these stay far inside i32 range after the final
350+ /// `as i32` cast.
351+ fn gen_float_literal ( & mut self , ty : & str ) -> String {
352+ let whole = self . next ( ) % 10 ;
353+ let frac = self . next ( ) % 10 ;
354+ if ty == "f32" {
355+ format ! ( "{}.{}" , whole, frac)
356+ } else {
357+ format ! ( "({}.{} as {})" , whole, frac, ty)
358+ }
359+ }
360+
361+ fn gen_float_expr (
362+ & mut self ,
363+ vars : & [ ( String , VarType ) ] ,
364+ kind : & FloatKind ,
365+ has_fma : bool ,
366+ depth : u8 ,
367+ ) -> String {
368+ if depth == 0 {
369+ return self . gen_float_leaf ( vars, kind) ;
370+ }
371+ let max_choice = if has_fma { 8 } else { 6 } ;
372+ match self . next ( ) % max_choice {
373+ // Float literal
374+ 0 ..=1 => self . gen_float_literal ( kind. ty ) ,
375+ // Float variable reference (of this type)
376+ 2 ..=3 => self . gen_float_var ( vars, kind. ty ) ,
377+ // Binary arithmetic (no division — avoids safety errors)
378+ 4 ..=5 => {
379+ let ops = [ "+" , "-" , "*" ] ;
380+ let op = ops[ self . next ( ) as usize % ops. len ( ) ] ;
381+ let l = self . gen_float_expr ( vars, kind, has_fma, depth - 1 ) ;
382+ let r = self . gen_float_expr ( vars, kind, has_fma, depth - 1 ) ;
383+ format ! ( "({} {} {})" , l, op, r)
384+ }
385+ // Helper call (only if its `a * b + c` helper was emitted) —
386+ // exercises float argument/return-value bridging across a call.
387+ 6 ..=7 => {
388+ let a = self . gen_float_expr ( vars, kind, has_fma, depth - 1 ) ;
389+ let b = self . gen_float_expr ( vars, kind, has_fma, depth - 1 ) ;
390+ let c = self . gen_float_expr ( vars, kind, has_fma, depth - 1 ) ;
391+ format ! ( "{}({}, {}, {})" , kind. fma, a, b, c)
392+ }
393+ _ => self . gen_float_literal ( kind. ty ) ,
394+ }
395+ }
396+
397+ fn gen_float_leaf ( & mut self , vars : & [ ( String , VarType ) ] , kind : & FloatKind ) -> String {
398+ if self . next ( ) % 2 == 0 {
399+ self . gen_float_var ( vars, kind. ty )
400+ } else {
401+ self . gen_float_literal ( kind. ty )
402+ }
403+ }
404+
405+ fn gen_float_var ( & mut self , vars : & [ ( String , VarType ) ] , ty : & str ) -> String {
406+ let fvars: Vec < & str > = vars
407+ . iter ( )
408+ . filter_map ( |( name, t) | match t {
409+ VarType :: Float ( vt) if * vt == ty => Some ( name. as_str ( ) ) ,
410+ _ => None ,
411+ } )
412+ . collect ( ) ;
413+ if fvars. is_empty ( ) {
414+ self . gen_float_literal ( ty)
415+ } else {
416+ let idx = self . next ( ) as usize % fvars. len ( ) ;
417+ fvars[ idx] . to_string ( )
418+ }
419+ }
420+ }
421+
422+ /// A float type the generator can emit, paired with the names it uses for
423+ /// that type's variables and `a * b + c` helper.
424+ struct FloatKind {
425+ ty : & ' static str ,
426+ var_prefix : & ' static str ,
427+ fma : & ' static str ,
292428}
293429
430+ const FLOAT_KINDS : [ FloatKind ; 2 ] = [
431+ FloatKind { ty : "f32" , var_prefix : "sv" , fma : "sfma" } ,
432+ FloatKind { ty : "f64" , var_prefix : "fv" , fma : "ffma" } ,
433+ ] ;
434+
294435#[ derive( Clone ) ]
295436enum VarType {
296437 Int ,
438+ Float ( & ' static str ) ,
297439 Struct ,
298440 Array ( usize ) ,
299441 Enum ( usize ) ,
@@ -321,6 +463,11 @@ fn capture_stdout<F: FnOnce()>(f: F) -> String {
321463 f ( ) ;
322464
323465 std:: io:: stdout ( ) . flush ( ) . ok ( ) ;
466+ // The stack backend's C interpreter prints via C stdio (printf), which
467+ // buffers independently of Rust's stdout. Flush all C streams before
468+ // restoring fd 1, or the buffered output is lost and the capture comes
469+ // back empty.
470+ unsafe { libc:: fflush ( std:: ptr:: null_mut ( ) ) } ;
324471
325472 unsafe { libc:: dup2 ( saved_fd, 1 ) } ;
326473 unsafe { libc:: close ( saved_fd) } ;
@@ -356,6 +503,17 @@ fn run_backend(program: &str, backend: &str) -> Option<String> {
356503 } ) ;
357504 Some ( output)
358505 }
506+ #[ cfg( has_stack_interp) ]
507+ "stack" => {
508+ // Compile to the stack VM and run it through the C interpreter
509+ // (the same path cli/src/main.rs uses for `--backend stack`).
510+ let output = capture_stdout ( || {
511+ if let Ok ( program) = compiler. compile_stack ( ) {
512+ let _ = lyte:: stack_interp_bridge:: run ( & program) ;
513+ }
514+ } ) ;
515+ Some ( output)
516+ }
359517 #[ cfg( target_arch = "aarch64" ) ]
360518 "asm" => {
361519 let output = capture_stdout ( || {
@@ -460,4 +618,10 @@ fuzz_target!(|data: &[u8]| {
460618 let llvm_output = run_backend( & program, "llvm" ) ;
461619 assert_same( "VM" , & vm_output, "LLVM" , & llvm_output, & program) ;
462620 }
621+
622+ #[ cfg( has_stack_interp) ]
623+ {
624+ let stack_output = run_backend( & program, "stack" ) ;
625+ assert_same( "VM" , & vm_output, "STACK" , & stack_output, & program) ;
626+ }
463627} ) ;
0 commit comments