Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions crates/rue-air/src/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1549,9 +1549,16 @@ impl<'a> ConstraintGenerator<'a> {
}

// Type declarations don't produce values
InstData::FnDecl { .. } | InstData::StructDecl { .. } | InstData::EnumDecl { .. } => {
InferType::Concrete(Type::Unit)
}
InstData::FnDecl { .. }
| InstData::StructDecl { .. }
| InstData::EnumDecl { .. }
| InstData::ImplDecl { .. } => InferType::Concrete(Type::Unit),

// Method call - placeholder for Phase 3 (see ADR-0009)
InstData::MethodCall { .. } => InferType::Concrete(Type::Unit),

// Associated function call - placeholder for Phase 3 (see ADR-0009)
InstData::AssocFnCall { .. } => InferType::Concrete(Type::Unit),
};

// Record the type for this expression
Expand Down
12 changes: 12 additions & 0 deletions crates/rue-air/src/inst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,15 @@ pub enum AirInstData {
/// The variant index (0-based)
variant_index: u32,
},

// Drop/destructor operations
/// Drop a value, running its destructor if the type has one.
/// For trivially droppable types, this is a no-op.
/// The type is stored in the AirInst.ty field.
Drop {
/// The value to drop
value: AirRef,
},
}

impl fmt::Display for AirRef {
Expand Down Expand Up @@ -526,6 +535,9 @@ impl fmt::Display for Air {
} => {
writeln!(f, "enum_variant #{}::{}", enum_id.0, variant_index)?;
}
AirInstData::Drop { value } => {
writeln!(f, "drop {}", value)?;
}
}
}
writeln!(f, "}}")
Expand Down
31 changes: 31 additions & 0 deletions crates/rue-air/src/sema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2600,6 +2600,37 @@ impl<'a> Sema<'a> {
});
Ok(AnalysisResult::new(air_ref, ty))
}

// Impl block declarations are processed during collection phase, skip here
InstData::ImplDecl { .. } => {
// Return Unit - impl blocks don't produce a value
let air_ref = air.add_inst(AirInst {
data: AirInstData::UnitConst,
ty: Type::Unit,
span: inst.span,
});
Ok(AnalysisResult::new(air_ref, Type::Unit))
}

// Method call - placeholder for Phase 3 (see ADR-0009)
InstData::MethodCall { .. } => {
let air_ref = air.add_inst(AirInst {
data: AirInstData::UnitConst,
ty: Type::Unit,
span: inst.span,
});
Ok(AnalysisResult::new(air_ref, Type::Unit))
}

// Associated function call - placeholder for Phase 3 (see ADR-0009)
InstData::AssocFnCall { .. } => {
let air_ref = air.add_inst(AirInst {
data: AirInstData::UnitConst,
ty: Type::Unit,
span: inst.span,
});
Ok(AnalysisResult::new(air_ref, Type::Unit))
}
}
}

Expand Down
97 changes: 94 additions & 3 deletions crates/rue-cfg/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! This module converts the structured control flow in AIR (Branch, Loop)
//! into explicit basic blocks with terminators.

use rue_air::{Air, AirInstData, AirPattern, AirRef, Type};
use rue_air::{Air, AirInstData, AirPattern, AirRef, ArrayTypeDef, StructDef, Type};
use rue_error::{CompileWarning, WarningKind};

