use std::collections::{HashMap, HashSet}; use crate::{ ast::{BinaryOp, MatchPatternKind}, check::{CheckedFunction, CheckedProgram, TExpr, TExprKind, TMatchArm}, diag::Diagnostic, std_runtime, types::Type, }; pub fn emit(_file: &str, program: &CheckedProgram) -> Result> { let mut out = String::new(); let layouts = struct_layouts(program); let enum_layouts = enum_layouts(program); let string_globals = StringGlobals::collect(program); let needs_process_runtime = needs_process_runtime(program); out.push_str("; Module generated by glagol\n"); out.push_str(&format!("; Slovo module: {}\n\n", program.module)); out.push_str("declare void @print_i32(i32)\n\n"); out.push_str("declare void @print_i64(i64)\n\n"); out.push_str("declare void @print_u32(i32)\n\n"); out.push_str("declare void @print_u64(i64)\n\n"); out.push_str("declare void @print_f64(double)\n\n"); out.push_str("declare void @print_string(ptr)\n\n"); out.push_str("declare void @print_bool(i1)\n\n"); out.push_str("declare i32 @string_len(ptr)\n\n"); out.push_str("declare ptr @__glagol_string_concat(ptr, ptr)\n\n"); out.push_str("declare i64 @__glagol_string_parse_i32_result(ptr)\n\n"); out.push_str("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)\n\n"); out.push_str("declare i64 @__glagol_string_parse_u32_result(ptr)\n\n"); out.push_str("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)\n\n"); out.push_str("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)\n\n"); out.push_str("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)\n\n"); out.push_str("declare ptr @__glagol_json_quote_string(ptr)\n\n"); out.push_str("declare ptr @__glagol_num_i32_to_string(i32)\n\n"); out.push_str("declare ptr @__glagol_num_i64_to_string(i64)\n\n"); out.push_str("declare ptr @__glagol_num_u32_to_string(i32)\n\n"); out.push_str("declare ptr @__glagol_num_u64_to_string(i64)\n\n"); out.push_str("declare ptr @__glagol_num_f64_to_string(double)\n\n"); out.push_str("declare void @__glagol_io_eprint(ptr)\n\n"); out.push_str("declare ptr @__glagol_io_read_stdin_result()\n\n"); out.push_str("declare void @__glagol_process_init(i32, ptr)\n\n"); out.push_str("declare i32 @__glagol_process_argc()\n\n"); out.push_str("declare ptr @__glagol_process_arg(i32)\n\n"); out.push_str("declare ptr @__glagol_process_arg_result(i32)\n\n"); out.push_str("declare ptr @__glagol_env_get(ptr)\n\n"); out.push_str("declare ptr @__glagol_env_get_result(ptr)\n\n"); out.push_str("declare ptr @__glagol_fs_read_text(ptr)\n\n"); out.push_str("declare ptr @__glagol_fs_read_text_result(ptr)\n\n"); out.push_str("declare i32 @__glagol_fs_write_text(ptr, ptr)\n\n"); out.push_str("declare i32 @__glagol_fs_write_text_result(ptr, ptr)\n\n"); out.push_str("declare i1 @__glagol_fs_exists(ptr)\n\n"); out.push_str("declare i1 @__glagol_fs_is_file(ptr)\n\n"); out.push_str("declare i1 @__glagol_fs_is_dir(ptr)\n\n"); out.push_str("declare i32 @__glagol_fs_remove_file_result(ptr)\n\n"); out.push_str("declare i32 @__glagol_fs_create_dir_result(ptr)\n\n"); out.push_str("declare i64 @__glagol_fs_open_text_read_result(ptr)\n\n"); out.push_str("declare ptr @__glagol_fs_read_open_text_result(i32)\n\n"); out.push_str("declare i32 @__glagol_fs_close_result(i32)\n\n"); out.push_str("declare i64 @__glagol_net_tcp_connect_loopback_result(i32)\n\n"); out.push_str("declare i64 @__glagol_net_tcp_listen_loopback_result(i32)\n\n"); out.push_str("declare i64 @__glagol_net_tcp_bound_port_result(i32)\n\n"); out.push_str("declare i64 @__glagol_net_tcp_accept_result(i32)\n\n"); out.push_str("declare ptr @__glagol_net_tcp_read_all_result(i32)\n\n"); out.push_str("declare i32 @__glagol_net_tcp_write_text_result(i32, ptr)\n\n"); out.push_str("declare i32 @__glagol_net_tcp_close_result(i32)\n\n"); out.push_str("declare i1 @__glagol_string_eq(ptr, ptr)\n\n"); out.push_str("declare ptr @__glagol_vec_i32_empty()\n\n"); out.push_str("declare ptr @__glagol_vec_i32_append(ptr, i32)\n\n"); out.push_str("declare i32 @__glagol_vec_i32_len(ptr)\n\n"); out.push_str("declare i32 @__glagol_vec_i32_index(ptr, i32)\n\n"); out.push_str("declare i1 @__glagol_vec_i32_eq(ptr, ptr)\n\n"); out.push_str("declare ptr @__glagol_vec_i64_empty()\n\n"); out.push_str("declare ptr @__glagol_vec_i64_append(ptr, i64)\n\n"); out.push_str("declare i32 @__glagol_vec_i64_len(ptr)\n\n"); out.push_str("declare i64 @__glagol_vec_i64_index(ptr, i32)\n\n"); out.push_str("declare i1 @__glagol_vec_i64_eq(ptr, ptr)\n\n"); out.push_str("declare ptr @__glagol_vec_f64_empty()\n\n"); out.push_str("declare ptr @__glagol_vec_f64_append(ptr, double)\n\n"); out.push_str("declare i32 @__glagol_vec_f64_len(ptr)\n\n"); out.push_str("declare double @__glagol_vec_f64_index(ptr, i32)\n\n"); out.push_str("declare i1 @__glagol_vec_f64_eq(ptr, ptr)\n\n"); out.push_str("declare ptr @__glagol_vec_bool_empty()\n\n"); out.push_str("declare ptr @__glagol_vec_bool_append(ptr, i1)\n\n"); out.push_str("declare i32 @__glagol_vec_bool_len(ptr)\n\n"); out.push_str("declare i1 @__glagol_vec_bool_index(ptr, i32)\n\n"); out.push_str("declare i1 @__glagol_vec_bool_eq(ptr, ptr)\n\n"); out.push_str("declare ptr @__glagol_vec_string_empty()\n\n"); out.push_str("declare ptr @__glagol_vec_string_append(ptr, ptr)\n\n"); out.push_str("declare i32 @__glagol_vec_string_len(ptr)\n\n"); out.push_str("declare ptr @__glagol_vec_string_index(ptr, i32)\n\n"); out.push_str("declare i1 @__glagol_vec_string_eq(ptr, ptr)\n\n"); out.push_str("declare i32 @__glagol_time_monotonic_ms()\n\n"); out.push_str("declare void @__glagol_time_sleep_ms(i32)\n\n"); out.push_str("declare i32 @__glagol_random_i32()\n\n"); out.push_str("declare void @__glagol_array_bounds_trap()\n\n"); out.push_str("declare void @__glagol_unwrap_some_trap()\n\n"); out.push_str("declare void @__glagol_unwrap_ok_trap()\n\n"); out.push_str("declare void @__glagol_unwrap_err_trap()\n\n"); let mut declared_c_imports = HashSet::new(); for import in &program.c_imports { if !declared_c_imports.insert(import.name.as_str()) { continue; } let params = import .params .iter() .map(|(_, ty)| ty.llvm()) .collect::>() .join(", "); out.push_str(&format!( "declare {} @{}({})\n\n", import.return_type.llvm(), import.name, params )); } if !string_globals.definitions.is_empty() { out.push_str(&string_globals.emit_definitions()); out.push('\n'); } for function in &program.functions { out.push_str(&emit_function( function.file.as_str(), function, &layouts, &enum_layouts, &string_globals, needs_process_runtime, )?); out.push('\n'); } Ok(out) } #[derive(Clone)] struct StructLayout { fields: Vec<(String, Type)>, } type StructLayouts = HashMap; #[derive(Clone)] struct EnumLayout { payload_ty: Option, } type EnumLayouts = HashMap; struct StringGlobals { names: HashMap, definitions: Vec, } struct StringGlobal { name: String, value: String, } impl StringGlobals { fn collect(program: &CheckedProgram) -> Self { let mut globals = Self { names: HashMap::new(), definitions: Vec::new(), }; for function in &program.functions { for expr in &function.body { globals.collect_expr(expr); } } globals } fn collect_expr(&mut self, expr: &TExpr) { match &expr.kind { TExprKind::String(value) => { self.intern(value); } TExprKind::EnumVariant { payload, .. } => { if let Some(payload) = payload { self.collect_expr(payload); } } TExprKind::StructInit { fields, .. } => { for (_, value) in fields { self.collect_expr(value); } } TExprKind::ArrayInit { elements } => { for element in elements { self.collect_expr(element); } } TExprKind::OptionSome { value } | TExprKind::ResultOk { value } | TExprKind::ResultErr { value } | TExprKind::OptionIsSome { value } | TExprKind::OptionIsNone { value } | TExprKind::OptionUnwrapSome { value } | TExprKind::ResultIsOk { value, .. } | TExprKind::ResultIsErr { value, .. } | TExprKind::ResultUnwrapOk { value, .. } | TExprKind::ResultUnwrapErr { value, .. } | TExprKind::FieldAccess { value, .. } => self.collect_expr(value), TExprKind::Index { array, index } => { self.collect_expr(array); self.collect_expr(index); } TExprKind::Local { initializer, .. } => self.collect_expr(initializer), TExprKind::Set { expr, .. } => self.collect_expr(expr), TExprKind::Binary { left, right, .. } => { self.collect_expr(left); self.collect_expr(right); } TExprKind::If { condition, then_expr, else_expr, } => { self.collect_expr(condition); self.collect_expr(then_expr); self.collect_expr(else_expr); } TExprKind::Match { subject, arms } => { self.collect_expr(subject); for arm in arms { for expr in &arm.body { self.collect_expr(expr); } } } TExprKind::While { condition, body } => { self.collect_expr(condition); for expr in body { self.collect_expr(expr); } } TExprKind::Unsafe { body } => { for expr in body { self.collect_expr(expr); } } TExprKind::Call { args, .. } => { for arg in args { self.collect_expr(arg); } } TExprKind::Int(_) | TExprKind::Int64(_) | TExprKind::UInt32(_) | TExprKind::UInt64(_) | TExprKind::Float(_) | TExprKind::Bool(_) | TExprKind::Var(_) | TExprKind::OptionNone => {} } } fn intern(&mut self, value: &str) { if self.names.contains_key(value) { return; } let name = format!(".str.{}", self.definitions.len()); self.names.insert(value.to_string(), name.clone()); self.definitions.push(StringGlobal { name, value: value.to_string(), }); } fn global_name(&self, value: &str) -> Option<&str> { self.names.get(value).map(String::as_str) } fn emit_definitions(&self) -> String { let mut out = String::new(); for global in &self.definitions { out.push_str(&format!( "@{} = private unnamed_addr constant [{} x i8] c\"{}\", align 1\n", global.name, global.value.len() + 1, llvm_c_string(&global.value) )); } out } } fn llvm_c_string(value: &str) -> String { let mut out = String::new(); for byte in value.bytes().chain(std::iter::once(0)) { match byte { b'"' => out.push_str("\\22"), b'\\' => out.push_str("\\5C"), b'\n' => out.push_str("\\0A"), b'\r' => out.push_str("\\0D"), b'\t' => out.push_str("\\09"), 0x20..=0x7e => out.push(byte as char), _ => out.push_str(&format!("\\{:02X}", byte)), } } out } fn struct_layouts(program: &CheckedProgram) -> StructLayouts { program .structs .iter() .map(|decl| { ( decl.name.clone(), StructLayout { fields: decl.fields.clone(), }, ) }) .collect() } fn enum_layouts(program: &CheckedProgram) -> EnumLayouts { program .enums .iter() .map(|decl| { ( decl.name.clone(), EnumLayout { payload_ty: decl .variants .iter() .find_map(|variant| variant.payload_ty.clone()), }, ) }) .collect() } fn needs_process_runtime(program: &CheckedProgram) -> bool { program .functions .iter() .flat_map(|function| function.body.iter()) .any(expr_needs_process_runtime) } fn expr_needs_process_runtime(expr: &TExpr) -> bool { match &expr.kind { TExprKind::StructInit { fields, .. } => fields .iter() .any(|(_, value)| expr_needs_process_runtime(value)), TExprKind::ArrayInit { elements } => elements.iter().any(expr_needs_process_runtime), TExprKind::OptionSome { value } | TExprKind::ResultOk { value } | TExprKind::ResultErr { value } | TExprKind::OptionIsSome { value } | TExprKind::OptionIsNone { value } | TExprKind::OptionUnwrapSome { value } | TExprKind::ResultIsOk { value, .. } | TExprKind::ResultIsErr { value, .. } | TExprKind::ResultUnwrapOk { value, .. } | TExprKind::ResultUnwrapErr { value, .. } | TExprKind::FieldAccess { value, .. } => expr_needs_process_runtime(value), TExprKind::Index { array, index } => { expr_needs_process_runtime(array) || expr_needs_process_runtime(index) } TExprKind::Local { initializer, .. } => expr_needs_process_runtime(initializer), TExprKind::Set { expr, .. } => expr_needs_process_runtime(expr), TExprKind::Binary { left, right, .. } => { expr_needs_process_runtime(left) || expr_needs_process_runtime(right) } TExprKind::If { condition, then_expr, else_expr, } => { expr_needs_process_runtime(condition) || expr_needs_process_runtime(then_expr) || expr_needs_process_runtime(else_expr) } TExprKind::Match { subject, arms } => { expr_needs_process_runtime(subject) || arms .iter() .flat_map(|arm| arm.body.iter()) .any(expr_needs_process_runtime) } TExprKind::While { condition, body } => { expr_needs_process_runtime(condition) || body.iter().any(expr_needs_process_runtime) } TExprKind::Unsafe { body } => body.iter().any(expr_needs_process_runtime), TExprKind::Call { name, args } => { matches!( std_runtime::runtime_symbol(name), Some( "__glagol_process_argc" | "__glagol_process_arg" | "__glagol_process_arg_result" ) ) || args.iter().any(expr_needs_process_runtime) } TExprKind::Int(_) | TExprKind::Int64(_) | TExprKind::UInt32(_) | TExprKind::UInt64(_) | TExprKind::Float(_) | TExprKind::Bool(_) | TExprKind::String(_) | TExprKind::Var(_) | TExprKind::OptionNone => false, TExprKind::EnumVariant { payload, .. } => payload .as_deref() .map(expr_needs_process_runtime) .unwrap_or(false), } } fn emit_function( file: &str, function: &CheckedFunction, layouts: &StructLayouts, enum_layouts: &EnumLayouts, string_globals: &StringGlobals, needs_process_runtime: bool, ) -> Result> { validate_function_signature(file, function, layouts, enum_layouts)?; let mut gen = FunctionGen { next_reg: 0, next_block: 0, local_alloc_counts: HashMap::new(), current_block: "entry".to_string(), locals: HashMap::new(), lines: Vec::new(), file: file.to_string(), layouts, enum_layouts, string_globals, }; for (name, ty) in &function.params { match ty { Type::Array(inner, len) if llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() => { let llvm_ty = llvm_type(layouts, enum_layouts, ty).expect("validated array type"); let ptr = gen.local_addr(name); gen.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); gen.lines .push(format!("store {} %{}, ptr {}", llvm_ty, name, ptr)); gen.locals.insert( name.clone(), LocalValue::Array { ptr, ty: ty.clone(), }, ); } _ => { gen.locals .insert(name.clone(), LocalValue::Value(format!("%{}", name))); } } } let is_entry_main = needs_process_runtime && function.name == "main" && function.params.is_empty(); let params = if is_entry_main { "i32 %__glagol_argc, ptr %__glagol_argv".to_string() } else { function .params .iter() .map(|(name, ty)| { format!( "{} %{}", llvm_type(layouts, enum_layouts, ty).expect("validated type"), name ) }) .collect::>() .join(", ") }; let return_ty = llvm_type(layouts, enum_layouts, &function.return_type).expect("validated return type"); let mut out = String::new(); out.push_str(&format!( "define {} @{}({}) {{\n", return_ty, function.name, params )); out.push_str("entry:\n"); if is_entry_main { gen.lines.push( "call void @__glagol_process_init(i32 %__glagol_argc, ptr %__glagol_argv)".to_string(), ); } let mut last = None; for expr in &function.body { match gen.emit_expr(expr) { Ok(value) => last = Some(value), Err(err) => return Err(vec![err]), } } for line in gen.lines { if !line.ends_with(':') { out.push_str(" "); } out.push_str(&line); out.push('\n'); } match function.return_type { Type::Unit => out.push_str(" ret void\n"), _ => { let value = last.unwrap_or_else(|| "0".to_string()); let return_ty = llvm_type(layouts, enum_layouts, &function.return_type) .expect("validated return type"); out.push_str(&format!(" ret {} {}\n", return_ty, value)); } } out.push_str("}\n"); Ok(out) } fn validate_function_signature( file: &str, function: &CheckedFunction, layouts: &StructLayouts, enum_layouts: &EnumLayouts, ) -> Result<(), Vec> { let mut errors = Vec::new(); let mut reported_types = HashSet::new(); for (_, ty) in &function.params { if !is_backend_param_type_supported(ty, layouts, enum_layouts) && reported_types.insert(ty.to_string()) { errors.push(unsupported_type(file, function, ty)); } } if !is_backend_return_type_supported(&function.return_type, layouts, enum_layouts) && reported_types.insert(function.return_type.to_string()) { errors.push(unsupported_type(file, function, &function.return_type)); } if errors.is_empty() { Ok(()) } else { Err(errors) } } fn is_backend_param_type_supported( ty: &Type, layouts: &StructLayouts, enum_layouts: &EnumLayouts, ) -> bool { match ty { Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::Unit | Type::String => true, Type::Named(_) => true, Type::Array(inner, len) => { llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() } Type::Vec(inner) => { **inner == Type::I32 || **inner == Type::I64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String } Type::Option(inner) => { **inner == Type::I32 || **inner == Type::I64 || **inner == Type::U32 || **inner == Type::U64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String } Type::Result(ok, err) => result_type_supported(ok, err), _ => false, } } fn is_backend_return_type_supported( ty: &Type, layouts: &StructLayouts, enum_layouts: &EnumLayouts, ) -> bool { match ty { Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::Unit | Type::String => true, Type::Named(_) => true, Type::Array(inner, len) => { llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() } Type::Vec(inner) => { **inner == Type::I32 || **inner == Type::I64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String } Type::Option(inner) => { **inner == Type::I32 || **inner == Type::I64 || **inner == Type::U32 || **inner == Type::U64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String } Type::Result(ok, err) => result_type_supported(ok, err), _ => false, } } fn llvm_type(layouts: &StructLayouts, enum_layouts: &EnumLayouts, ty: &Type) -> Option { match ty { Type::Array(inner, len) => llvm_fixed_array_type(layouts, enum_layouts, inner, *len), Type::Named(name) => { if let Some(layout) = enum_layouts.get(name) { return Some(if let Some(payload_ty) = &layout.payload_ty { format!( "{{ i32, {} }}", llvm_type(layouts, enum_layouts, payload_ty)? ) } else { "i32".to_string() }); } let Some(layout) = layouts.get(name) else { return Some("i32".to_string()); }; let fields = layout .fields .iter() .map(|(_, ty)| llvm_type(layouts, enum_layouts, ty)) .collect::>>()? .join(", "); Some(format!("{{ {} }}", fields)) } _ => Some(ty.llvm().to_string()), } } fn llvm_fixed_array_type( layouts: &StructLayouts, enum_layouts: &EnumLayouts, inner: &Type, len: usize, ) -> Option { if len == 0 { return None; } let inner_ty = match inner { Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String => { inner.llvm().to_string() } Type::Named(_) => llvm_type(layouts, enum_layouts, inner)?, _ => return None, }; Some(format!("[{} x {}]", len, inner_ty)) } fn format_f64_literal(value: f64) -> String { format!("{:.17}", value) } fn is_vec_i32_type(ty: &Type) -> bool { matches!(ty, Type::Vec(inner) if **inner == Type::I32) } fn is_vec_i64_type(ty: &Type) -> bool { matches!(ty, Type::Vec(inner) if **inner == Type::I64) } fn is_vec_f64_type(ty: &Type) -> bool { matches!(ty, Type::Vec(inner) if **inner == Type::F64) } fn is_vec_bool_type(ty: &Type) -> bool { matches!(ty, Type::Vec(inner) if **inner == Type::Bool) } fn is_vec_string_type(ty: &Type) -> bool { matches!(ty, Type::Vec(inner) if **inner == Type::String) } fn vec_eq_runtime_symbol(ty: &Type) -> Option<&'static str> { if is_vec_i32_type(ty) { Some("__glagol_vec_i32_eq") } else if is_vec_i64_type(ty) { Some("__glagol_vec_i64_eq") } else if is_vec_f64_type(ty) { Some("__glagol_vec_f64_eq") } else if is_vec_bool_type(ty) { Some("__glagol_vec_bool_eq") } else if is_vec_string_type(ty) { Some("__glagol_vec_string_eq") } else { None } } fn result_type_supported(ok: &Type, err: &Type) -> bool { matches!( (ok, err), (Type::I32, Type::I32) | (Type::I64, Type::I32) | (Type::U32, Type::I32) | (Type::U64, Type::I32) | (Type::F64, Type::I32) | (Type::Bool, Type::I32) | (Type::String, Type::I32) ) } fn zero_value_for_type(ty: &Type) -> Option { match ty { Type::I32 => Some("0".to_string()), Type::I64 => Some("0".to_string()), Type::U32 => Some("0".to_string()), Type::U64 => Some("0".to_string()), Type::F64 => Some("0.0".to_string()), Type::Bool => Some("0".to_string()), Type::String => Some("null".to_string()), Type::Array(_, _) | Type::Named(_) => Some("zeroinitializer".to_string()), _ => None, } } fn result_payload_index(ty: &Type, ok_payload: bool) -> Option { match ty { Type::Option(inner) if (**inner == Type::I32 || **inner == Type::I64 || **inner == Type::U32 || **inner == Type::U64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String) && ok_payload => { Some(1) } Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => Some(1), Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => { Some(if ok_payload { 1 } else { 2 }) } Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => Some(1), Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => { Some(if ok_payload { 1 } else { 2 }) } Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { Some(if ok_payload { 1 } else { 2 }) } Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => { Some(if ok_payload { 1 } else { 2 }) } Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { Some(if ok_payload { 1 } else { 2 }) } _ => None, } } fn unsupported_type(file: &str, function: &CheckedFunction, ty: &Type) -> Diagnostic { Diagnostic::new( file, "UnsupportedBackendFeature", format!( "backend does not support `{}` in function `{}` signature", ty, function.name ), ) .with_span(function.span) .hint( "keep this type in speculative examples until layout and LLVM ABI behavior are implemented", ) } struct FunctionGen<'a> { next_reg: usize, next_block: usize, local_alloc_counts: HashMap, current_block: String, locals: HashMap, lines: Vec, file: String, layouts: &'a StructLayouts, enum_layouts: &'a EnumLayouts, string_globals: &'a StringGlobals, } #[derive(Clone)] enum LocalValue { Value(String), Ptr { ptr: String, ty: Type }, Array { ptr: String, ty: Type }, } impl FunctionGen<'_> { fn reg(&mut self) -> String { let reg = format!("%{}", self.next_reg); self.next_reg += 1; reg } fn block(&mut self, prefix: &str) -> String { let block = format!("{}.{}", prefix, self.next_block); self.next_block += 1; block } fn local_addr(&mut self, name: &str) -> String { let count = self.local_alloc_counts.entry(name.to_string()).or_insert(0); let ptr = if *count == 0 { format!("%{}.addr", name) } else { format!("%{}.addr.{}", name, count) }; *count += 1; ptr } fn label(&mut self, block: &str) { self.lines.push(format!("{}:", block)); self.current_block = block.to_string(); } fn unsupported(&self, expr: &TExpr, feature: &str) -> Diagnostic { Diagnostic::new( &self.file, "UnsupportedBackendFeature", format!("backend does not support {}", feature), ) .with_span(expr.span) .hint("keep this form in speculative examples until LLVM lowering is implemented") } fn llvm_type(&self, expr: &TExpr, ty: &Type) -> Result { llvm_type(self.layouts, self.enum_layouts, ty).ok_or_else(|| { self.unsupported(expr, &format!("LLVM representation for type `{}`", ty)) }) } fn emit_numeric_widening_conversion( &mut self, expr: &TExpr, args: &[TExpr], instruction: &str, from_ty: &str, to_ty: &str, ) -> Result { let [arg] = args else { return Err(self.unsupported(expr, "malformed numeric widening conversion")); }; let value = self.emit_expr(arg)?; let reg = self.reg(); self.lines.push(format!( "{} = {} {} {} to {}", reg, instruction, from_ty, value, to_ty )); Ok(reg) } fn emit_i64_to_i32_result_conversion( &mut self, expr: &TExpr, args: &[TExpr], ) -> Result { let [arg] = args else { return Err(self.unsupported(expr, "malformed checked i64-to-i32 conversion")); }; let value = self.emit_expr(arg)?; let above_min = self.reg(); self.lines.push(format!( "{} = icmp sge i64 {}, -2147483648", above_min, value )); let below_max = self.reg(); self.lines.push(format!( "{} = icmp sle i64 {}, 2147483647", below_max, value )); let in_range = self.reg(); self.lines.push(format!( "{} = and i1 {}, {}", in_range, above_min, below_max )); let narrowed = self.reg(); self.lines .push(format!("{} = trunc i64 {} to i32", narrowed, value)); let payload = self.reg(); self.lines.push(format!( "{} = select i1 {}, i32 {}, i32 1", payload, in_range, narrowed )); self.emit_tagged_i32_aggregate_from_values(expr, &in_range, &payload) } fn emit_f64_to_i32_result_conversion( &mut self, expr: &TExpr, args: &[TExpr], ) -> Result { let [arg] = args else { return Err(self.unsupported(expr, "malformed checked f64-to-i32 conversion")); }; let value = self.emit_expr(arg)?; let above_min = self.reg(); self.lines.push(format!( "{} = fcmp oge double {}, -2147483648.0", above_min, value )); let below_max = self.reg(); self.lines.push(format!( "{} = fcmp ole double {}, 2147483647.0", below_max, value )); let in_range = self.reg(); self.lines.push(format!( "{} = and i1 {}, {}", in_range, above_min, below_max )); let integral_block = self.block("f64.to_i32.integral"); let exponent_block = self.block("f64.to_i32.exponent"); let fraction_block = self.block("f64.to_i32.fraction"); let convert_block = self.block("f64.to_i32.convert"); let err_block = self.block("f64.to_i32.err"); let merge_block = self.block("f64.to_i32.end"); self.lines.push(format!( "br i1 {}, label %{}, label %{}", in_range, integral_block, err_block )); self.label(&integral_block); let bits = self.reg(); self.lines .push(format!("{} = bitcast double {} to i64", bits, value)); let abs_bits = self.reg(); self.lines.push(format!( "{} = and i64 {}, 9223372036854775807", abs_bits, bits )); let is_zero = self.reg(); self.lines .push(format!("{} = icmp eq i64 {}, 0", is_zero, abs_bits)); self.lines.push(format!( "br i1 {}, label %{}, label %{}", is_zero, convert_block, exponent_block )); self.label(&exponent_block); let exponent = self.reg(); self.lines .push(format!("{} = lshr i64 {}, 52", exponent, abs_bits)); let exponent_ge_bias = self.reg(); self.lines.push(format!( "{} = icmp uge i64 {}, 1023", exponent_ge_bias, exponent )); self.lines.push(format!( "br i1 {}, label %{}, label %{}", exponent_ge_bias, fraction_block, err_block )); self.label(&fraction_block); let unbiased_exponent = self.reg(); self.lines.push(format!( "{} = sub i64 {}, 1023", unbiased_exponent, exponent )); let fractional_width = self.reg(); self.lines.push(format!( "{} = sub i64 52, {}", fractional_width, unbiased_exponent )); let fractional_one = self.reg(); self.lines.push(format!( "{} = shl i64 1, {}", fractional_one, fractional_width )); let fractional_mask = self.reg(); self.lines.push(format!( "{} = sub i64 {}, 1", fractional_mask, fractional_one )); let fractional_bits = self.reg(); self.lines.push(format!( "{} = and i64 {}, {}", fractional_bits, abs_bits, fractional_mask )); let integral = self.reg(); self.lines .push(format!("{} = icmp eq i64 {}, 0", integral, fractional_bits)); self.lines.push(format!( "br i1 {}, label %{}, label %{}", integral, convert_block, err_block )); self.label(&convert_block); let narrowed = self.reg(); self.lines .push(format!("{} = fptosi double {} to i32", narrowed, value)); let ok_result = self.emit_tagged_i32_aggregate_from_values(expr, "true", &narrowed)?; let ok_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", merge_block)); self.label(&err_block); let err_result = self.emit_tagged_i32_aggregate_from_values(expr, "false", "1")?; let err_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", merge_block)); self.label(&merge_block); let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let result = self.reg(); self.lines.push(format!( "{} = phi {} [ {}, %{} ], [ {}, %{} ]", result, aggregate_ty, ok_result, ok_pred, err_result, err_pred )); Ok(result) } fn emit_f64_to_i64_result_conversion( &mut self, expr: &TExpr, args: &[TExpr], ) -> Result { let [arg] = args else { return Err(self.unsupported(expr, "malformed checked f64-to-i64 conversion")); }; let value = self.emit_expr(arg)?; let above_min = self.reg(); self.lines.push(format!( "{} = fcmp oge double {}, -9223372036854775808.0", above_min, value )); let below_max = self.reg(); self.lines.push(format!( "{} = fcmp olt double {}, 9223372036854775808.0", below_max, value )); let in_range = self.reg(); self.lines.push(format!( "{} = and i1 {}, {}", in_range, above_min, below_max )); let integral_block = self.block("f64.to_i64.integral"); let exponent_block = self.block("f64.to_i64.exponent"); let mantissa_block = self.block("f64.to_i64.mantissa"); let fraction_block = self.block("f64.to_i64.fraction"); let convert_block = self.block("f64.to_i64.convert"); let err_block = self.block("f64.to_i64.err"); let merge_block = self.block("f64.to_i64.end"); self.lines.push(format!( "br i1 {}, label %{}, label %{}", in_range, integral_block, err_block )); self.label(&integral_block); let bits = self.reg(); self.lines .push(format!("{} = bitcast double {} to i64", bits, value)); let abs_bits = self.reg(); self.lines.push(format!( "{} = and i64 {}, 9223372036854775807", abs_bits, bits )); let is_zero = self.reg(); self.lines .push(format!("{} = icmp eq i64 {}, 0", is_zero, abs_bits)); self.lines.push(format!( "br i1 {}, label %{}, label %{}", is_zero, convert_block, exponent_block )); self.label(&exponent_block); let exponent = self.reg(); self.lines .push(format!("{} = lshr i64 {}, 52", exponent, abs_bits)); let exponent_ge_bias = self.reg(); self.lines.push(format!( "{} = icmp uge i64 {}, 1023", exponent_ge_bias, exponent )); self.lines.push(format!( "br i1 {}, label %{}, label %{}", exponent_ge_bias, mantissa_block, err_block )); self.label(&mantissa_block); let unbiased_exponent = self.reg(); self.lines.push(format!( "{} = sub i64 {}, 1023", unbiased_exponent, exponent )); let no_fractional_bits = self.reg(); self.lines.push(format!( "{} = icmp uge i64 {}, 52", no_fractional_bits, unbiased_exponent )); self.lines.push(format!( "br i1 {}, label %{}, label %{}", no_fractional_bits, convert_block, fraction_block )); self.label(&fraction_block); let fractional_width = self.reg(); self.lines.push(format!( "{} = sub i64 52, {}", fractional_width, unbiased_exponent )); let fractional_one = self.reg(); self.lines.push(format!( "{} = shl i64 1, {}", fractional_one, fractional_width )); let fractional_mask = self.reg(); self.lines.push(format!( "{} = sub i64 {}, 1", fractional_mask, fractional_one )); let fractional_bits = self.reg(); self.lines.push(format!( "{} = and i64 {}, {}", fractional_bits, abs_bits, fractional_mask )); let integral = self.reg(); self.lines .push(format!("{} = icmp eq i64 {}, 0", integral, fractional_bits)); self.lines.push(format!( "br i1 {}, label %{}, label %{}", integral, convert_block, err_block )); self.label(&convert_block); let narrowed = self.reg(); self.lines .push(format!("{} = fptosi double {} to i64", narrowed, value)); let ok_result = self.emit_i64_result_aggregate(expr, "true", &narrowed, "0")?; let ok_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", merge_block)); self.label(&err_block); let err_result = self.emit_i64_result_aggregate(expr, "false", "0", "1")?; let err_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", merge_block)); self.label(&merge_block); let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let result = self.reg(); self.lines.push(format!( "{} = phi {} [ {}, %{} ], [ {}, %{} ]", result, aggregate_ty, ok_result, ok_pred, err_result, err_pred )); Ok(result) } fn is_payload_enum_type(&self, ty: &Type) -> bool { matches!( ty, Type::Named(name) if self .enum_layouts .get(name) .and_then(|layout| layout.payload_ty.as_ref()) .is_some() ) } fn enum_payload_type<'b>(&'b self, ty: &Type) -> Option<&'b Type> { match ty { Type::Named(name) => self .enum_layouts .get(name) .and_then(|layout| layout.payload_ty.as_ref()), _ => None, } } fn emit_equality_for_type( &mut self, ty: &Type, left: &str, right: &str, ) -> Result { let reg = self.reg(); match ty { Type::String => self.lines.push(format!( "{} = call i1 @__glagol_string_eq(ptr {}, ptr {})", reg, left, right )), Type::F64 => self .lines .push(format!("{} = fcmp oeq double {}, {}", reg, left, right)), Type::Bool => self .lines .push(format!("{} = icmp eq i1 {}, {}", reg, left, right)), Type::U64 => self .lines .push(format!("{} = icmp eq i64 {}, {}", reg, left, right)), Type::I64 => self .lines .push(format!("{} = icmp eq i64 {}, {}", reg, left, right)), Type::U32 => self .lines .push(format!("{} = icmp eq i32 {}, {}", reg, left, right)), Type::I32 => self .lines .push(format!("{} = icmp eq i32 {}, {}", reg, left, right)), _ => { return Err(Diagnostic::new( &self.file, "UnsupportedBackendFeature", format!( "backend does not support equality for enum payload type `{}`", ty ), )) } } Ok(reg) } fn enum_payload_zero_value(&self, ty: &Type) -> Result { zero_value_for_type(ty).ok_or_else(|| { Diagnostic::new( &self.file, "UnsupportedBackendFeature", format!("backend does not support enum payload type `{}`", ty), ) }) } fn enum_payload_llvm_type(&self, ty: &Type) -> Result { llvm_type(self.layouts, self.enum_layouts, ty).ok_or_else(|| { Diagnostic::new( &self.file, "UnsupportedBackendFeature", format!("backend does not support enum payload type `{}`", ty), ) }) } fn emit_payload_enum_equality( &mut self, reg: &str, ty: &Type, left_aggregate_ty: &str, left_value: &str, right_value: &str, ) -> Result<(), Diagnostic> { let left_tag = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 0", left_tag, left_aggregate_ty, left_value )); let right_tag = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 0", right_tag, left_aggregate_ty, right_value )); let tags_equal = self.reg(); self.lines.push(format!( "{} = icmp eq i32 {}, {}", tags_equal, left_tag, right_tag )); let left_payload = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 1", left_payload, left_aggregate_ty, left_value )); let right_payload = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 1", right_payload, left_aggregate_ty, right_value )); let payloads_equal = self.emit_equality_for_type(ty, &left_payload, &right_payload)?; self.lines.push(format!( "{} = and i1 {}, {}", reg, tags_equal, payloads_equal )); Ok(()) } fn emit_expr(&mut self, expr: &TExpr) -> Result { match &expr.kind { TExprKind::Int(value) => Ok(value.to_string()), TExprKind::Int64(value) => Ok(value.to_string()), TExprKind::UInt32(value) => Ok(value.to_string()), TExprKind::UInt64(value) => Ok(value.to_string()), TExprKind::Float(value) => Ok(format_f64_literal(*value)), TExprKind::Bool(value) => Ok(if *value { "1".to_string() } else { "0".to_string() }), TExprKind::String(value) => self .string_globals .global_name(value) .map(|name| format!("@{}", name)) .ok_or_else(|| self.unsupported(expr, "string literal storage")), TExprKind::EnumVariant { enum_name, discriminant, payload, .. } => self.emit_enum_variant(expr, enum_name, *discriminant, payload.as_deref()), TExprKind::StructInit { name, fields } => self.emit_struct_init(expr, name, fields), TExprKind::ArrayInit { elements } => self.emit_array_init(expr, elements), TExprKind::OptionSome { value } => self.emit_option_aggregate(expr, true, Some(value)), TExprKind::OptionNone => self.emit_option_aggregate(expr, false, None), TExprKind::ResultOk { value } => self.emit_result_aggregate(expr, true, value), TExprKind::ResultErr { value } => self.emit_result_aggregate(expr, false, value), TExprKind::OptionIsSome { value } => self.emit_tag_observer(expr, value, false), TExprKind::OptionIsNone { value } => self.emit_tag_observer(expr, value, true), TExprKind::OptionUnwrapSome { value } => { self.emit_tag_checked_payload_access(expr, value, true, "__glagol_unwrap_some_trap") } TExprKind::ResultIsOk { value, .. } => self.emit_tag_observer(expr, value, false), TExprKind::ResultIsErr { value, .. } => self.emit_tag_observer(expr, value, true), TExprKind::ResultUnwrapOk { value, .. } => { self.emit_tag_checked_payload_access(expr, value, true, "__glagol_unwrap_ok_trap") } TExprKind::ResultUnwrapErr { value, .. } => { self.emit_tag_checked_payload_access(expr, value, false, "__glagol_unwrap_err_trap") } TExprKind::FieldAccess { value, field } => self.emit_field_access(expr, value, field), TExprKind::Index { array, index } => self.emit_index(expr, array, index), TExprKind::Var(name) => match self.locals.get(name).cloned() { Some(LocalValue::Value(value)) => Ok(value), Some(LocalValue::Ptr { ptr, ty }) => { let reg = self.reg(); let llvm_ty = self.llvm_type(expr, &ty)?; self.lines .push(format!("{} = load {}, ptr {}", reg, llvm_ty, ptr)); Ok(reg) } Some(LocalValue::Array { ptr, ty }) => { let reg = self.reg(); let llvm_ty = self.llvm_type(expr, &ty)?; self.lines .push(format!("{} = load {}, ptr {}", reg, llvm_ty, ptr)); Ok(reg) } None => Ok(format!("%{}", name)), }, TExprKind::Local { mutable, name, initializer, } => match &initializer.ty { Type::I32 | Type::Bool | Type::I64 | Type::U32 | Type::U64 | Type::F64 if !*mutable => { self.emit_ssa_local(name, initializer) } Type::I32 | Type::Bool | Type::I64 | Type::U32 | Type::U64 | Type::F64 => { self.emit_value_local(name, initializer) } Type::String => self.emit_value_local(name, initializer), Type::Vec(inner) if (**inner == Type::I32 || **inner == Type::I64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String) => { self.emit_value_local(name, initializer) } Type::Option(inner) if (**inner == Type::I32 || **inner == Type::I64 || **inner == Type::U32 || **inner == Type::U64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String) => { self.emit_value_local(name, initializer) } Type::Result(ok, err) if result_type_supported(ok, err) => { self.emit_value_local(name, initializer) } Type::Named(struct_name) if self.layouts.contains_key(struct_name) => { self.emit_value_local(name, initializer) } Type::Named(_) => self.emit_value_local(name, initializer), Type::Array(_, _) if !*mutable => self.emit_array_local(expr, name, initializer), _ => Err(self.unsupported(expr, "unsupported local variables")), }, TExprKind::Set { name, expr: value } => { let rhs = self.emit_expr(value)?; let Some(LocalValue::Ptr { ptr, ty }) = self.locals.get(name).cloned() else { return Err(self.unsupported(expr, "assignment to non-local values")); }; let llvm_ty = self.llvm_type(expr, &ty)?; self.lines .push(format!("store {} {}, ptr {}", llvm_ty, rhs, ptr)); Ok("0".to_string()) } TExprKind::Binary { op, left, right } => { let l = self.emit_expr(left)?; let r = self.emit_expr(right)?; let reg = self.reg(); match op { BinaryOp::Add if left.ty == Type::F64 => self .lines .push(format!("{} = fadd double {}, {}", reg, l, r)), BinaryOp::Sub if left.ty == Type::F64 => self .lines .push(format!("{} = fsub double {}, {}", reg, l, r)), BinaryOp::Mul if left.ty == Type::F64 => self .lines .push(format!("{} = fmul double {}, {}", reg, l, r)), BinaryOp::Div if left.ty == Type::F64 => self .lines .push(format!("{} = fdiv double {}, {}", reg, l, r)), BinaryOp::Rem if left.ty == Type::F64 => { return Err(self.unsupported(expr, "f64 remainder")) } BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor if left.ty == Type::F64 => { return Err(self.unsupported(expr, "f64 bitwise operation")) } BinaryOp::Add if left.ty == Type::I64 => { self.lines.push(format!("{} = add i64 {}, {}", reg, l, r)) } BinaryOp::Add if left.ty == Type::U64 => { self.lines.push(format!("{} = add i64 {}, {}", reg, l, r)) } BinaryOp::Sub if left.ty == Type::I64 => { self.lines.push(format!("{} = sub i64 {}, {}", reg, l, r)) } BinaryOp::Sub if left.ty == Type::U64 => { self.lines.push(format!("{} = sub i64 {}, {}", reg, l, r)) } BinaryOp::Mul if left.ty == Type::I64 => { self.lines.push(format!("{} = mul i64 {}, {}", reg, l, r)) } BinaryOp::Mul if left.ty == Type::U64 => { self.lines.push(format!("{} = mul i64 {}, {}", reg, l, r)) } BinaryOp::Div if left.ty == Type::I64 => { self.lines.push(format!("{} = sdiv i64 {}, {}", reg, l, r)) } BinaryOp::Div if left.ty == Type::U64 => { self.lines.push(format!("{} = udiv i64 {}, {}", reg, l, r)) } BinaryOp::Rem if left.ty == Type::I64 => { self.lines.push(format!("{} = srem i64 {}, {}", reg, l, r)) } BinaryOp::Rem if left.ty == Type::U64 => { self.lines.push(format!("{} = urem i64 {}, {}", reg, l, r)) } BinaryOp::BitAnd if left.ty == Type::I64 => { self.lines.push(format!("{} = and i64 {}, {}", reg, l, r)) } BinaryOp::BitAnd if left.ty == Type::U64 => { self.lines.push(format!("{} = and i64 {}, {}", reg, l, r)) } BinaryOp::BitOr if left.ty == Type::I64 => { self.lines.push(format!("{} = or i64 {}, {}", reg, l, r)) } BinaryOp::BitOr if left.ty == Type::U64 => { self.lines.push(format!("{} = or i64 {}, {}", reg, l, r)) } BinaryOp::BitXor if left.ty == Type::I64 => { self.lines.push(format!("{} = xor i64 {}, {}", reg, l, r)) } BinaryOp::BitXor if left.ty == Type::U64 => { self.lines.push(format!("{} = xor i64 {}, {}", reg, l, r)) } BinaryOp::Add => self.lines.push(format!("{} = add i32 {}, {}", reg, l, r)), BinaryOp::Sub => self.lines.push(format!("{} = sub i32 {}, {}", reg, l, r)), BinaryOp::Mul => self.lines.push(format!("{} = mul i32 {}, {}", reg, l, r)), BinaryOp::Div if left.ty == Type::U32 => { self.lines.push(format!("{} = udiv i32 {}, {}", reg, l, r)) } BinaryOp::Div => self.lines.push(format!("{} = sdiv i32 {}, {}", reg, l, r)), BinaryOp::Rem if left.ty == Type::U32 => { self.lines.push(format!("{} = urem i32 {}, {}", reg, l, r)) } BinaryOp::Rem => self.lines.push(format!("{} = srem i32 {}, {}", reg, l, r)), BinaryOp::BitAnd => self.lines.push(format!("{} = and i32 {}, {}", reg, l, r)), BinaryOp::BitOr => self.lines.push(format!("{} = or i32 {}, {}", reg, l, r)), BinaryOp::BitXor => self.lines.push(format!("{} = xor i32 {}, {}", reg, l, r)), BinaryOp::Eq if left.ty == Type::String && right.ty == Type::String => { self.lines.push(format!( "{} = call i1 @__glagol_string_eq(ptr {}, ptr {})", reg, l, r )) } BinaryOp::Eq if vec_eq_runtime_symbol(&left.ty).is_some() && left.ty == right.ty => { let symbol = vec_eq_runtime_symbol(&left.ty).expect("vector equality symbol"); self.lines.push(format!( "{} = call i1 @{}(ptr {}, ptr {})", reg, symbol, l, r )) } BinaryOp::Eq if self.is_payload_enum_type(&left.ty) => { let left_ty = self.llvm_type(left, &left.ty)?; let payload_ty = self .enum_payload_type(&left.ty) .ok_or_else(|| self.unsupported(expr, "enum payload metadata"))? .clone(); self.emit_payload_enum_equality(®, &payload_ty, &left_ty, &l, &r)?; } BinaryOp::Eq if left.ty == Type::F64 => self .lines .push(format!("{} = fcmp oeq double {}, {}", reg, l, r)), BinaryOp::Eq if left.ty == Type::Bool => self .lines .push(format!("{} = icmp eq i1 {}, {}", reg, l, r)), BinaryOp::Eq if left.ty == Type::I64 => self .lines .push(format!("{} = icmp eq i64 {}, {}", reg, l, r)), BinaryOp::Eq if left.ty == Type::U64 => self .lines .push(format!("{} = icmp eq i64 {}, {}", reg, l, r)), BinaryOp::Eq => self .lines .push(format!("{} = icmp eq i32 {}, {}", reg, l, r)), BinaryOp::Lt if left.ty == Type::F64 => self .lines .push(format!("{} = fcmp olt double {}, {}", reg, l, r)), BinaryOp::Lt if left.ty == Type::I64 => self .lines .push(format!("{} = icmp slt i64 {}, {}", reg, l, r)), BinaryOp::Lt if left.ty == Type::U64 => self .lines .push(format!("{} = icmp ult i64 {}, {}", reg, l, r)), BinaryOp::Lt if left.ty == Type::U32 => self .lines .push(format!("{} = icmp ult i32 {}, {}", reg, l, r)), BinaryOp::Lt => self .lines .push(format!("{} = icmp slt i32 {}, {}", reg, l, r)), BinaryOp::Gt if left.ty == Type::F64 => self .lines .push(format!("{} = fcmp ogt double {}, {}", reg, l, r)), BinaryOp::Gt if left.ty == Type::I64 => self .lines .push(format!("{} = icmp sgt i64 {}, {}", reg, l, r)), BinaryOp::Gt if left.ty == Type::U64 => self .lines .push(format!("{} = icmp ugt i64 {}, {}", reg, l, r)), BinaryOp::Gt if left.ty == Type::U32 => self .lines .push(format!("{} = icmp ugt i32 {}, {}", reg, l, r)), BinaryOp::Gt => self .lines .push(format!("{} = icmp sgt i32 {}, {}", reg, l, r)), BinaryOp::Le if left.ty == Type::F64 => self .lines .push(format!("{} = fcmp ole double {}, {}", reg, l, r)), BinaryOp::Le if left.ty == Type::I64 => self .lines .push(format!("{} = icmp sle i64 {}, {}", reg, l, r)), BinaryOp::Le if left.ty == Type::U64 => self .lines .push(format!("{} = icmp ule i64 {}, {}", reg, l, r)), BinaryOp::Le if left.ty == Type::U32 => self .lines .push(format!("{} = icmp ule i32 {}, {}", reg, l, r)), BinaryOp::Le => self .lines .push(format!("{} = icmp sle i32 {}, {}", reg, l, r)), BinaryOp::Ge if left.ty == Type::F64 => self .lines .push(format!("{} = fcmp oge double {}, {}", reg, l, r)), BinaryOp::Ge if left.ty == Type::I64 => self .lines .push(format!("{} = icmp sge i64 {}, {}", reg, l, r)), BinaryOp::Ge if left.ty == Type::U64 => self .lines .push(format!("{} = icmp uge i64 {}, {}", reg, l, r)), BinaryOp::Ge if left.ty == Type::U32 => self .lines .push(format!("{} = icmp uge i32 {}, {}", reg, l, r)), BinaryOp::Ge => self .lines .push(format!("{} = icmp sge i32 {}, {}", reg, l, r)), } Ok(reg) } TExprKind::Call { name, args } => { match name.as_str() { "std.num.i32_to_i64" => { return self .emit_numeric_widening_conversion(expr, args, "sext", "i32", "i64"); } "std.num.i32_to_f64" => { return self.emit_numeric_widening_conversion( expr, args, "sitofp", "i32", "double", ); } "std.num.i64_to_f64" => { return self.emit_numeric_widening_conversion( expr, args, "sitofp", "i64", "double", ); } "std.num.i64_to_i32_result" => { return self.emit_i64_to_i32_result_conversion(expr, args); } "std.num.f64_to_i32_result" => { return self.emit_f64_to_i32_result_conversion(expr, args); } "std.num.f64_to_i64_result" => { return self.emit_f64_to_i64_result_conversion(expr, args); } _ => {} } let mut arg_values = Vec::new(); for arg in args { let value = self.emit_expr(arg)?; let arg_ty = self.llvm_type(arg, &arg.ty)?; arg_values.push(format!("{} {}", arg_ty, value)); } let arg_values = arg_values.join(", "); let callee = std_runtime::runtime_symbol(name).unwrap_or(name); if matches!( callee, "__glagol_process_arg_result" | "__glagol_env_get_result" | "__glagol_fs_read_text_result" | "__glagol_fs_read_open_text_result" | "__glagol_net_tcp_read_all_result" | "__glagol_io_read_stdin_result" ) { return self.emit_string_result_host_call(expr, callee, &arg_values); } if callee == "__glagol_fs_write_text_result" || callee == "__glagol_fs_remove_file_result" || callee == "__glagol_fs_create_dir_result" || callee == "__glagol_fs_close_result" || callee == "__glagol_net_tcp_write_text_result" || callee == "__glagol_net_tcp_close_result" { return self.emit_i32_result_status_call(expr, callee, &arg_values); } if callee == "__glagol_fs_open_text_read_result" || callee == "__glagol_net_tcp_connect_loopback_result" || callee == "__glagol_net_tcp_listen_loopback_result" || callee == "__glagol_net_tcp_bound_port_result" || callee == "__glagol_net_tcp_accept_result" || callee == "__glagol_string_parse_i32_result" { return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values); } if callee == "__glagol_string_parse_u32_result" { return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values); } if callee == "__glagol_string_parse_i64_result" { return self.emit_i64_result_out_param_call(expr, callee, &arg_values); } if callee == "__glagol_string_parse_u64_result" { return self.emit_i64_result_out_param_call(expr, callee, &arg_values); } if callee == "__glagol_string_parse_f64_result" { return self.emit_f64_result_out_param_call(expr, callee, &arg_values); } if callee == "__glagol_string_parse_bool_result" { return self.emit_bool_result_out_param_call(expr, callee, &arg_values); } if expr.ty == Type::Unit { self.lines .push(format!("call void @{}({})", callee, arg_values)); Ok("0".to_string()) } else { let reg = self.reg(); let result_ty = self.llvm_type(expr, &expr.ty)?; self.lines.push(format!( "{} = call {} @{}({})", reg, result_ty, callee, arg_values )); Ok(reg) } } TExprKind::If { condition, then_expr, else_expr, } => { let then_block = self.block("if.then"); let else_block = self.block("if.else"); let end_block = self.block("if.end"); let cond = self.emit_expr(condition)?; self.lines.push(format!( "br i1 {}, label %{}, label %{}", cond, then_block, else_block )); self.label(&then_block); let then_value = self.emit_expr(then_expr)?; let then_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", end_block)); self.label(&else_block); let else_value = self.emit_expr(else_expr)?; let else_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", end_block)); self.label(&end_block); if expr.ty == Type::Unit { Ok("0".to_string()) } else { let reg = self.reg(); let phi_ty = self.llvm_type(expr, &expr.ty)?; self.lines.push(format!( "{} = phi {} [ {}, %{} ], [ {}, %{} ]", reg, phi_ty, then_value, then_pred, else_value, else_pred, )); Ok(reg) } } TExprKind::Match { subject, arms } => self.emit_match(expr, subject, arms), TExprKind::While { condition, body } => { let cond_block = self.block("while.cond"); let body_block = self.block("while.body"); let end_block = self.block("while.end"); self.lines.push(format!("br label %{}", cond_block)); self.label(&cond_block); let cond = self.emit_expr(condition)?; self.lines.push(format!( "br i1 {}, label %{}, label %{}", cond, body_block, end_block )); self.label(&body_block); for expr in body { self.emit_expr(expr)?; } self.lines.push(format!("br label %{}", cond_block)); self.label(&end_block); Ok("0".to_string()) } TExprKind::Unsafe { body } => { let saved_locals = self.locals.clone(); let mut value = "0".to_string(); for expr in body { value = self.emit_expr(expr)?; } self.locals = saved_locals; Ok(value) } } } fn emit_option_aggregate( &mut self, expr: &TExpr, tag: bool, payload: Option<&TExpr>, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tag_value = if tag { "1" } else { "0" }; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let payload_value = match payload { Some(payload) => self.emit_expr(payload)?, None => match &expr.ty { Type::Option(inner) => zero_value_for_type(inner).ok_or_else(|| { self.unsupported(expr, "option constructor for unsupported payload type") })?, _ => return Err(self.unsupported(expr, "option constructor for non-option type")), }, }; let payload_ty = match &expr.ty { Type::Option(inner) if **inner == Type::I32 => "i32", Type::Option(inner) if **inner == Type::I64 => "i64", Type::Option(inner) if **inner == Type::U32 => "i32", Type::Option(inner) if **inner == Type::U64 => "i64", Type::Option(inner) if **inner == Type::F64 => "double", Type::Option(inner) if **inner == Type::Bool => "i1", Type::Option(inner) if **inner == Type::String => "ptr", _ => return Err(self.unsupported(expr, "option constructor for non-option type")), }; let with_payload = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, {} {}, 1", with_payload, aggregate_ty, tagged, payload_ty, payload_value )); Ok(with_payload) } fn emit_tagged_i32_aggregate( &mut self, expr: &TExpr, tag: bool, payload: Option<&TExpr>, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tag_value = if tag { "1" } else { "0" }; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let payload_value = match payload { Some(payload) => self.emit_expr(payload)?, None => "0".to_string(), }; let with_payload = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i32 {}, 1", with_payload, aggregate_ty, tagged, payload_value )); Ok(with_payload) } fn emit_enum_variant( &mut self, expr: &TExpr, enum_name: &str, discriminant: i32, payload: Option<&TExpr>, ) -> Result { let Some(layout) = self.enum_layouts.get(enum_name) else { return Ok(discriminant.to_string()); }; let Some(payload_ty) = layout.payload_ty.as_ref() else { return Ok(discriminant.to_string()); }; let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i32 {}, 0", tagged, aggregate_ty, discriminant )); let payload_value = match payload { Some(payload) => self.emit_expr(payload)?, None => self.enum_payload_zero_value(payload_ty)?, }; let payload_llvm_ty = self.enum_payload_llvm_type(payload_ty)?; let with_payload = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, {} {}, 1", with_payload, aggregate_ty, tagged, payload_llvm_ty, payload_value )); Ok(with_payload) } fn emit_result_aggregate( &mut self, expr: &TExpr, is_ok: bool, payload: &TExpr, ) -> Result { match &expr.ty { Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => { self.emit_tagged_i32_aggregate(expr, is_ok, Some(payload)) } Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => { let payload_value = self.emit_expr(payload)?; if is_ok { self.emit_i64_result_aggregate(expr, "1", &payload_value, "0") } else { self.emit_i64_result_aggregate(expr, "0", "0", &payload_value) } } Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => { self.emit_tagged_i32_aggregate(expr, is_ok, Some(payload)) } Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => { let payload_value = self.emit_expr(payload)?; if is_ok { self.emit_i64_result_aggregate(expr, "1", &payload_value, "0") } else { self.emit_i64_result_aggregate(expr, "0", "0", &payload_value) } } Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => { let payload_value = self.emit_expr(payload)?; if is_ok { self.emit_f64_result_aggregate(expr, "1", &payload_value, "0") } else { self.emit_f64_result_aggregate(expr, "0", "0.0", &payload_value) } } Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => { let payload_value = self.emit_expr(payload)?; if is_ok { self.emit_bool_result_aggregate(expr, "1", &payload_value, "0") } else { self.emit_bool_result_aggregate(expr, "0", "0", &payload_value) } } Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => { let payload_value = self.emit_expr(payload)?; if is_ok { self.emit_string_result_aggregate(expr, "1", &payload_value, "0") } else { self.emit_string_result_aggregate(expr, "0", "null", &payload_value) } } Type::Result(_, _) => Err(self.unsupported(expr, "unsupported result payload types")), _ => Err(self.unsupported(expr, "result constructor for non-result type")), } } fn emit_string_result_aggregate( &mut self, expr: &TExpr, tag_value: &str, ok_value: &str, err_value: &str, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let with_ok = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, ptr {}, 1", with_ok, aggregate_ty, tagged, ok_value )); let with_err = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i32 {}, 2", with_err, aggregate_ty, with_ok, err_value )); Ok(with_err) } fn emit_i64_result_aggregate( &mut self, expr: &TExpr, tag_value: &str, ok_value: &str, err_value: &str, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let with_ok = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i64 {}, 1", with_ok, aggregate_ty, tagged, ok_value )); let with_err = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i32 {}, 2", with_err, aggregate_ty, with_ok, err_value )); Ok(with_err) } fn emit_f64_result_aggregate( &mut self, expr: &TExpr, tag_value: &str, ok_value: &str, err_value: &str, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let with_ok = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, double {}, 1", with_ok, aggregate_ty, tagged, ok_value )); let with_err = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i32 {}, 2", with_err, aggregate_ty, with_ok, err_value )); Ok(with_err) } fn emit_bool_result_aggregate( &mut self, expr: &TExpr, tag_value: &str, ok_value: &str, err_value: &str, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let with_ok = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i1 {}, 1", with_ok, aggregate_ty, tagged, ok_value )); let with_err = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i32 {}, 2", with_err, aggregate_ty, with_ok, err_value )); Ok(with_err) } fn emit_string_result_host_call( &mut self, expr: &TExpr, callee: &str, arg_values: &str, ) -> Result { let payload = self.reg(); self.lines.push(format!( "{} = call ptr @{}({})", payload, callee, arg_values )); let is_ok = self.reg(); self.lines .push(format!("{} = icmp ne ptr {}, null", is_ok, payload)); let err_code = self.reg(); self.lines .push(format!("{} = select i1 {}, i32 0, i32 1", err_code, is_ok)); self.emit_string_result_aggregate(expr, &is_ok, &payload, &err_code) } fn emit_i32_result_status_call( &mut self, expr: &TExpr, callee: &str, arg_values: &str, ) -> Result { let status = self.reg(); self.lines .push(format!("{} = call i32 @{}({})", status, callee, arg_values)); let is_ok = self.reg(); self.lines .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); self.emit_tagged_i32_aggregate_from_values(expr, &is_ok, &status) } fn emit_i32_result_encoded_i64_call( &mut self, expr: &TExpr, callee: &str, arg_values: &str, ) -> Result { let encoded = self.reg(); self.lines.push(format!( "{} = call i64 @{}({})", encoded, callee, arg_values )); let status64 = self.reg(); self.lines .push(format!("{} = lshr i64 {}, 32", status64, encoded)); let status = self.reg(); self.lines .push(format!("{} = trunc i64 {} to i32", status, status64)); let is_ok = self.reg(); self.lines .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); let payload = self.reg(); self.lines .push(format!("{} = trunc i64 {} to i32", payload, encoded)); self.emit_tagged_i32_aggregate_from_values(expr, &is_ok, &payload) } fn emit_i64_result_out_param_call( &mut self, expr: &TExpr, callee: &str, arg_values: &str, ) -> Result { let out_ptr = self.reg(); self.lines.push(format!("{} = alloca i64", out_ptr)); self.lines.push(format!("store i64 0, ptr {}", out_ptr)); let status = self.reg(); self.lines.push(format!( "{} = call i32 @{}({}, ptr {})", status, callee, arg_values, out_ptr )); let is_ok = self.reg(); self.lines .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); let ok_payload = self.reg(); self.lines .push(format!("{} = load i64, ptr {}", ok_payload, out_ptr)); let err_payload = self.reg(); self.lines.push(format!( "{} = select i1 {}, i32 0, i32 1", err_payload, is_ok )); self.emit_i64_result_aggregate(expr, &is_ok, &ok_payload, &err_payload) } fn emit_f64_result_out_param_call( &mut self, expr: &TExpr, callee: &str, arg_values: &str, ) -> Result { let out_ptr = self.reg(); self.lines.push(format!("{} = alloca double", out_ptr)); self.lines .push(format!("store double 0.0, ptr {}", out_ptr)); let status = self.reg(); self.lines.push(format!( "{} = call i32 @{}({}, ptr {})", status, callee, arg_values, out_ptr )); let is_ok = self.reg(); self.lines .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); let ok_payload = self.reg(); self.lines .push(format!("{} = load double, ptr {}", ok_payload, out_ptr)); let err_payload = self.reg(); self.lines.push(format!( "{} = select i1 {}, i32 0, i32 1", err_payload, is_ok )); self.emit_f64_result_aggregate(expr, &is_ok, &ok_payload, &err_payload) } fn emit_bool_result_out_param_call( &mut self, expr: &TExpr, callee: &str, arg_values: &str, ) -> Result { let out_ptr = self.reg(); self.lines.push(format!("{} = alloca i1", out_ptr)); self.lines.push(format!("store i1 0, ptr {}", out_ptr)); let status = self.reg(); self.lines.push(format!( "{} = call i32 @{}({}, ptr {})", status, callee, arg_values, out_ptr )); let is_ok = self.reg(); self.lines .push(format!("{} = icmp eq i32 {}, 0", is_ok, status)); let ok_payload = self.reg(); self.lines .push(format!("{} = load i1, ptr {}", ok_payload, out_ptr)); let err_payload = self.reg(); self.lines.push(format!( "{} = select i1 {}, i32 0, i32 1", err_payload, is_ok )); self.emit_bool_result_aggregate(expr, &is_ok, &ok_payload, &err_payload) } fn emit_tagged_i32_aggregate_from_values( &mut self, expr: &TExpr, tag_value: &str, payload_value: &str, ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let tagged = self.reg(); self.lines.push(format!( "{} = insertvalue {} undef, i1 {}, 0", tagged, aggregate_ty, tag_value )); let with_payload = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, i32 {}, 1", with_payload, aggregate_ty, tagged, payload_value )); Ok(with_payload) } fn emit_tag_observer( &mut self, _expr: &TExpr, value: &TExpr, invert: bool, ) -> Result { let aggregate = self.emit_expr(value)?; let aggregate_ty = self.llvm_type(value, &value.ty)?; let tag = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 0", tag, aggregate_ty, aggregate )); if invert { let inverted = self.reg(); self.lines .push(format!("{} = xor i1 {}, true", inverted, tag)); Ok(inverted) } else { Ok(tag) } } fn emit_tag_checked_payload_access( &mut self, _expr: &TExpr, value: &TExpr, expected_tag: bool, trap_fn: &str, ) -> Result { let aggregate = self.emit_expr(value)?; let aggregate_ty = self.llvm_type(value, &value.ty)?; let tag = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 0", tag, aggregate_ty, aggregate )); let valid = if expected_tag { tag } else { let valid = self.reg(); self.lines .push(format!("{} = icmp eq i1 {}, 0", valid, tag)); valid }; let ok_block = self.block("unwrap.ok"); let trap_block = self.block("unwrap.trap"); self.lines.push(format!( "br i1 {}, label %{}, label %{}", valid, ok_block, trap_block )); self.label(&trap_block); self.lines.push(format!("call void @{}()", trap_fn)); self.lines.push("unreachable".to_string()); self.label(&ok_block); let payload_index = result_payload_index(&value.ty, expected_tag) .ok_or_else(|| self.unsupported(value, "unsupported result payload access"))?; let payload = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, {}", payload, aggregate_ty, aggregate, payload_index )); Ok(payload) } fn emit_match( &mut self, expr: &TExpr, subject: &TExpr, arms: &[TMatchArm], ) -> Result { if matches!(subject.ty, Type::Named(_)) { return self.emit_enum_match(expr, subject, arms); } let subject_value = self.emit_expr(subject)?; let aggregate_ty = self.llvm_type(subject, &subject.ty)?; let tag = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 0", tag, aggregate_ty, subject_value )); let (truthy_kind, falsy_kind, truthy_prefix, falsy_prefix) = match &subject.ty { Type::Option(inner) if **inner == Type::I32 || **inner == Type::I64 || **inner == Type::U32 || **inner == Type::U64 || **inner == Type::F64 || **inner == Type::Bool || **inner == Type::String => { ( MatchPatternKind::Some, MatchPatternKind::None, "match.some", "match.none", ) } Type::Result(ok, err) if result_type_supported(ok, err) => ( MatchPatternKind::Ok, MatchPatternKind::Err, "match.ok", "match.err", ), _ => { return Err(self.unsupported( expr, "match subject types outside concrete option/result families", )) } }; let truthy_arm = arms .iter() .find(|arm| arm.pattern == truthy_kind) .ok_or_else(|| self.unsupported(expr, "non-exhaustive match lowering"))?; let falsy_arm = arms .iter() .find(|arm| arm.pattern == falsy_kind) .ok_or_else(|| self.unsupported(expr, "non-exhaustive match lowering"))?; let truthy_block = self.block(truthy_prefix); let falsy_block = self.block(falsy_prefix); let end_block = self.block("match.end"); self.lines.push(format!( "br i1 {}, label %{}, label %{}", tag, truthy_block, falsy_block )); self.label(&truthy_block); let truthy_value = self.emit_match_arm(truthy_arm, &subject.ty, &aggregate_ty, &subject_value)?; let truthy_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", end_block)); self.label(&falsy_block); let falsy_value = self.emit_match_arm(falsy_arm, &subject.ty, &aggregate_ty, &subject_value)?; let falsy_pred = self.current_block.clone(); self.lines.push(format!("br label %{}", end_block)); self.label(&end_block); if expr.ty == Type::Unit { Ok("0".to_string()) } else { let reg = self.reg(); let phi_ty = self.llvm_type(expr, &expr.ty)?; self.lines.push(format!( "{} = phi {} [ {}, %{} ], [ {}, %{} ]", reg, phi_ty, truthy_value, truthy_pred, falsy_value, falsy_pred, )); Ok(reg) } } fn emit_enum_match( &mut self, expr: &TExpr, subject: &TExpr, arms: &[TMatchArm], ) -> Result { let subject_value = self.emit_expr(subject)?; let subject_llvm_ty = self.llvm_type(subject, &subject.ty)?; let switch_value = if self.is_payload_enum_type(&subject.ty) { let tag = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, 0", tag, subject_llvm_ty, subject_value )); tag } else { subject_value.clone() }; let end_block = self.block("match.end"); let default_block = self.block("match.invalid"); let mut arm_blocks = Vec::new(); for arm in arms { let Some(discriminant) = arm.discriminant else { return Err(self.unsupported(expr, "enum match arm without discriminant")); }; arm_blocks.push((discriminant, self.block("match.enum"), arm)); } let cases = arm_blocks .iter() .map(|(discriminant, block, _)| format!("i32 {}, label %{}", discriminant, block)) .collect::>() .join("\n "); self.lines.push(format!( "switch i32 {}, label %{} [\n {}\n ]", switch_value, default_block, cases )); self.label(&default_block); self.lines.push("unreachable".to_string()); let mut values = Vec::new(); for (_, block, arm) in arm_blocks { self.label(&block); let value = self.emit_match_arm(arm, &subject.ty, &subject_llvm_ty, &subject_value)?; let pred = self.current_block.clone(); self.lines.push(format!("br label %{}", end_block)); values.push((value, pred)); } self.label(&end_block); if expr.ty == Type::Unit { Ok("0".to_string()) } else { let reg = self.reg(); let phi_ty = self.llvm_type(expr, &expr.ty)?; let incoming = values .into_iter() .map(|(value, pred)| format!("[ {}, %{} ]", value, pred)) .collect::>() .join(", "); self.lines .push(format!("{} = phi {} {}", reg, phi_ty, incoming)); Ok(reg) } } fn emit_match_arm( &mut self, arm: &TMatchArm, subject_ty: &Type, aggregate_ty: &str, subject_value: &str, ) -> Result { let saved_locals = self.locals.clone(); if let Some(binding) = &arm.binding { let payload_index = match subject_ty { Type::Named(name) if self .enum_layouts .get(name) .and_then(|layout| layout.payload_ty.as_ref()) .is_some() => { Some(1) } Type::Option(_) => Some(1), Type::Result(_, _) => { result_payload_index(subject_ty, matches!(arm.pattern, MatchPatternKind::Ok)) } _ => None, } .ok_or_else(|| { Diagnostic::new( &self.file, "UnsupportedBackendFeature", "backend does not support this match payload binding", ) })?; let payload = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, {}", payload, aggregate_ty, subject_value, payload_index )); self.locals .insert(binding.clone(), LocalValue::Value(payload)); } let mut value = "0".to_string(); for expr in &arm.body { value = self.emit_expr(expr)?; } self.locals = saved_locals; Ok(value) } fn emit_struct_init( &mut self, expr: &TExpr, _name: &str, fields: &[(String, TExpr)], ) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let mut aggregate = "undef".to_string(); for (index, (_, field_expr)) in fields.iter().enumerate() { let value = self.emit_expr(field_expr)?; let field_ty = self.llvm_type(field_expr, &field_expr.ty)?; let with_field = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, {} {}, {}", with_field, aggregate_ty, aggregate, field_ty, value, index )); aggregate = with_field; } Ok(aggregate) } fn emit_array_init(&mut self, expr: &TExpr, elements: &[TExpr]) -> Result { let aggregate_ty = self.llvm_type(expr, &expr.ty)?; let mut aggregate = "undef".to_string(); for (index, element) in elements.iter().enumerate() { let value = self.emit_expr(element)?; let element_ty = self.llvm_type(element, &element.ty)?; let with_element = self.reg(); self.lines.push(format!( "{} = insertvalue {} {}, {} {}, {}", with_element, aggregate_ty, aggregate, element_ty, value, index )); aggregate = with_element; } Ok(aggregate) } fn emit_ssa_local(&mut self, name: &str, initializer: &TExpr) -> Result { let value = self.emit_expr(initializer)?; self.locals .insert(name.to_string(), LocalValue::Value(value)); Ok("0".to_string()) } fn emit_value_local(&mut self, name: &str, initializer: &TExpr) -> Result { let value = self.emit_expr(initializer)?; let llvm_ty = self.llvm_type(initializer, &initializer.ty)?; let ptr = self.local_addr(name); self.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); self.lines .push(format!("store {} {}, ptr {}", llvm_ty, value, ptr)); self.locals.insert( name.to_string(), LocalValue::Ptr { ptr, ty: initializer.ty.clone(), }, ); Ok("0".to_string()) } fn emit_array_local( &mut self, _expr: &TExpr, name: &str, initializer: &TExpr, ) -> Result { let Type::Array(inner, len) = &initializer.ty else { return Err(self.unsupported(initializer, "unsupported array locals")); }; let llvm_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len) .ok_or_else(|| self.unsupported(initializer, "unsupported array locals"))?; let ptr = self.local_addr(name); self.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); if let TExprKind::ArrayInit { elements } = &initializer.kind { for (index, element) in elements.iter().enumerate() { let value = self.emit_expr(element)?; let element_ty = self.llvm_type(element, &element.ty)?; let element_ptr = self.reg(); self.lines.push(format!( "{} = getelementptr inbounds {}, ptr {}, i32 0, i32 {}", element_ptr, llvm_ty, ptr, index )); self.lines.push(format!( "store {} {}, ptr {}", element_ty, value, element_ptr )); } } else { let value = self.emit_expr(initializer)?; self.lines .push(format!("store {} {}, ptr {}", llvm_ty, value, ptr)); } self.locals.insert( name.to_string(), LocalValue::Array { ptr, ty: initializer.ty.clone(), }, ); Ok("0".to_string()) } fn emit_index( &mut self, expr: &TExpr, array: &TExpr, index: &TExpr, ) -> Result { if let (TExprKind::ArrayInit { elements }, TExprKind::Int(index)) = (&array.kind, &index.kind) { let index = usize::try_from(*index) .map_err(|_| self.unsupported(expr, "negative array indices"))?; if index >= elements.len() { return Err(self.unsupported(expr, "out-of-bounds array indices")); } let aggregate = self.emit_expr(array)?; let aggregate_ty = self.llvm_type(array, &array.ty)?; let value = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, {}", value, aggregate_ty, aggregate, index )); return Ok(value); } let Type::Array(inner, len) = &array.ty else { return Err(self.unsupported(expr, "indexing non-array values")); }; let llvm_array_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len) .ok_or_else(|| self.unsupported(expr, "unsupported fixed array indexing"))?; let element_ty = self.llvm_type(expr, inner)?; let (array_ptr, len) = self.emit_array_address(expr, array)?; let index_value = self.emit_expr(index)?; self.emit_array_bounds_check(&index_value, len); let element_ptr = self.reg(); self.lines.push(format!( "{} = getelementptr inbounds {}, ptr {}, i32 0, i32 {}", element_ptr, llvm_array_ty, array_ptr, index_value )); let value = self.reg(); self.lines.push(format!( "{} = load {}, ptr {}", value, element_ty, element_ptr )); Ok(value) } fn emit_array_address( &mut self, _expr: &TExpr, array: &TExpr, ) -> Result<(String, usize), Diagnostic> { if let TExprKind::Var(name) = &array.kind { if let Some(LocalValue::Array { ptr, ty }) = self.locals.get(name).cloned() { let Type::Array(_, len) = ty else { return Err(self.unsupported(array, "unsupported fixed array locals")); }; return Ok((ptr, len)); } } let Type::Array(inner, len) = &array.ty else { return Err(self.unsupported(array, "unsupported fixed array values")); }; let value = self.emit_expr(array)?; let llvm_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len) .ok_or_else(|| self.unsupported(array, "unsupported fixed array values"))?; let ptr = self.local_addr("array.tmp"); self.lines.push(format!("{} = alloca {}", ptr, llvm_ty)); self.lines .push(format!("store {} {}, ptr {}", llvm_ty, value, ptr)); Ok((ptr, *len)) } fn emit_array_bounds_check(&mut self, index_value: &str, len: usize) { let lower_ok = self.reg(); self.lines .push(format!("{} = icmp sge i32 {}, 0", lower_ok, index_value)); let upper_ok = self.reg(); self.lines.push(format!( "{} = icmp slt i32 {}, {}", upper_ok, index_value, len )); let in_bounds = self.reg(); self.lines .push(format!("{} = and i1 {}, {}", in_bounds, lower_ok, upper_ok)); let ok_block = self.block("array.index.ok"); let trap_block = self.block("array.index.trap"); self.lines.push(format!( "br i1 {}, label %{}, label %{}", in_bounds, ok_block, trap_block )); self.label(&trap_block); self.lines .push("call void @__glagol_array_bounds_trap()".to_string()); self.lines.push("unreachable".to_string()); self.label(&ok_block); } fn emit_field_access( &mut self, expr: &TExpr, value: &TExpr, field: &str, ) -> Result { let Type::Named(struct_name) = &value.ty else { return Err(self.unsupported(expr, "field access on non-struct values")); }; let Some(layout) = self.layouts.get(struct_name) else { return Err(self.unsupported(expr, "unknown struct layouts")); }; let Some((index, (_, field_ty))) = layout .fields .iter() .enumerate() .find(|(_, (name, _))| name == field) else { return Err(self.unsupported(expr, "unknown struct fields")); }; let aggregate = self.emit_expr(value)?; let aggregate_ty = self.llvm_type(value, &value.ty)?; let _field_ty = self.llvm_type(expr, field_ty)?; let result = self.reg(); self.lines.push(format!( "{} = extractvalue {} {}, {}", result, aggregate_ty, aggregate, index )); Ok(result) } }