diff --git a/flux-ftl/src/codegen.rs b/flux-ftl/src/codegen.rs index a86f4a2..6887e5f 100644 --- a/flux-ftl/src/codegen.rs +++ b/flux-ftl/src/codegen.rs @@ -557,9 +557,44 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { .into(), ) } - TypeBody::Struct { .. } | TypeBody::Variant { .. } | TypeBody::Fn { .. } => { - // For now, represent complex types as i64 - Some(self.context.i64_type().into()) + TypeBody::Struct { fields, .. } => { + let field_types: Vec> = fields + .iter() + .filter_map(|f| self.type_ref_to_llvm(&f.type_ref)) + .collect(); + if field_types.is_empty() { + None // unit-like struct + } else { + Some(self.context.struct_type(&field_types, false).into()) + } + } + TypeBody::Variant { cases } => { + let tag_type = self.context.i32_type(); + // Find the case with the largest payload using type_ref_byte_size + let max_case = cases + .iter() + .filter(|c| self.type_ref_to_llvm(&c.payload).is_some()) + .max_by_key(|c| self.type_ref_byte_size(&c.payload)); + match max_case { + Some(c) => { + let payload_ty = self.type_ref_to_llvm(&c.payload).unwrap(); + Some( + self.context + .struct_type(&[tag_type.into(), payload_ty], false) + .into(), + ) + } + None => Some(tag_type.into()), // enum with no payloads + } + } + TypeBody::Fn { .. } => { + // Function pointers are represented as i8* + Some( + self.context + .i8_type() + .ptr_type(AddressSpace::default()) + .into(), + ) } } } @@ -594,10 +629,23 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { TypeBody::Boolean => 1, TypeBody::Unit => 0, TypeBody::Opaque { size, .. } => *size as u64, - TypeBody::Array { max_length, .. } => { - *max_length as u64 * 8 + TypeBody::Array { + element, + max_length, + .. + } => *max_length as u64 * self.type_ref_byte_size(element), + TypeBody::Struct { fields, .. } => fields + .iter() + .map(|f| self.type_ref_byte_size(&f.type_ref)) + .sum(), + TypeBody::Variant { cases } => { + 4 + cases + .iter() + .map(|c| self.type_ref_byte_size(&c.payload)) + .max() + .unwrap_or(0) } - _ => 8, + TypeBody::Fn { .. } => 8, // pointer size }, None => 8, } @@ -605,6 +653,26 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { } } + /// Get element size in bytes for a memory node. For array types, returns + /// the element type's size; otherwise returns the full type's size. + fn get_memory_element_size(&self, mem_node_id: &str) -> u64 { + for mem in &self.program.memories { + if mem.id.0 == mem_node_id + && let MemoryOp::Alloc { type_ref, .. } = &mem.op + { + if let TypeRef::Id { node } = type_ref + && let Some(td) = + self.program.types.iter().find(|t| t.id.0 == node.0) + && let TypeBody::Array { element, .. } = &td.body + { + return self.type_ref_byte_size(element); + } + return self.type_ref_byte_size(type_ref); + } + } + 8 // fallback + } + // ------------------------------------------------------------------ // Emit global constants (ConstBytes) // ------------------------------------------------------------------ @@ -751,7 +819,9 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { .map_err(|e| CodegenError::EmitFailed(e.to_string()))? .into_pointer_value(); - let element_size = self.context.i64_type().const_int(8, false); + let element_size_bytes = self.get_memory_element_size(&target.0); + let element_size = + self.context.i64_type().const_int(element_size_bytes, false); let idx_i64 = self.int_to_i64(idx_val.into_int_value(), builder)?; let byte_offset = builder .build_int_mul(idx_i64, element_size, "byte_offset") @@ -801,7 +871,9 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { .map_err(|e| CodegenError::EmitFailed(e.to_string()))? .into_pointer_value(); - let element_size = self.context.i64_type().const_int(8, false); + let element_size_bytes = self.get_memory_element_size(&source.0); + let element_size = + self.context.i64_type().const_int(element_size_bytes, false); let idx_i64 = self.int_to_i64(idx_val.into_int_value(), builder)?; let byte_offset = builder .build_int_mul(idx_i64, element_size, "byte_offset") @@ -1001,6 +1073,8 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { // Body builder.position_at_end(body_bb); + // Zero-initialize scoped region memory at loop iteration start + self.emit_scoped_region_cleanup(builder)?; self.emit_control_node(&body.0, function, builder)?; if builder .get_insert_block() @@ -1020,6 +1094,82 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { Ok(()) } + // ------------------------------------------------------------------ + // Scoped region cleanup (zero-init at loop iteration boundaries) + // ------------------------------------------------------------------ + + fn emit_scoped_region_cleanup( + &self, + builder: &inkwell::builder::Builder<'ctx>, + ) -> Result<(), CodegenError> { + // Only zero frame-scoped regions (innermost scoped regions whose parent + // is another scoped or game region). We identify these as scoped regions + // that have a parent which is also a scoped region. If no such nesting + // exists, pick scoped regions that are NOT the outermost scoped region. + let scoped_regions: Vec<&crate::ast::RegionDef> = self + .program + .regions + .iter() + .filter(|r| matches!(r.lifetime, crate::ast::Lifetime::Scoped)) + .collect(); + + // Find innermost scoped regions: those whose parent is also scoped + let outer_scoped_ids: std::collections::HashSet = scoped_regions + .iter() + .filter(|r| { + if let Some(parent) = &r.parent { + // If parent is static, this is an outer scoped region + self.program + .regions + .iter() + .any(|p| p.id == *parent && matches!(p.lifetime, crate::ast::Lifetime::Static)) + } else { + true + } + }) + .map(|r| r.id.0.clone()) + .collect(); + + // Inner scoped regions = scoped but NOT outer + let scoped_region_ids: Vec = scoped_regions + .iter() + .filter(|r| !outer_scoped_ids.contains(&r.id.0)) + .map(|r| r.id.0.clone()) + .collect(); + + if scoped_region_ids.is_empty() { + return Ok(()); + } + + // For each memory allocation in a scoped region, zero out its memory + for mem in &self.program.memories { + if let MemoryOp::Alloc { + region, type_ref, .. + } = &mem.op + && scoped_region_ids.contains(®ion.0) + && let Some(ptr) = self.pointers.get(&mem.id.0) + { + let size = self.type_ref_byte_size(type_ref); + let i8_ptr = builder + .build_bitcast( + *ptr, + self.context.i8_type().ptr_type(AddressSpace::default()), + "cleanup_ptr", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))? + .into_pointer_value(); + + let size_val = self.context.i64_type().const_int(size, false); + let zero = self.context.i8_type().const_int(0, false); + + builder + .build_memset(i8_ptr, 1, zero, size_val) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + } + } + Ok(()) + } + // ------------------------------------------------------------------ // Compute node side-effect emission (for Seq steps referencing C:) // ------------------------------------------------------------------ @@ -1980,6 +2130,238 @@ impl<'ctx, 'prog> CodeGenerator<'ctx, 'prog> { .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; Ok(BasicValueEnum::IntValue(result)) } + "abs" => { + if inputs.is_empty() { + return Err(CodegenError::Unsupported( + "abs expects 1 input".to_string(), + )); + } + let val = self.resolve_value(&inputs[0].0, function, builder)?; + let int_val = val.into_int_value(); + let zero = int_val.get_type().const_int(0, false); + let is_neg = builder + .build_int_compare(inkwell::IntPredicate::SLT, int_val, zero, "is_neg") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let negated = builder + .build_int_neg(int_val, "negated") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let result = builder + .build_select( + is_neg, + BasicValueEnum::IntValue(negated), + BasicValueEnum::IntValue(int_val), + "abs_result", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + Ok(result) + } + "min" => { + if inputs.len() < 2 { + return Err(CodegenError::Unsupported( + "min expects 2 inputs".to_string(), + )); + } + let a = self + .resolve_value(&inputs[0].0, function, builder)? + .into_int_value(); + let b = self + .resolve_value(&inputs[1].0, function, builder)? + .into_int_value(); + let (a, b) = self.normalize_int_widths(a, b, builder)?; + let cmp = builder + .build_int_compare(inkwell::IntPredicate::SLT, a, b, "min_cmp") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let result = builder + .build_select( + cmp, + BasicValueEnum::IntValue(a), + BasicValueEnum::IntValue(b), + "min_result", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + Ok(result) + } + "max" => { + if inputs.len() < 2 { + return Err(CodegenError::Unsupported( + "max expects 2 inputs".to_string(), + )); + } + let a = self + .resolve_value(&inputs[0].0, function, builder)? + .into_int_value(); + let b = self + .resolve_value(&inputs[1].0, function, builder)? + .into_int_value(); + let (a, b) = self.normalize_int_widths(a, b, builder)?; + let cmp = builder + .build_int_compare(inkwell::IntPredicate::SGT, a, b, "max_cmp") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let result = builder + .build_select( + cmp, + BasicValueEnum::IntValue(a), + BasicValueEnum::IntValue(b), + "max_result", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + Ok(result) + } + "clamp" => { + if inputs.len() < 3 { + return Err(CodegenError::Unsupported( + "clamp expects 3 inputs (val, min, max)".to_string(), + )); + } + let val = self + .resolve_value(&inputs[0].0, function, builder)? + .into_int_value(); + let lo = self + .resolve_value(&inputs[1].0, function, builder)? + .into_int_value(); + let hi = self + .resolve_value(&inputs[2].0, function, builder)? + .into_int_value(); + // clamp = max(lo, min(val, hi)) + let (val_n, hi_n) = self.normalize_int_widths(val, hi, builder)?; + let cmp_hi = builder + .build_int_compare(inkwell::IntPredicate::SLT, val_n, hi_n, "cmp_hi") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let min_val = builder + .build_select( + cmp_hi, + BasicValueEnum::IntValue(val_n), + BasicValueEnum::IntValue(hi_n), + "min_val", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let (min_n, lo_n) = + self.normalize_int_widths(min_val.into_int_value(), lo, builder)?; + let cmp_lo = builder + .build_int_compare(inkwell::IntPredicate::SGT, min_n, lo_n, "cmp_lo") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let result = builder + .build_select( + cmp_lo, + BasicValueEnum::IntValue(min_n), + BasicValueEnum::IntValue(lo_n), + "clamp_result", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + Ok(result) + } + "bhaskara_approx" => { + // Bhaskara sine approximation: 16x(pi-x) / (5pi^2 - 4x(pi-x)) + // Input: one i32 value representing angle * 1000 (fixed-point) + // Output: i32 result * 1000 + if inputs.is_empty() { + return Err(CodegenError::Unsupported( + "bhaskara_approx expects 1 input".to_string(), + )); + } + let val = self.resolve_value(&inputs[0].0, function, builder)?; + let int_val = val.into_int_value(); + + // Constants for fixed-point Bhaskara approximation + let i64_ty = self.context.i64_type(); + let pi_1000 = i64_ty.const_int(3142, false); // pi * 1000 + let sixteen = i64_ty.const_int(16, false); + let five = i64_ty.const_int(5, false); + let four = i64_ty.const_int(4, false); + let thousand = i64_ty.const_int(1000, false); + + // Widen input to i64 + let x = builder + .build_int_s_extend(int_val, i64_ty, "x_ext") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + + // pi_minus_x = pi*1000 - x + let pi_minus_x = builder + .build_int_sub(pi_1000, x, "pi_minus_x") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + + // numerator = 16 * x * pi_minus_x + let num1 = builder + .build_int_mul(sixteen, x, "num1") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let numerator = builder + .build_int_mul(num1, pi_minus_x, "numerator") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + + // denom_part1 = 5 * pi^2 = 5 * pi_1000 * pi_1000 / 1000 + let pi_sq = builder + .build_int_mul(pi_1000, pi_1000, "pi_sq") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let five_pi_sq = builder + .build_int_mul(five, pi_sq, "five_pi_sq") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let denom1 = builder + .build_int_signed_div(five_pi_sq, thousand, "denom1") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + + // denom_part2 = 4 * x * pi_minus_x / 1000 + let four_x = builder + .build_int_mul(four, x, "four_x") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let denom2_raw = builder + .build_int_mul(four_x, pi_minus_x, "denom2_raw") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let denom2 = builder + .build_int_signed_div(denom2_raw, thousand, "denom2") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + + // denominator = denom1 - denom2 + let denominator = builder + .build_int_sub(denom1, denom2, "denominator") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + + // result = numerator / denominator (with division-by-zero guard) + let zero = i64_ty.const_int(0, false); + let is_zero = builder + .build_int_compare( + inkwell::IntPredicate::EQ, + denominator, + zero, + "denom_is_zero", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + // If denominator is zero, return 0 (degenerate sine input) + let safe_denom = builder + .build_select( + is_zero, + BasicValueEnum::IntValue(i64_ty.const_int(1, false)), + BasicValueEnum::IntValue(denominator), + "safe_denom", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let div_result = builder + .build_int_signed_div( + numerator, + safe_denom.into_int_value(), + "bhaskara_div", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))?; + let result = builder + .build_select( + is_zero, + BasicValueEnum::IntValue(i64_ty.const_int(0, false)), + BasicValueEnum::IntValue(div_result), + "bhaskara_result", + ) + .map_err(|e| CodegenError::EmitFailed(e.to_string()))? + .into_int_value(); + + // Truncate back to original width if needed + let result_truncated = if int_val.get_type().get_bit_width() < 64 { + builder + .build_int_truncate(result, int_val.get_type(), "bhaskara_trunc") + .map_err(|e| CodegenError::EmitFailed(e.to_string()))? + } else { + result + }; + + Ok(BasicValueEnum::IntValue(result_truncated)) + } _ => { // Unknown generic compute: return a zero constant Ok(self.context.i64_type().const_int(0, false).into())