use crate::CfgOutput;
Expand Down Expand Up @@ -37,6 +37,10 @@ struct LoopContext {
pub struct CfgBuilder<'a> {
air: &'a Air,
cfg: Cfg,
/// Struct definitions for type queries (e.g., needs_drop)
struct_defs: &'a [StructDef],
/// Array type definitions for type queries
array_types: &'a [ArrayTypeDef],
/// Current block we're building
current_block: BlockId,
/// Stack of loop contexts for nested loops
Expand All @@ -49,7 +53,17 @@ pub struct CfgBuilder<'a> {

impl<'a> CfgBuilder<'a> {
/// Build a CFG from AIR, returning the CFG and any warnings.
pub fn build(air: &'a Air, num_locals: u32, num_params: u32, fn_name: &str) -> CfgOutput {
///
/// The `struct_defs` and `array_types` parameters provide type definitions
/// needed for queries like `type_needs_drop`.
pub fn build(
air: &'a Air,
num_locals: u32,
num_params: u32,
fn_name: &str,
struct_defs: &'a [StructDef],
array_types: &'a [ArrayTypeDef],
) -> CfgOutput {
let mut builder = CfgBuilder {
air,
cfg: Cfg::new(
Expand All @@ -58,6 +72,8 @@ impl<'a> CfgBuilder<'a> {
num_params,
fn_name.to_string(),
),
struct_defs,
array_types,
current_block: BlockId(0),
loop_stack: Vec::new(),
value_cache: vec![None; air.len()],
Expand Down Expand Up @@ -1192,6 +1208,26 @@ impl<'a> CfgBuilder<'a> {
continuation: Continuation::Continues,
}
}

AirInstData::Drop { value } => {
// Lower the value to drop
let val = self.lower_inst(*value).value.unwrap();
let val_ty = self.air.get(*value).ty;

// Only emit a Drop instruction if the type needs drop.
// For trivially droppable types, this is a no-op.
// We use self.type_needs_drop() which has access to struct/array
// definitions to recursively check if fields need drop.
if self.type_needs_drop(val_ty) {
self.emit(CfgInstData::Drop { value: val }, Type::Unit, span);
}

// Drop is a statement, produces no value
ExprResult {
value: None,
continuation: Continuation::Continues,
}
}
}
}

Expand All @@ -1205,6 +1241,53 @@ impl<'a> CfgBuilder<'a> {
fn cache(&mut self, air_ref: AirRef, value: CfgValue) {
self.value_cache[air_ref.as_u32() as usize] = Some(value);
}

/// Check if a type needs to be dropped (has a destructor).
///
/// This method has access to struct and array definitions, allowing it to
/// recursively check if struct fields or array elements need drop.
///
/// A type needs drop if dropping it requires cleanup actions:
/// - Primitives, bool, unit, never, error, enums: trivially droppable (no)
/// - String: will need drop when mutable strings land (currently no)
/// - Struct: needs drop if any field needs drop
/// - Array: needs drop if element type needs drop
fn type_needs_drop(&self, ty: Type) -> bool {
match ty {
// Primitive types are trivially droppable
Type::I8
| Type::I16
| Type::I32
| Type::I64
| Type::U8
| Type::U16
| Type::U32
| Type::U64
| Type::Bool
| Type::Unit
| Type::Never
| Type::Error => false,

// Enum types are trivially droppable (just discriminant values)
Type::Enum(_) => false,

// String will need drop when mutable strings land (heap allocation)
// For now, string literals are static and don't need drop
Type::String => false,

// Struct types need drop if any field needs drop
Type::Struct(struct_id) => {
let struct_def = &self.struct_defs[struct_id.0 as usize];
struct_def.fields.iter().any(|f| self.type_needs_drop(f.ty))
}

// Array types need drop if element type needs drop
Type::Array(array_id) => {
let array_def = &self.array_types[array_id.0 as usize];
self.type_needs_drop(array_def.element_type)
}
}
}
}

#[cfg(test)]
Expand All @@ -1230,7 +1313,15 @@ mod tests {
let output = sema.analyze_all().unwrap();

let func = &output.functions[0];
CfgBuilder::build(&func.air, func.num_locals, func.num_param_slots, &func.name).cfg
CfgBuilder::build(
&func.air,
func.num_locals,
func.num_param_slots,
&func.name,
&output.struct_defs,
&output.array_types,
)
.cfg
}

#[test]
Expand Down
10 changes: 10 additions & 0 deletions crates/rue-cfg/src/inst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ pub enum CfgInstData {
enum_id: EnumId,
variant_index: u32,
},

// Drop/destructor operations
/// Drop a value, running its destructor if the type has one.
/// For trivially droppable types, this is a no-op that will be elided.
Drop {
value: CfgValue,
},
}

/// Block terminator - how control leaves a basic block.
Expand Down Expand Up @@ -673,6 +680,9 @@ impl Cfg {
} => {
write!(f, "enum_variant #{}::{}", enum_id.0, variant_index)
}
CfgInstData::Drop { value } => {
write!(f, "drop {}", value)
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/rue-codegen/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ rust_library(
deps = [
"//crates/rue-air:rue-air",
"//crates/rue-cfg:rue-cfg",
"//crates/rue-error:rue-error",
"//crates/rue-span:rue-span",
],
visibility = ["PUBLIC"],
Expand All @@ -15,6 +16,7 @@ rust_test(
deps = [
"//crates/rue-air:rue-air",
"//crates/rue-cfg:rue-cfg",
"//crates/rue-error:rue-error",
"//crates/rue-intern:rue-intern",
"//crates/rue-lexer:rue-lexer",
"//crates/rue-parser:rue-parser",
Expand Down
23 changes: 21 additions & 2 deletions crates/rue-codegen/src/aarch64/cfg_lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,19 @@ impl<'a> CfgLower<'a> {
imm: *variant_index as i64,
});
}

CfgInstData::Drop { value: _ } => {
// Drop instruction - runs destructor if the type needs one.
// The CFG builder already elides Drop for trivially droppable types,
// so reaching here means we need to emit actual cleanup code.
//
// This will be implemented in Phase 3 (rue-wjha.9) when we add
// types with destructors (e.g., mutable strings).
debug_assert!(
false,
"Drop instruction reached codegen but no types currently need drop"
);
}
}
}

Expand Down Expand Up @@ -2628,8 +2641,14 @@ mod tests {
let struct_defs = &output.struct_defs;
let array_types = &output.array_types;
let strings = &output.strings;
let cfg_output =
CfgBuilder::build(&func.air, func.num_locals, func.num_param_slots, &func.name);
let cfg_output = CfgBuilder::build(
&func.air,
func.num_locals,
func.num_param_slots,
&func.name,
struct_defs,
array_types,
);

CfgLower::new(&cfg_output.cfg, struct_defs, array_types, strings).lower()
}
Expand Down
9 changes: 5 additions & 4 deletions crates/rue-codegen/src/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub use regalloc::RegAlloc;

use rue_air::{ArrayTypeDef, StructDef};
use rue_cfg::Cfg;
use rue_error::CompileResult;

use crate::MachineCode;

Expand All @@ -35,7 +36,7 @@ pub fn generate(
struct_defs: &[StructDef],
array_types: &[ArrayTypeDef],
strings: &[String],
) -> MachineCode {
) -> CompileResult<MachineCode> {
let num_locals = cfg.num_locals();
let num_params = cfg.num_params();

Expand All @@ -45,16 +46,16 @@ pub fn generate(
// Allocate physical registers
let existing_slots = num_locals + num_params;
let (mir, num_spills, used_callee_saved) =
RegAlloc::new(mir, existing_slots).allocate_with_spills();
RegAlloc::new(mir, existing_slots).allocate_with_spills()?;

// Emit machine code bytes
let total_locals = num_locals + num_spills;
let (code, relocations) =
Emitter::new(&mir, total_locals, num_params, &used_callee_saved, strings).emit();

MachineCode {
Ok(MachineCode {
code,
relocations,
strings: strings.to_vec(),
}
})
}
Loading