use { self::strong_ref::StrongRef, crate::{ backend::{ hbvm::{Comptime, HbvmBackend}, Backend, }, debug, lexer::TokenKind, nodes::*, parser::{ self, idfl::{self}, CapturedIdent, CommentOr, CtorField, DeclId, Expr, ExprRef, FieldList, ListKind, MatchBranch, Pos, }, ty::{ self, Arg, ArrayLen, CompState, ConstData, EnumData, EnumField, FTask, FuncData, GlobalData, List, Loc, Module, Offset, OffsetIter, OptLayout, Sig, StringRef, StructData, StructField, SymKey, TemplateData, TupleData, TypeBase, TypeIns, Types, UnionData, UnionField, }, utils::{BitSet, EntSlice, Vc}, Ident, }, alloc::{string::String, vec::Vec}, core::{ assert_matches::debug_assert_matches, cell::RefCell, fmt::{self, Debug, Display, Write}, format_args as fa, mem, }, hbbytecode::DisasmError, std::panic, }; const DEFAULT_ACLASS: usize = 0; const GLOBAL_ACLASS: usize = 1; macro_rules! inference { ($ty:ident, $ctx:expr, $self:expr, $pos:expr, $subject:literal, $example:literal) => { let Some($ty) = $ctx.ty else { $self.error( $pos, concat!( "resulting ", $subject, " cannot be inferred from context, consider using `", $example, "` to hint the type", ), ); return Value::NEVER; }; }; } impl Nodes { fn load_loop_var(&mut self, index: usize, var: &mut Variable, loops: &mut [Loop]) { if var.value() != VOID { return; } debug_assert!(!var.ptr); let [loops @ .., loob] = loops else { unreachable!() }; let &mut Loop::Runtime { node, ref mut scope, .. } = loob else { self.load_loop_var(index, var, loops); return; }; let lvar = &mut scope.vars[index]; debug_assert!(!lvar.ptr); self.load_loop_var(index, lvar, loops); if !self[lvar.value()].is_lazy_phi(node) { let lvalue = lvar.value(); let inps = [node, lvalue, VOID]; lvar.set_value(self.new_node_nop(lvar.ty, Kind::Phi, inps), self); self.pass_aclass(self.aclass_index(lvalue).1, lvar.value()); } var.set_value(lvar.value(), self); } fn load_loop_aclass(&mut self, index: usize, aclass: &mut AClass, loops: &mut [Loop]) { if aclass.last_store.get() != VOID { return; } let [loops @ .., loob] = loops else { unreachable!() }; let &mut Loop::Runtime { node, ref mut scope, .. } = loob else { self.load_loop_aclass(index, aclass, loops); return; }; let lvar = &mut scope.aclasses[index]; self.load_loop_aclass(index, lvar, loops); if !self[lvar.last_store.get()].is_lazy_phi(node) { let inps = [node, lvar.last_store.get(), VOID]; lvar.last_store.set(self.new_node_nop(ty::Id::VOID, Kind::Phi, inps), self); } aclass.last_store.set(lvar.last_store.get(), self); } fn merge_scopes( &mut self, loops: &mut [Loop], ctrl: &StrongRef, to: &mut Scope, from: &mut Scope, tys: &Types, ) { for (i, (to_value, from_value)) in to.vars.iter_mut().zip(from.vars.iter_mut()).enumerate() { debug_assert_eq!(to_value.ty, from_value.ty); if to_value.value() != from_value.value() { self.load_loop_var(i, from_value, loops); self.load_loop_var(i, to_value, loops); if to_value.value() != from_value.value() { debug_assert!(!to_value.ptr); debug_assert!(!from_value.ptr); let inps = [ctrl.get(), from_value.value(), to_value.value()]; to_value .set_value_remove(self.new_node(from_value.ty, Kind::Phi, inps, tys), self); } } } for (i, (to_class, from_class)) in to.aclasses.iter_mut().zip(from.aclasses.iter_mut()).enumerate() { if to_class.last_store.get() != from_class.last_store.get() { self.load_loop_aclass(i, from_class, loops); self.load_loop_aclass(i, to_class, loops); if to_class.last_store.get() != from_class.last_store.get() { let inps = [ctrl.get(), from_class.last_store.get(), to_class.last_store.get()]; to_class .last_store .set_remove(self.new_node(ty::Id::VOID, Kind::Phi, inps, tys), self); } } } } fn new_const_lit(&mut self, ty: ty::Id, value: impl Into) -> Value { Value::new(self.new_const(ty, value)).ty(ty) } fn new_node_lit(&mut self, ty: ty::Id, kind: Kind, inps: impl Into, tys: &Types) -> Value { Value::new(self.new_node(ty, kind, inps, tys)).ty(ty) } } #[derive(Clone, Copy)] pub enum CtLoopState { Terminated, Continued, } #[derive(Clone)] enum Loop { Comptime { state: Option<(CtLoopState, Pos)>, defer_base: usize, }, Runtime { node: Nid, ctrl: [StrongRef; 2], ctrl_scope: [Scope; 2], scope: Scope, defer_base: usize, }, } mod strong_ref { use { super::{Kind, Nid, Nodes}, crate::debug, core::ops::Not, }; #[derive(Clone)] pub struct StrongRef(Nid); impl StrongRef { pub const DEFAULT: Self = Self(Nid::MAX); pub fn new(value: Nid, nodes: &mut Nodes) -> Self { nodes.lock(value); Self(value) } pub fn get(&self) -> Nid { debug_assert!(self.0 != Nid::MAX); self.0 } pub fn unwrap(self, nodes: &mut Nodes) -> Option { let nid = self.0; if nid != Nid::MAX { nodes.unlock(nid); core::mem::forget(self); Some(nid) } else { None } } pub fn set(&mut self, mut new_value: Nid, nodes: &mut Nodes) -> Nid { nodes.unlock(self.0); core::mem::swap(&mut self.0, &mut new_value); nodes.lock(self.0); new_value } pub fn dup(&self, nodes: &mut Nodes) -> Self { nodes.lock(self.0); Self(self.0) } pub fn remove(self, nodes: &mut Nodes) -> Option { let ret = nodes.unlock_remove(self.0).not().then_some(self.0); core::mem::forget(self); ret } pub fn set_remove(&mut self, new_value: Nid, nodes: &mut Nodes) { let old = self.set(new_value, nodes); nodes.remove(old); } pub fn remove_ignore_arg(self, nodes: &mut Nodes) { if nodes[self.0].kind == Kind::Arg { nodes.unlock(self.0); } else { nodes.unlock_remove(self.0); } core::mem::forget(self); } pub fn soft_remove(self, nodes: &mut Nodes) -> Nid { let nid = self.0; nodes.unlock(self.0); core::mem::forget(self); nid } pub fn is_live(&self) -> bool { self.0 != Nid::MAX } } impl Default for StrongRef { fn default() -> Self { Self::DEFAULT } } impl Drop for StrongRef { fn drop(&mut self) { if self.0 != Nid::MAX && !debug::panicking() { panic!("variable unproperly deinitialized") } } } } // makes sure value inside is laways locked for this instance of variable #[derive(Default, Clone)] struct Variable { id: Ident, ty: ty::Id, ptr: bool, value: StrongRef, } impl Variable { fn new(id: Ident, ty: ty::Id, ptr: bool, value: Nid, nodes: &mut Nodes) -> Self { Self { id, ty, ptr, value: StrongRef::new(value, nodes) } } fn value(&self) -> Nid { self.value.get() } fn set_value(&mut self, new_value: Nid, nodes: &mut Nodes) -> Nid { self.value.set(new_value, nodes) } fn dup(&self, nodes: &mut Nodes) -> Self { Self { id: self.id, ty: self.ty, ptr: self.ptr, value: self.value.dup(nodes) } } fn remove(self, nodes: &mut Nodes) { self.value.remove(nodes); } fn set_value_remove(&mut self, new_value: Nid, nodes: &mut Nodes) { self.value.set_remove(new_value, nodes); } fn remove_ignore_arg(self, nodes: &mut Nodes) { self.value.remove_ignore_arg(nodes); } } #[derive(Default, Clone)] pub struct AClass { last_store: StrongRef, clobber: StrongRef, } impl AClass { fn dup(&self, nodes: &mut Nodes) -> Self { Self { last_store: self.last_store.dup(nodes), clobber: self.clobber.dup(nodes) } } fn remove(self, nodes: &mut Nodes) { self.last_store.remove(nodes); self.clobber.remove(nodes); } fn new(nodes: &mut Nodes) -> Self { Self { last_store: StrongRef::new(MEM, nodes), clobber: StrongRef::new(VOID, nodes) } } } #[derive(Default, Clone)] pub struct Scope { vars: Vec, aclasses: Vec, } impl Scope { fn dup(&self, nodes: &mut Nodes) -> Self { Self { vars: self.vars.iter().map(|v| v.dup(nodes)).collect(), aclasses: self.aclasses.iter().map(|v| v.dup(nodes)).collect(), } } fn clear(&mut self, nodes: &mut Nodes) { self.vars.drain(..).for_each(|n| n.remove(nodes)); self.aclasses.drain(..).for_each(|l| l.remove(nodes)); } } #[derive(Default, Clone)] pub struct ItemCtx { file: Module, parent: ty::Id, pos: Vec, ret: Option, task_base: usize, inline_var_base: usize, inline_aclass_base: usize, inline_depth: u16, inline_defer_base: usize, inline_ret: Option<(Value, StrongRef, Scope, Option)>, nodes: Nodes, ctrl: StrongRef, loops: Vec, defers: Vec<(Pos, ExprRef)>, scope: Scope, } impl ItemCtx { fn init(&mut self, file: Module, parent: ty::Id, ret: Option, task_base: usize) { debug_assert_eq!(self.loops.len(), 0); debug_assert_eq!(self.defers.len(), 0); debug_assert_eq!(self.scope.vars.len(), 0); debug_assert_eq!(self.scope.aclasses.len(), 0); debug_assert!(self.inline_ret.is_none()); debug_assert_eq!(self.inline_depth, 0); debug_assert_eq!(self.inline_var_base, 0); debug_assert_eq!(self.inline_aclass_base, 0); debug_assert_eq!(self.inline_defer_base, 0); self.file = file; self.parent = parent; self.ret = ret; self.task_base = task_base; self.nodes.clear(); self.scope.vars.clear(); let start = self.nodes.new_node_nop(ty::Id::VOID, Kind::Start, []); debug_assert_eq!(start, VOID); let end = self.nodes.new_node_nop(ty::Id::NEVER, Kind::End, []); debug_assert_eq!(end, NEVER); self.nodes.lock(end); self.ctrl = StrongRef::new( self.nodes.new_node_nop(ty::Id::VOID, Kind::Entry, [VOID]), &mut self.nodes, ); debug_assert_eq!(self.ctrl.get(), ENTRY); let mem = self.nodes.new_node_nop(ty::Id::VOID, Kind::Mem, [VOID]); debug_assert_eq!(mem, MEM); self.nodes.lock(mem); let loops = self.nodes.new_node_nop(ty::Id::VOID, Kind::Loops, [VOID]); debug_assert_eq!(loops, LOOPS); self.nodes.lock(loops); self.scope.aclasses.push(AClass::new(&mut self.nodes)); // DEFAULT self.scope.aclasses.push(AClass::new(&mut self.nodes)); // GLOBAL } fn finalize( &mut self, stack: &mut Vec, tys: &Types, _files: &EntSlice, ) { self.scope.clear(&mut self.nodes); mem::take(&mut self.ctrl).soft_remove(&mut self.nodes); self.nodes.iter_peeps(1000, stack, tys); self.nodes.unlock(MEM); self.nodes.unlock(NEVER); self.nodes.unlock(LOOPS); } } #[derive(Default, Debug)] struct Ctx { ty: Option, } impl Ctx { fn with_ty(self, ty: ty::Id) -> Self { Self { ty: Some(ty) } } } #[derive(Default)] pub struct Pool { cis: Vec, used_cis: usize, scratch1: Vec, scratch2: Vec, lit_buf: Vec, nid_set: BitSet, } impl Pool { fn push_ci( &mut self, file: Module, parent: ty::Id, ret: Option, task_base: usize, target: &mut ItemCtx, ) -> &mut ItemCtx { if let Some(slot) = self.cis.get_mut(self.used_cis) { mem::swap(slot, target); } else { self.cis.push(ItemCtx::default()); mem::swap(self.cis.last_mut().unwrap(), target); } target.init(file, parent, ret, task_base); self.used_cis += 1; &mut self.cis[self.used_cis - 1] } fn pop_ci(&mut self, target: &mut ItemCtx) { self.used_cis -= 1; mem::swap(&mut self.cis[self.used_cis], target); } fn save_ci(&mut self, ci: &ItemCtx) { if let Some(slot) = self.cis.get_mut(self.used_cis) { slot.clone_from(ci); } else { self.cis.push(ci.clone()); } self.used_cis += 1; } fn restore_ci(&mut self, dst: &mut ItemCtx) { self.used_cis -= 1; dst.scope.clear(&mut dst.nodes); dst.loops.drain(..).for_each(|l| { if let Loop::Runtime { ctrl, ctrl_scope, mut scope, .. } = l { ctrl.map(|c| { if c.is_live() { c.remove(&mut dst.nodes); } }); scope.clear(&mut dst.nodes); ctrl_scope.map(|mut s| s.clear(&mut dst.nodes)); } }); if let Some((_, ctrl, mut scope, acl)) = dst.inline_ret.take() { ctrl.remove(&mut dst.nodes); if let Some(acl) = acl { acl.remove(&mut dst.nodes) }; scope.clear(&mut dst.nodes); } mem::take(&mut dst.ctrl).remove(&mut dst.nodes); *dst = mem::take(&mut self.cis[self.used_cis]); } fn clear(&mut self) { debug_assert_eq!(self.used_cis, 0); } } #[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] struct Value { ty: ty::Id, var: bool, ptr: bool, id: Nid, } impl Value { const NEVER: Option = Some(Self { ty: ty::Id::NEVER, var: false, ptr: false, id: NEVER }); const VOID: Value = Self { ty: ty::Id::VOID, var: false, ptr: false, id: VOID }; fn new(id: Nid) -> Self { Self { id, ..Default::default() } } fn var(id: usize) -> Self { Self { id: u16::MAX - (id as Nid), var: true, ..Default::default() } } fn ptr(id: Nid) -> Self { Self { id, ptr: true, ..Default::default() } } #[inline(always)] fn ty(self, ty: ty::Id) -> Self { Self { ty, ..self } } } #[derive(Default)] pub struct CodegenCtx { pub parser: parser::Ctx, tys: Types, pool: Pool, ct: Comptime, ct_backend: HbvmBackend, } impl CodegenCtx { pub fn clear(&mut self) { self.parser.clear(); self.tys.clear(); self.pool.clear(); self.ct.clear(); } } pub struct Codegen<'a> { pub files: &'a EntSlice, pub errors: &'a RefCell, pub warnings: &'a RefCell, tys: &'a mut Types, ci: ItemCtx, pool: &'a mut Pool, ct: &'a mut Comptime, ct_backend: &'a mut HbvmBackend, backend: &'a mut dyn Backend, } impl Drop for Codegen<'_> { fn drop(&mut self) { if debug::panicking() { if let Some(&pos) = self.ci.pos.last() { self.error(pos, "panic occured here"); } if !self.errors.borrow().is_empty() { log::error!("{}", self.errors.borrow()); } } } } impl<'a> Codegen<'a> { pub fn new( backend: &'a mut dyn Backend, files: &'a [parser::Ast], ctx: &'a mut CodegenCtx, ) -> Self { Self { files: files.into(), errors: &ctx.parser.errors, warnings: &ctx.parser.warnings, tys: &mut ctx.tys, ci: Default::default(), pool: &mut ctx.pool, ct: &mut ctx.ct, ct_backend: &mut ctx.ct_backend, backend, } } pub fn generate(&mut self, entry: Module) { self.find_type(0, entry, entry, "main"); if self.tys.ins.funcs.is_empty() { return; } self.make_func_reachable(ty::Func::MAIN); self.complete_call_graph(); } pub fn assemble_comptime(&mut self) -> Comptime { self.ct.code.clear(); self.backend.assemble_bin(ty::Func::MAIN, self.tys, &mut self.ct.code); self.ct.reset(); core::mem::take(self.ct) } pub fn assemble(&mut self, buf: &mut Vec) { self.backend.assemble_bin(ty::Func::MAIN, self.tys, buf); } pub fn disasm(&mut self, output: &mut String, bin: &[u8]) -> Result<(), DisasmError> { self.backend.disasm(bin, &mut |_| {}, self.tys, self.files, output) } pub fn push_embeds(&mut self, embeds: Vec>) { for data in embeds { let g = GlobalData { ty: self.tys.make_array(ty::Id::U8, data.len() as _), data, ..Default::default() }; self.tys.ins.globals.push(g); } } fn emit_and_eval(&mut self, file: Module, ret: ty::Id, ret_loc: &mut [u8]) -> u64 { let mut rets = self.ci.nodes[NEVER] .inputs .iter() .filter(|&&i| matches!(self.ci.nodes[i].kind, Kind::Return { .. })); if let Some(&ret) = rets.next() && rets.next().is_none() && let Kind::CInt { value } = self.ci.nodes[self.ci.nodes[ret].inputs[1]].kind { if let len @ 1..=8 = ret_loc.len() { ret_loc.copy_from_slice(&value.to_ne_bytes()[..len]) } return value as _; } if !self.complete_call_graph() { return 1; } let fuc = self.tys.ins.funcs.push(FuncData { file, sig: Sig { args: List::empty(), ret }, ..Default::default() }); self.ct_backend.emit_ct_body(fuc, &self.ci.nodes, self.tys, self.files); // TODO: return them back let entry = self.ct_backend.assemble_reachable(fuc, self.tys, &mut self.ct.code).entry; #[cfg(debug_assertions)] { let mut vc = String::new(); if let Err(e) = self.ct_backend.disasm(&self.ct.code, &mut |_| {}, self.tys, self.files, &mut vc) { panic!("{e} {}", vc); } else { log::info!("{}", vc); } } self.ct.run(ret_loc, entry) } fn new_stack(&mut self, pos: Pos, ty: ty::Id) -> Nid { let stck = self.ci.nodes.new_node_nop(ty, Kind::Stck, [VOID, MEM]); self.ci.nodes[stck].aclass = self.ci.scope.aclasses.len() as _; self.ci.nodes[stck].pos = pos; self.ci.scope.aclasses.push(AClass::new(&mut self.ci.nodes)); stck } fn store_mem(&mut self, region: Nid, ty: ty::Id, value: Nid) -> Nid { if value == NEVER { return NEVER; } debug_assert!( self.ci.nodes[region].kind != Kind::Load || self.ci.nodes[region].ty.is_pointer() ); debug_assert!(self.ci.nodes[region].kind != Kind::Stre); let (value_index, value_region) = self.ci.nodes.aclass_index(value); if value_index != 0 { self.ci.nodes[value_region].aclass = 0; self.ci.nodes.load_loop_aclass(0, &mut self.ci.scope.aclasses[0], &mut self.ci.loops); self.ci.nodes.load_loop_aclass( value_index, &mut self.ci.scope.aclasses[value_index], &mut self.ci.loops, ); let base_class = self.ci.scope.aclasses[0].last_store.get(); let last_store = self.ci.scope.aclasses[value_index].last_store.get(); match [base_class, last_store] { [_, MEM] => {} [MEM, a] => { self.ci.scope.aclasses[0].last_store.set(a, &mut self.ci.nodes); } [a, b] => { let a = self.ci.nodes.new_node_nop(ty::Id::VOID, Kind::Join, [0, a, b]); self.ci.scope.aclasses[0].last_store.set(a, &mut self.ci.nodes); } } } let (index, _) = self.ci.nodes.aclass_index(region); if self.ci.nodes[value].kind == Kind::Load { let (lindex, ..) = self.ci.nodes.aclass_index(self.ci.nodes[value].inputs[1]); let clobber = self.ci.scope.aclasses[lindex].clobber.get(); if self.ci.nodes.idepth(clobber, None) > self.ci.nodes.idepth(self.ci.scope.aclasses[index].clobber.get(), None) { self.ci.scope.aclasses[index].clobber.set(clobber, &mut self.ci.nodes); } } let aclass = &mut self.ci.scope.aclasses[index]; self.ci.nodes.load_loop_aclass(index, aclass, &mut self.ci.loops); let vc = Vc::from([aclass.clobber.get(), value, region, aclass.last_store.get()]); mem::take(&mut aclass.last_store).soft_remove(&mut self.ci.nodes); let store = self.ci.nodes.new_node(ty, Kind::Stre, vc, self.tys); aclass.last_store = StrongRef::new(store, &mut self.ci.nodes); store } fn load_mem(&mut self, region: Nid, ty: ty::Id) -> Nid { debug_assert_ne!(region, VOID); debug_assert_ne!({ self.ci.nodes[region].ty }, ty::Id::VOID, "{:?}", { self.ci.nodes[region].lock_rc.set(Nid::MAX); self.ci.nodes.graphviz_in_browser(self.ty_display(ty::Id::VOID)); }); debug_assert!( self.ci.nodes[region].kind != Kind::Load || self.ci.nodes[region].kind == Kind::Stck || self.ci.nodes[region].ty.is_pointer(), "{:?} {} {} {:?}", self.ci.nodes.graphviz_in_browser(self.ty_display(ty::Id::VOID)), self.file().path, self.ty_display(self.ci.nodes[region].ty), self.ci.nodes[region], ); debug_assert!(self.ci.nodes[region].kind != Kind::Stre); let (index, _) = self.ci.nodes.aclass_index(region); let aclass = &mut self.ci.scope.aclasses[index]; self.ci.nodes.load_loop_aclass(index, aclass, &mut self.ci.loops); let vc = [aclass.clobber.get(), region, aclass.last_store.get()]; self.ci.nodes.new_node(ty, Kind::Load, vc, self.tys) } fn make_func_reachable(&mut self, func: ty::Func) { let state_slot = self.ct.active() as usize; let fuc = &mut self.tys.ins.funcs[func]; if CompState::from(fuc.comp_state[state_slot]) == CompState::Dead { fuc.comp_state[state_slot] = CompState::Queued(self.tys.tasks.len() as _).into(); self.tys.tasks.push(Some(FTask { file: fuc.file, id: func, ct: self.ct.active() })); } } fn raw_expr(&mut self, expr: &Expr) -> Option { self.raw_expr_ctx(expr, Ctx::default()) } fn raw_expr_ctx(&mut self, expr: &Expr, ctx: Ctx) -> Option { self.ci.pos.push(expr.pos()); let res = self.raw_expr_ctx_low(expr, ctx); self.ci.pos.pop().unwrap(); res } fn raw_expr_ctx_low(&mut self, expr: &Expr, mut ctx: Ctx) -> Option { // ordered by complexity of the expression match *expr { Expr::Null { pos } => { inference!(oty, ctx, self, pos, "null pointer", "@as(^, null)"); let Some(ty) = self.tys.inner_of(oty) else { self.error( pos, fa!( "'null' expression was inferred to be '{}', which is not optional", self.ty_display(oty) ), ); return Value::NEVER; }; match oty.loc(self.tys) { Loc::Reg => Some(self.ci.nodes.new_const_lit(oty, 0)), Loc::Stack => { let OptLayout { flag_ty, flag_offset, .. } = self.tys.opt_layout(ty); let stack = self.new_stack(pos, oty); let offset = self.offset(stack, flag_offset); let value = self.ci.nodes.new_const(flag_ty, 0); self.store_mem(offset, flag_ty, value); Some(Value::ptr(stack).ty(oty)) } } } Expr::Idk { pos } => { inference!(ty, ctx, self, pos, "value", "@as(, idk)"); if ty.loc(self.tys) == Loc::Stack { Some(Value::ptr(self.new_stack(pos, ty)).ty(ty)) } else { Some(self.ci.nodes.new_const_lit(ty, 0)) } } Expr::Bool { value, .. } => Some(self.ci.nodes.new_const_lit(ty::Id::BOOL, value)), Expr::Number { value, .. } => self.gen_inferred_const(ctx, ty::Id::DINT, value), Expr::Float { value, .. } => { self.gen_inferred_const_low(ctx, ty::Id::F32, value as i64, true) } Expr::Ident { id, .. } if let Ok(bt) = ty::Builtin::try_from(id) => { Some(self.ci.nodes.new_const_lit(ty::Id::TYPE, bt)) } Expr::Ident { id, .. } if let Some(index) = self.ci.scope.vars.iter().rposition(|v| v.id == id) => { let var = &mut self.ci.scope.vars[index]; self.ci.nodes.load_loop_var(index, var, &mut self.ci.loops); Some(Value::var(index).ty(var.ty)) } Expr::Ident { id, .. } if let Some(vl) = { let mut piter = self.ci.parent; let f = self.file(); loop { if let Some((captures, capture_tuple)) = self.tys.captures_of(piter, f) && let Some(idx) = captures.iter().position(|&cid| cid.id == id) { let ty = if captures[idx].is_ct { ty::Id::TYPE } else { self.tys.ins.args[capture_tuple.range().start + idx] }; break Some(Value::new(NEVER).ty(ty)); } piter = match self.tys.parent_of(piter) { Some(p) => p, None => break None, }; } } => { Some(vl) } Expr::Ident { id, pos, .. } => self.find_type_as_value(pos, self.ci.parent, id, ctx), Expr::Comment { .. } => Some(Value::VOID), Expr::Char { pos, literal } | Expr::String { pos, literal } => { let literal = &literal[1..literal.len() - 1]; let mut data = core::mem::take(&mut self.pool.lit_buf); debug_assert!(data.is_empty()); let report = |bytes: &core::str::Bytes, message: &str| { self.error(pos + (literal.len() - bytes.len()) as u32 - 1, message); }; let char_count = crate::endoce_string(literal, &mut data, report).unwrap(); if matches!(expr, Expr::Char { .. }) { if char_count != 1 { return self.error( pos, fa!("character literal can only contain one character, \ but you supplied {char_count}"), ); } let value = match data.as_slice() { &[v] => v as i64, _ => return self.error(pos, "TODO: support utf-8 characters"), }; data.clear(); self.pool.lit_buf = data; self.gen_inferred_const(ctx, ty::Id::U8, value) } else { if data.last() != Some(&0) { self.error(pos, "string literal must end with null byte (for now)"); } let (global, ty) = self.create_string_global(&data); data.clear(); self.pool.lit_buf = data; Some(Value::new(global).ty(ty)) } } Expr::Defer { pos, value } => { self.ci.defers.push((pos, ExprRef::new(value))); Some(Value::VOID) } Expr::Return { pos, val } => { let mut value = if let Some(val) = val { self.ptr_expr_ctx(val, Ctx { ty: self.ci.ret })? } else { Value { ty: ty::Id::VOID, ..Default::default() } }; let expected = *self.ci.ret.get_or_insert(value.ty); self.assert_ty(pos, &mut value, expected, "return value"); self.strip_ptr(&mut value); self.gen_defers(self.ci.inline_defer_base); if self.ci.inline_depth == 0 { debug_assert_ne!(self.ci.ctrl.get(), VOID); let mut inps = Vc::from([self.ci.ctrl.get(), value.id]); for (i, aclass) in self.ci.scope.aclasses.iter_mut().enumerate() { self.ci.nodes.load_loop_aclass(i, aclass, &mut self.ci.loops); if aclass.last_store.get() != MEM { inps.push(aclass.last_store.get()); } } let ret = self.ci.nodes.new_node_nop( ty::Id::VOID, Kind::Return { file: self.ci.file }, inps, ); self.ci.ctrl.set(NEVER, &mut self.ci.nodes); self.ci.nodes[ret].pos = pos; self.ci.nodes.bind(ret, NEVER); } else if let Some((mut pv, mut ctrl, mut scope, aclass)) = self.ci.inline_ret.take() { if value.ty.loc(self.tys) == Loc::Stack { self.spill(pos, &mut value); } debug_assert!( aclass.is_none(), "TODO: oh no, we cant return structs from divergent branches" ); ctrl.set( self.ci.nodes.new_node( ty::Id::VOID, Kind::Region, [self.ci.ctrl.get(), ctrl.get()], self.tys, ), &mut self.ci.nodes, ); self.ci.nodes.merge_scopes( &mut self.ci.loops, &ctrl, &mut scope, &mut self.ci.scope, self.tys, ); self.ci.nodes.unlock(pv.id); pv.id = self.ci.nodes.new_node( value.ty, Kind::Phi, [ctrl.get(), value.id, pv.id], self.tys, ); self.ci.nodes.lock(pv.id); self.ci.ctrl.set(NEVER, &mut self.ci.nodes); self.ci.inline_ret = Some((pv, ctrl, scope, aclass)); } else { if value.ty.loc(self.tys) == Loc::Stack { self.spill(pos, &mut value); } for (i, aclass) in self.ci.scope.aclasses[..2].iter_mut().enumerate() { self.ci.nodes.load_loop_aclass(i, aclass, &mut self.ci.loops); } self.ci.nodes.lock(value.id); let mut scope = self.ci.scope.dup(&mut self.ci.nodes); scope .vars .drain(self.ci.inline_var_base..) .for_each(|v| v.remove(&mut self.ci.nodes)); scope .aclasses .drain(self.ci.inline_aclass_base..) .for_each(|v| v.remove(&mut self.ci.nodes)); let repl = StrongRef::new(NEVER, &mut self.ci.nodes); let (index, _) = self.ci.nodes.aclass_index(value.id); let aclass = (self.ci.inline_aclass_base <= index) .then(|| self.ci.scope.aclasses[index].dup(&mut self.ci.nodes)); self.ci.inline_ret = Some((value, mem::replace(&mut self.ci.ctrl, repl), scope, aclass)); } None } Expr::Die { .. } => { self.ci.ctrl.set( self.ci.nodes.new_node_nop(ty::Id::VOID, Kind::Die, [self.ci.ctrl.get()]), &mut self.ci.nodes, ); self.ci.nodes.bind(self.ci.ctrl.get(), NEVER); None } Expr::Field { target, name, pos } => { self.gen_field(ctx, target, pos, name)?.ok().or_else(|| { self.error(pos, "method can not be used like this"); Value::NEVER }) } Expr::UnOp { op: TokenKind::Band, val, pos } => { let ctx = Ctx { ty: ctx.ty.and_then(|ty| self.tys.base_of(ty)) }; let mut val = self.ptr_expr_ctx(val, ctx)?; if val.ptr { val.ptr = false; val.ty = self.tys.make_ptr(val.ty); return Some(val); } let stack = self.new_stack(pos, val.ty); self.store_mem(stack, val.ty, val.id); Some(Value::new(stack).ty(self.tys.make_ptr(val.ty))) } Expr::UnOp { op: TokenKind::Mul, val, pos } => { let ctx = Ctx { ty: ctx.ty.map(|ty| self.tys.make_ptr(ty)) }; let mut vl = self.expr_ctx(val, ctx)?; self.implicit_unwrap(val.pos(), &mut vl); let Some(base) = self.tys.base_of(vl.ty) else { return self.error( pos, fa!("the '{}' can not be dereferneced", self.ty_display(vl.ty)), ); }; vl.ptr = true; vl.ty = base; Some(vl) } Expr::UnOp { pos, op: TokenKind::Dot, val: &Expr::Ident { id, .. } } => { inference!(ty, ctx, self, pos, "enum type", ".Variant"); let ty::Kind::Enum(e) = ty.expand() else { return self.error( pos, fa!("expected inferred type to be enum but got '{}'", self.ty_display(ty)), ); }; let intrnd = self.tys.names.project(self.file().ident_str(id)); self.gen_enum_variant(pos, e, intrnd) } Expr::UnOp { pos, op: op @ TokenKind::Sub, val } => { let val = self.expr_ctx(val, Ctx::default().with_ty(ctx.ty.unwrap_or(ty::Id::INT)))?; if val.ty.is_integer() { Some(self.ci.nodes.new_node_lit( val.ty, Kind::UnOp { op }, [VOID, val.id], self.tys, )) } else if val.ty.is_float() { let value = self.ci.nodes.new_const(val.ty, (-1f64).to_bits() as i64); Some(self.ci.nodes.new_node_lit( val.ty, Kind::BinOp { op: TokenKind::Mul }, [VOID, val.id, value], self.tys, )) } else { self.error(pos, fa!("cant negate '{}'", self.ty_display(val.ty))) } } Expr::UnOp { pos, op: op @ TokenKind::Not, val } => { let val = self.expr_ctx(val, Ctx::default().with_ty(ctx.ty.unwrap_or(ty::Id::INT)))?; if val.ty == ty::Id::BOOL { Some(self.ci.nodes.new_node_lit( val.ty, Kind::UnOp { op }, [VOID, val.id], self.tys, )) } else { self.error(pos, fa!("cant logically negate '{}'", self.ty_display(val.ty))) } } Expr::BinOp { left, op: TokenKind::Colon, right: &Expr::BinOp { left: ty, op: TokenKind::Assign, right, pos }, .. } => { let ty = self.ty(ty); let mut right = self.checked_expr(right, ty, "declaration")?; if right.ty.loc(self.tys) == Loc::Stack { self.spill(pos, &mut right); } self.assign_pattern(left, right); Some(Value::VOID) } Expr::BinOp { left, op: TokenKind::Decl, right, pos } => { let mut right = self.expr(right)?; if right.ty.loc(self.tys) == Loc::Stack { self.spill(pos, &mut right); } self.assign_pattern(left, right); Some(Value::VOID) } Expr::BinOp { left: Expr::Wildcard { .. }, op: TokenKind::Assign, right, .. } => { self.expr(right)?; Some(Value::VOID) } Expr::BinOp { left, pos, op: TokenKind::Assign, right } => { let dest = self.raw_expr(left)?; let value = self.checked_expr(right, dest.ty, "assignment source")?; if dest.var { let var = &mut self.ci.scope.vars[(u16::MAX - dest.id) as usize]; if var.ptr { let val = var.value(); let ty = var.ty; self.store_mem(val, ty, value.id); } else { var.set_value_remove(value.id, &mut self.ci.nodes); } } else if dest.ptr { self.store_mem(dest.id, dest.ty, value.id); } else { self.error(pos, "cannot assign to this expression"); } Some(Value::VOID) } Expr::BinOp { left: &Expr::Null { pos }, .. } => { self.error(pos, "'null' must always be no the right side of an expression") } Expr::BinOp { left, op: op @ (TokenKind::Eq | TokenKind::Ne), right: Expr::Null { .. }, .. } => { let cmped = self.ptr_expr(left)?; let Some(ty) = self.tys.inner_of(cmped.ty) else { return self.error( left.pos(), fa!("'{}' is never null, remove this check", self.ty_display(cmped.ty)), ); }; Some(Value::new(self.gen_null_check(cmped, ty, op)).ty(ty::Id::BOOL)) } Expr::BinOp { left, pos, op, right } => { debug_assert!(!matches!( op, TokenKind::Assign | TokenKind::Decl | TokenKind::Colon )); let mut lhs = self.ptr_expr_ctx(left, ctx)?; self.implicit_unwrap(left.pos(), &mut lhs); fn is_scalar_op(op: TokenKind, ty: ty::Id) -> bool { ty.is_pointer() || ty.is_integer() || ty == ty::Id::BOOL || (ty == ty::Id::TYPE && matches!(op, TokenKind::Eq | TokenKind::Ne)) || (ty.is_float() && op.is_supported_float_op()) } match lhs.ty.expand() { ty::Kind::Struct(s) if op.is_homogenous() => { debug_assert!(lhs.ptr); self.ci.nodes.lock(lhs.id); let rhs = self.ptr_expr_ctx(right, Ctx::default().with_ty(lhs.ty)); self.ci.nodes.unlock(lhs.id); let mut rhs = rhs?; debug_assert!(rhs.ptr); self.assert_ty(pos, &mut rhs, lhs.ty, "struct operand"); let dst = self.new_stack(pos, lhs.ty); self.struct_op(left.pos(), op, s, dst, lhs.id, rhs.id); Some(Value::ptr(dst).ty(lhs.ty)) } ty::Kind::Struct(s) if op.is_compatison() => { let binding_op = match op { TokenKind::Eq => TokenKind::Band, _ => TokenKind::Bor, }; self.ci.nodes.lock(lhs.id); let rhs = self.ptr_expr_ctx(right, Ctx::default().with_ty(lhs.ty)); self.ci.nodes.unlock(lhs.id); let mut rhs = rhs?; self.assert_ty(pos, &mut rhs, lhs.ty, "struct operand"); self.struct_fold_op(left.pos(), op, binding_op, s, lhs.id, rhs.id) .or(Value::NEVER) } _ if is_scalar_op(op, lhs.ty) => { self.strip_ptr(&mut lhs); self.ci.nodes.lock(lhs.id); let rhs = self.expr_ctx(right, Ctx::default().with_ty(lhs.ty)); self.ci.nodes.unlock(lhs.id); let mut rhs = rhs?; self.implicit_unwrap(right.pos(), &mut rhs); let (ty, aclass) = self.binop_ty(pos, &mut lhs, &mut rhs, op); if op.is_compatison() { if lhs.ty.is_float() { } else { self.ci.nodes.lock(rhs.id); let lty = lhs.ty.extend(); if lty != lhs.ty { self.extend(&mut lhs, lty); } self.ci.nodes.unlock(rhs.id); let rty = rhs.ty.extend(); if rty != rhs.ty { self.extend(&mut rhs, rty); } } } let bop = self.ci.nodes.new_node_lit( ty.bin_ret(op), Kind::BinOp { op }, [VOID, lhs.id, rhs.id], self.tys, ); self.ci.nodes.pass_aclass(aclass, bop.id); Some(bop) } _ => self .error(pos, fa!("'{} {op} _' is not supported", self.ty_display(lhs.ty))), } } Expr::Index { base, index } => { let mut bs = self.ptr_expr(base)?; if let Some(base) = self.tys.base_of(bs.ty) { bs.ptr = true; bs.ty = base; } let idx = self.checked_expr(index, ty::Id::DINT, "subscript")?; match bs.ty.expand() { ty::Kind::Slice(s) => { let elem = self.tys.ins.slices[s].elem; let size = self.ci.nodes.new_const(ty::Id::INT, self.tys.size_of(elem)); let inps = [VOID, idx.id, size]; let offset = self.ci.nodes.new_node( ty::Id::INT, Kind::BinOp { op: TokenKind::Mul }, inps, self.tys, ); let aclass = self.ci.nodes.aclass_index(bs.id).1; let inps = [VOID, bs.id, offset]; let ptr = self.ci.nodes.new_node( ty::Id::INT, Kind::BinOp { op: TokenKind::Add }, inps, self.tys, ); self.ci.nodes.pass_aclass(aclass, ptr); Some(Value::ptr(ptr).ty(elem)) } ty::Kind::Struct(s) => { let Kind::CInt { value: idx } = self.ci.nodes[idx.id].kind else { return self.error( index.pos(), "field index needs to be known at compile time", ); }; let Some((f, offset)) = OffsetIter::new(s, self.tys) .into_iter(self.tys) .nth(idx as _) .map(|(f, off)| (f.ty, off)) else { return self.error( index.pos(), fa!( "struct '{}' has only `{}' fields, \ but index was '{}'", self.ty_display(bs.ty), self.tys.struct_fields(s).len(), idx ), ); }; Some(Value::ptr(self.offset(bs.id, offset)).ty(f)) } ty::Kind::Tuple(t) => { let Kind::CInt { value: idx } = self.ci.nodes[idx.id].kind else { return self.error( index.pos(), "field index needs to be known at compile time", ); }; let Some((f, offset)) = OffsetIter::new(t, self.tys) .into_iter(self.tys) .nth(idx as _) .map(|(&f, off)| (f, off)) else { return self.error( index.pos(), fa!( "struct '{}' has only `{}' fields, \ but index was '{}'", self.ty_display(bs.ty), self.tys.tuple_fields(t).len(), idx ), ); }; Some(Value::ptr(self.offset(bs.id, offset)).ty(f)) } _ => self.error( base.pos(), fa!( "cant index into '{}' which is not array nor slice or tuple or struct", self.ty_display(bs.ty) ), ), } } Expr::Embed { id, .. } => { let glob = &self.tys.ins.globals[id]; let g = self.ci.nodes.new_node(glob.ty, Kind::Global { global: id }, [VOID], self.tys); Some(Value::ptr(g).ty(glob.ty)) } Expr::Directive { name: "kindof", args: [ty], .. } => { let ty = self.ty(ty); self.gen_inferred_const(ctx, ty::Id::U8, ty.kind()) } Expr::Directive { name: "nameof", args: [ty], .. } => { let ty = self.ty(ty); let mut data = core::mem::take(&mut self.pool.lit_buf); self.tys.name_of(ty, self.files, &mut data); data.push(0); let (global, ty) = self.create_string_global(&data); data.clear(); self.pool.lit_buf = data; Some(Value::new(global).ty(ty)) } Expr::Directive { name: "sizeof", args: [ty], .. } => { let ty = self.ty(ty); self.gen_inferred_const(ctx, ty::Id::DINT, self.tys.size_of(ty)) } Expr::Directive { name: "alignof", args: [ty], .. } => { let ty = self.ty(ty); let align = self.tys.align_of(ty); self.gen_inferred_const(ctx, ty::Id::DINT, align) } Expr::Directive { name: "lenof", args: [ety], .. } => { let ty = self.expr(ety)?; let len = match self.ci.nodes[ty.id].kind { Kind::CInt { value } => match self.tys.len_of(ty::Id::from(value as u64)) { Some(len) => len, None => { return self.error( ety.pos(), fa!( "'@len' only supports structs and arrays or strings. got {}", self.ty_display(ty::Id::from(value as u64)) ), ) } }, Kind::Global { global } => self.tys.ins.globals[global].data.len() as u32 - 1, _ => { return self .error(ety.pos(), "'@len' only supports structs and arrays or strings") } }; self.gen_inferred_const(ctx, ty::Id::DINT, len) } Expr::Directive { name: "bitcast", args: [val], pos } => { let mut val = self.ptr_expr(val)?; inference!(ty, ctx, self, pos, "type", "@as(, @bitcast())"); let (got, expected) = (self.tys.size_of(val.ty), self.tys.size_of(ty)); if got != expected { self.error( pos, fa!( "cast from '{}' to '{}' is not supported, \ sizes dont match ({got} != {expected})", self.ty_display(val.ty), self.ty_display(ty) ), ); } match ty.loc(self.tys) { Loc::Reg if mem::take(&mut val.ptr) => val.id = self.load_mem(val.id, ty), Loc::Stack if !val.ptr => self.spill(pos, &mut val), _ => {} } val.ty = ty; Some(val) } Expr::Directive { name: "unwrap", args: [expr], .. } => { let mut val = self.ptr_expr(expr)?; if !val.ty.is_optional() { return self.error( expr.pos(), fa!( "only optional types can be unwrapped ('{}' is not optional)", self.ty_display(val.ty) ), ); }; self.explicit_unwrap(expr.pos(), &mut val); Some(val) } Expr::Directive { name: "intcast", args: [expr], pos } => { let mut val = self.expr(expr)?; if !val.ty.is_integer() { return self.error( expr.pos(), fa!( "only integers can be truncated ('{}' is not an integer)", self.ty_display(val.ty) ), ); } inference!(ty, ctx, self, pos, "integer", "@as(, @intcast())"); if !ty.is_integer() { self.error( expr.pos(), fa!( "intcast is inferred to output '{}', which is not an integer", self.ty_display(ty) ), ); } if self.tys.size_of(val.ty) < self.tys.size_of(ty) { self.extend(&mut val, ty); Some(val) } else { Some(val.ty(ty)) } } Expr::Directive { pos, name: "floatcast", args: [expr] } => { let val = self.expr(expr)?; if !val.ty.is_float() { return self.error( expr.pos(), fa!( "only floats can be truncated ('{}' is not a float)", self.ty_display(val.ty) ), ); } inference!(ty, ctx, self, pos, "float", "@as(, @floatcast())"); if !ty.is_float() { self.error( expr.pos(), fa!( "floatcast is inferred to output '{}', which is not a float", self.ty_display(ty) ), ); } if self.tys.size_of(val.ty) != self.tys.size_of(ty) { Some(self.ci.nodes.new_node_lit( ty, Kind::UnOp { op: TokenKind::Float }, [VOID, val.id], self.tys, )) } else { Some(val.ty(ty)) } } Expr::Directive { name: "fti", args: [expr], .. } => { let val = self.expr(expr)?; let ret_ty = match val.ty { ty::Id::F32 | ty::Id::F64 => ty::Id::INT, _ => { return self.error( expr.pos(), fa!("expected float ('{}' is not a float)", self.ty_display(val.ty)), ) } }; Some(self.ci.nodes.new_node_lit( ret_ty, Kind::UnOp { op: TokenKind::Number }, [VOID, val.id], self.tys, )) } Expr::Directive { name: "itf", args: [expr], pos } => { let mut val = self.expr_ctx(expr, Ctx::default().with_ty(ty::Id::INT))?; inference!(fty, ctx, self, pos, "float", "@as())"); self.assert_ty(expr.pos(), &mut val, ty::Id::INT, "converted integer"); Some(self.ci.nodes.new_node_lit( fty, Kind::UnOp { op: TokenKind::Float }, [VOID, val.id], self.tys, )) } Expr::Directive { name: "as", args: [ty, expr], pos } => { let ty = self.ty(ty); let mut val = self.ptr_expr_ctx(expr, Ctx::default().with_ty(ty))?; if let Some(ity) = ctx.ty && ity.try_upcast(ty) == Some(ty) && val.ty == ity { _ = pos; //self.error(pos, "the type is known at this point, remove the hint"); } self.assert_ty(expr.pos(), &mut val, ty, "hinted expr"); Some(val) } Expr::Directive { pos, name: "error", args } => { let mut error_msg = String::new(); for arg in args { let Some(val) = self.expr(arg) else { self.error(arg.pos(), "unreachable argument"); continue; }; match self.ci.nodes[val.id].kind { Kind::Global { global } if let Ok(str) = core::str::from_utf8( &self.tys.ins.globals[global].data [..self.tys.ins.globals[global].data.len() - 1], ) => { error_msg.push_str(str) } Kind::CInt { value } if val.ty == ty::Id::TYPE => { _ = write!( error_msg, "{}", ty::Display::new(self.tys, self.files, ty::Id::from(value as u64)) ) } _ => _ = self.error(arg.pos(), "expression can not (yet) be displayed"), } } self.error(pos, error_msg); None } Expr::Directive { pos, name: "eca", args } => { inference!( ret_ty, ctx, self, pos, "return type", "@as(, @eca(...))" ); let mut inps = Vc::from([NEVER]); let arg_base = self.tys.tmp.args.len(); let mut clobbered_aliases = BitSet::default(); for arg in args { let value = self.expr(arg)?; self.add_clobbers(value, &mut clobbered_aliases); self.tys.tmp.args.push(value.ty); debug_assert_ne!(self.ci.nodes[value.id].kind, Kind::Stre); self.ci.nodes.lock(value.id); inps.push(value.id); } let args = self.tys.pack_args(arg_base).expect("TODO"); for &n in inps.iter().skip(1) { self.ci.nodes.unlock(n); } self.append_clobbers(&mut inps, &mut clobbered_aliases); let alt_value = match ret_ty.loc(self.tys) { Loc::Reg => None, Loc::Stack => { let stck = self.new_stack(pos, ret_ty); inps.push(stck); Some(Value::ptr(stck).ty(ret_ty)) } }; inps[0] = self.ci.ctrl.get(); self.ci.ctrl.set( self.ci.nodes.new_node_nop( ret_ty, Kind::Call { func: ty::Func::ECA, args, unreachable: false }, inps, ), &mut self.ci.nodes, ); self.add_clobber_stores(clobbered_aliases); Some(alt_value.unwrap_or_else(|| { let ret = self.ci.nodes.new_node_nop(ret_ty, Kind::RetVal, [self.ci.ctrl.get()]); Value::new(ret).ty(ret_ty) })) } Expr::Call { func, args, .. } => self.gen_call(func, args, false), Expr::Directive { name: "inline", args: [func, args @ ..], .. } => { self.gen_call(func, args, true) } Expr::List { pos, kind, ty, fields, .. } => { let rty = ty .map(|ty| self.ty(ty)) .or(ctx.ty.map(|ty| self.tys.inner_of(ty).unwrap_or(ty))) .map(ty::Id::expand); match (rty, kind) { (None, ListKind::Tuple) => { let arg_base = self.tys.tmp.args.len(); let mut values = Vec::with_capacity(fields.len()); for field in fields { let val = self.expr(field)?; self.tys.tmp.args.push(val.ty); self.ci.nodes.lock(val.id); values.push(val); } let Some(fields) = self.tys.pack_args(arg_base) else { return self.error(pos, "this tuple exceeded the reasonable limit"); }; let key = SymKey::Tuple(fields); let ty::Kind::Tuple(tupl) = self .tys .syms .get_or_insert(key, &mut self.tys.ins, |ins| { ins.tuples.push(TupleData { fields, ..Default::default() }).into() }) .expand() else { unreachable!() }; let mem = self.new_stack(pos, tupl.into()); let mut offs = OffsetIter::new(tupl, self.tys); for value in values { let (ty, offset) = offs.next_ty(self.tys).unwrap(); let mem = self.offset(mem, offset); self.ci.nodes.unlock(value.id); self.store_mem(mem, ty, value.id); } Some(Value::ptr(mem).ty(tupl.into())) } (None, ListKind::Array) => { let mut array_meta = None::<(ty::Id, Nid)>; let mut offset = 0; for field in fields { let (elem, mem) = match array_meta { Some((ty, mem)) => { (self.checked_expr(field, ty, "array element")?, mem) } None => { let expr = self.expr(field)?; let aty = self.tys.make_array(expr.ty, fields.len() as _); let mem = self.new_stack(pos, aty); array_meta = Some((expr.ty, mem)); (expr, mem) } }; let mem = self.offset(mem, offset); self.store_mem(mem, elem.ty, elem.id); offset += self.tys.size_of(elem.ty); } let Some((_, mem)) = array_meta else { return self.error(pos, "can not infer the type of the array element, \ array is empty, has no explicit type, and type is not obvious from the context"); }; Some(Value::ptr(mem).ty(self.ci.nodes[mem].ty)) } (Some(ty::Kind::Struct(s)), ListKind::Tuple) => { let mem = self.new_stack(pos, s.into()); let mut offs = OffsetIter::new(s, self.tys); for field in fields { let Some((ty, offset)) = offs.next_ty(self.tys) else { self.error( field.pos(), "this init argumen overflows the field count", ); break; }; let value = self.checked_expr(field, ty, "tuple field")?; let mem = self.offset(mem, offset); self.store_mem(mem, ty, value.id); } let field_list = offs .into_iter(self.tys) .map(|(f, ..)| self.tys.names.ident_str(f.name)) .intersperse(", ") .collect::(); if !field_list.is_empty() { self.error( pos, fa!("the struct initializer is missing {field_list} \ (append them to the end of the constructor)"), ); } Some(Value::ptr(mem).ty(s.into())) } (Some(kty), ListKind::Array) => { let (len, elem) = if ty.is_none() && let ty::Kind::Slice(a) = kty { let arr = &self.tys.ins.slices[a]; (arr.len().unwrap_or(fields.len()), arr.elem) } else { (fields.len(), kty.compress()) }; let elem_size = self.tys.size_of(elem); let aty = self.tys.make_array(elem, len as ArrayLen); if len != fields.len() { return self.error( pos, fa!( "expected '{}' but constructor has {} elements", self.ty_display(aty), fields.len() ), ); } let mem = self.new_stack(pos, aty); for (field, offset) in fields.iter().zip((0u32..).step_by(elem_size as usize)) { let value = self.checked_expr(field, elem, "array value")?; let mem = self.offset(mem, offset); self.store_mem(mem, elem, value.id); } Some(Value::ptr(mem).ty(aty)) } (Some(ty::Kind::Tuple(tupl)), ListKind::Tuple) => { let mem = self.new_stack(pos, tupl.into()); let mut offs = OffsetIter::new(tupl, self.tys); for field in fields { let Some((ty, offset)) = offs.next_ty(self.tys) else { self.error( field.pos(), "this init argumen overflows the field count", ); break; }; let value = self.checked_expr(field, ty, "tuple field")?; let mem = self.offset(mem, offset); self.store_mem(mem, ty, value.id); } Some(Value::ptr(mem).ty(tupl.into())) } (Some(t), ListKind::Tuple) => self.error( pos, fa!( "the {}type of the constructor is `{}`, but thats not a struct", if ty.is_some() { "" } else { "inferred " }, self.ty_display(t.compress()), ), ), } } Expr::Struct { .. } => { let value = self.ty(expr).repr(); Some(self.ci.nodes.new_const_lit(ty::Id::TYPE, value)) } Expr::Ctor { pos, ty, fields, .. } => { ctx.ty = ty .map(|ty| self.ty(ty)) .or(ctx.ty.map(|ty| self.tys.inner_of(ty).unwrap_or(ty))); inference!(sty, ctx, self, pos, "struct", ".{...}"); match sty.expand() { ty::Kind::Union(u) => { let &[CtorField { pos: fpos, name, value }] = fields else { return self.error( pos, fa!("union initializer needs to have exactly one field"), ); }; let mem = self.new_stack(pos, sty); let Some((_, field)) = self.tys.find_union_field(u, name) else { return self.error( fpos, fa!("union '{}' does not have this field", self.ty_display(sty)), ); }; let (ty, offset) = (field.ty, 0); let value = self.checked_expr(&value, ty, fa!("field {}", name))?; let mem = self.offset(mem, offset); self.store_mem(mem, ty, value.id); Some(Value::ptr(mem).ty(sty)) } ty::Kind::Struct(s) => { let mut offs = OffsetIter::new(s, self.tys) .into_iter(self.tys) .map(|(f, o)| (f.ty, o)) .collect::>(); let mem = self.new_stack(pos, sty); for field in fields { let Some(index) = self.tys.find_struct_field(s, field.name) else { self.error( field.pos, fa!( "struct '{}' does not have this field", self.ty_display(sty) ), ); continue; }; let (ty, offset) = mem::replace(&mut offs[index], (ty::Id::UNDECLARED, field.pos)); if ty == ty::Id::UNDECLARED { self.error(field.pos, "the struct field is already initialized"); self.error(offset, "previous initialization is here"); continue; } let value = self.checked_expr(&field.value, ty, fa!("field {}", field.name))?; let mem = self.offset(mem, offset); self.store_mem(mem, ty, value.id); } for (i, &mut (ref mut ty, offset)) in self.tys.struct_field_range(s).zip(&mut offs) { if *ty == ty::Id::UNDECLARED { continue; } let field = &self.tys.ins.struct_fields[i]; let Some(deva) = field.default_value else { continue }; let value = self.gen_const(deva, Ctx::default().with_ty(*ty))?; let mem = self.offset(mem, offset); self.store_mem(mem, *ty, value.id); *ty = ty::Id::UNDECLARED; } let field_list = self .tys .struct_fields(s) .iter() .zip(offs) .filter(|&(_, (ty, _))| ty != ty::Id::UNDECLARED) .map(|(f, _)| self.tys.names.ident_str(f.name)) .intersperse(", ") .collect::(); if !field_list.is_empty() { self.error(pos, fa!("the struct initializer is missing {field_list}")); } Some(Value::ptr(mem).ty(sty)) } _ => self.error( pos, fa!( "the {}type of the constructor is `{}`, \ but thats not a struct or union", if ty.is_some() { "" } else { "inferred " }, self.ty_display(sty) ), ), } } Expr::Block { stmts, .. } => { let base = self.ci.scope.vars.len(); let aclass_base = self.ci.scope.aclasses.len(); let defer_base = self.ci.defers.len(); let mut ret = Some(Value::VOID); for stmt in stmts { ret = ret.and(self.expr_ctx(stmt, Ctx::default().with_ty(ty::Id::VOID))); if let Some(id) = ret { if id.ty != ty::Id::VOID { self.warn( stmt.pos(), fa!( "statements need to evaluate to 'void', \ but this statements evaluates '{}', \ use '_ = ' to discard the value if its intentional", self.ty_display(id.ty) ), ); } } else { break; } } if ret.is_some() { self.gen_defers(defer_base); } self.ci.defers.truncate(defer_base); for var in self.ci.scope.vars.drain(base..) { var.remove(&mut self.ci.nodes); } for aclass in self.ci.scope.aclasses.drain(aclass_base..) { aclass.remove(&mut self.ci.nodes); } ret } Expr::Loop { unrolled: true, body, pos } => { let mut loop_fuel = 100; self.ci .loops .push(Loop::Comptime { state: None, defer_base: self.ci.defers.len() }); loop { if loop_fuel == 0 { return self.error( pos, "unrolled loop exceeded 100 iterations, use normal loop instead, TODO: add escape hatch", ); } loop_fuel -= 1; let terminated = self.expr(body).is_none(); let Some(&Loop::Comptime { state, .. }) = self.ci.loops.last() else { unreachable!() }; if !terminated && let Some((_, prev)) = state { self.error( pos, "reached a constrol flow keyword inside an unrolled loop, \ as well ast the end of the loop, make sure control flow is \ not dependant on a runtime value", ); return self.error(prev, "previous reachable control flow found here"); } match state { Some((CtLoopState::Terminated, _)) => break, Some((CtLoopState::Continued, _)) | None => {} } } self.ci.loops.pop().unwrap(); Some(Value::VOID) } Expr::Loop { body, unrolled: false, .. } => { self.ci.ctrl.set( self.ci.nodes.new_node( ty::Id::VOID, Kind::Loop, [self.ci.ctrl.get(), self.ci.ctrl.get(), LOOPS], self.tys, ), &mut self.ci.nodes, ); self.ci.loops.push(Loop::Runtime { node: self.ci.ctrl.get(), ctrl: [StrongRef::DEFAULT; 2], ctrl_scope: core::array::from_fn(|_| Default::default()), scope: self.ci.scope.dup(&mut self.ci.nodes), defer_base: self.ci.defers.len(), }); for var in self.ci.scope.vars.iter_mut().skip(self.ci.inline_var_base) { if var.ty != ty::Id::TYPE && !var.ptr { var.set_value(VOID, &mut self.ci.nodes); } } for aclass in self.ci.scope.aclasses[..2].iter_mut() { aclass.last_store.set(VOID, &mut self.ci.nodes); } for aclass in self.ci.scope.aclasses.iter_mut().skip(self.ci.inline_aclass_base) { aclass.last_store.set(VOID, &mut self.ci.nodes); } self.expr(body); let Some(Loop::Runtime { ctrl: [con, ..], ctrl_scope: [cons, ..], .. }) = self.ci.loops.last_mut() else { unreachable!() }; let mut cons = mem::take(cons); if let Some(con) = mem::take(con).unwrap(&mut self.ci.nodes) { self.ci.ctrl.set( self.ci.nodes.new_node( ty::Id::VOID, Kind::Region, [con, self.ci.ctrl.get()], self.tys, ), &mut self.ci.nodes, ); self.ci.nodes.merge_scopes( &mut self.ci.loops, &self.ci.ctrl, &mut self.ci.scope, &mut cons, self.tys, ); cons.clear(&mut self.ci.nodes); } let Some(Loop::Runtime { node, ctrl: [.., bre], ctrl_scope: [.., mut bres], mut scope, defer_base, }) = self.ci.loops.pop() else { unreachable!() }; self.gen_defers(defer_base); self.ci.defers.truncate(defer_base); self.ci.nodes.modify_input(node, 1, self.ci.ctrl.get()); if let Some(idx) = self.ci.nodes[node].outputs.iter().position(|&n| self.ci.nodes.is_cfg(n)) { self.ci.nodes[node].outputs.swap(idx, 0); } let Some(bre) = bre.unwrap(&mut self.ci.nodes) else { for (loop_var, scope_var) in self.ci.scope.vars.iter_mut().zip(scope.vars.iter_mut()) { if self.ci.nodes[scope_var.value()].is_lazy_phi(node) { if loop_var.value() != scope_var.value() { scope_var.set_value( self.ci.nodes.modify_input( scope_var.value(), 2, loop_var.value(), ), &mut self.ci.nodes, ); } else { let phi = &self.ci.nodes[scope_var.value()]; let prev = phi.inputs[1]; self.ci.nodes.replace(scope_var.value(), prev); scope_var.set_value(prev, &mut self.ci.nodes); } } } for (loop_class, scope_class) in self.ci.scope.aclasses.iter_mut().zip(scope.aclasses.iter_mut()) { if self.ci.nodes[scope_class.last_store.get()].is_lazy_phi(node) { if loop_class.last_store.get() != scope_class.last_store.get() && loop_class.last_store.get() != 0 { scope_class.last_store.set( self.ci.nodes.modify_input( scope_class.last_store.get(), 2, loop_class.last_store.get(), ), &mut self.ci.nodes, ); } else { let phi = &self.ci.nodes[scope_class.last_store.get()]; let prev = phi.inputs[1]; self.ci.nodes.replace(scope_class.last_store.get(), prev); scope_class.last_store.set(prev, &mut self.ci.nodes); } } if loop_class.last_store.get() == 0 { loop_class .last_store .set(scope_class.last_store.get(), &mut self.ci.nodes); } } debug_assert!(self.ci.scope.aclasses.iter().all(|a| a.last_store.get() != 0)); scope.clear(&mut self.ci.nodes); self.ci.ctrl.set(NEVER, &mut self.ci.nodes); return None; }; self.ci.ctrl.set(bre, &mut self.ci.nodes); mem::swap(&mut self.ci.scope, &mut bres); debug_assert_eq!(self.ci.scope.vars.len(), scope.vars.len()); debug_assert_eq!(self.ci.scope.vars.len(), bres.vars.len()); self.ci.nodes.lock(node); for ((dest_var, scope_var), loop_var) in self .ci .scope .vars .iter_mut() .zip(scope.vars.iter_mut()) .zip(bres.vars.iter_mut()) { if self.ci.nodes[scope_var.value()].is_lazy_phi(node) { if loop_var.value() != scope_var.value() && loop_var.value() != 0 { scope_var.set_value( self.ci.nodes.modify_input(scope_var.value(), 2, loop_var.value()), &mut self.ci.nodes, ); } else { if dest_var.value() == scope_var.value() { dest_var.set_value(VOID, &mut self.ci.nodes); } let phi = &self.ci.nodes[scope_var.value()]; let prev = phi.inputs[1]; self.ci.nodes.replace(scope_var.value(), prev); scope_var.set_value(prev, &mut self.ci.nodes); } } if dest_var.value() == VOID { dest_var.set_value(scope_var.value(), &mut self.ci.nodes); } debug_assert!(!self.ci.nodes[dest_var.value()].is_lazy_phi(node)); } for ((dest_class, scope_class), loop_class) in self .ci .scope .aclasses .iter_mut() .zip(scope.aclasses.iter_mut()) .zip(bres.aclasses.iter_mut()) { if self.ci.nodes[scope_class.last_store.get()].is_lazy_phi(node) { if loop_class.last_store.get() != scope_class.last_store.get() && loop_class.last_store.get() != 0 { scope_class.last_store.set( self.ci.nodes.modify_input( scope_class.last_store.get(), 2, loop_class.last_store.get(), ), &mut self.ci.nodes, ); } else { if dest_class.last_store.get() == scope_class.last_store.get() { dest_class.last_store.set(VOID, &mut self.ci.nodes); } let phi = &self.ci.nodes[scope_class.last_store.get()]; let prev = phi.inputs[1]; self.ci.nodes.replace(scope_class.last_store.get(), prev); scope_class.last_store.set(prev, &mut self.ci.nodes); } } if dest_class.last_store.get() == VOID { dest_class.last_store.set(scope_class.last_store.get(), &mut self.ci.nodes); } debug_assert!( !self.ci.nodes[dest_class.last_store.get()].is_lazy_phi(node), "{:?}", self.ci.nodes[dest_class.last_store.get()] ); } scope.clear(&mut self.ci.nodes); bres.clear(&mut self.ci.nodes); self.ci.nodes.unlock(node); let rpl = self.ci.nodes.late_peephole(node, self.tys).unwrap_or(node); if self.ci.ctrl.get() == node { self.ci.ctrl.set_remove(rpl, &mut self.ci.nodes); } Some(Value::VOID) } Expr::Break { pos } => self.jump_to(pos, 1), Expr::Continue { pos } => self.jump_to(pos, 0), Expr::If { cond, then, else_, .. } => { let cnd = self.checked_expr(cond, ty::Id::BOOL, "condition")?; let if_node = self.ci.nodes.new_node( ty::Id::VOID, Kind::If, [self.ci.ctrl.get(), cnd.id], self.tys, ); 'b: { let branch = match self.ci.nodes[if_node].ty { ty::Id::LEFT_UNREACHABLE => else_, ty::Id::RIGHT_UNREACHABLE => Some(then), _ => break 'b, }; self.ci.nodes.remove(if_node); if let Some(branch) = branch { return self.expr(branch); } else { return Some(Value::VOID); } } let else_scope = self.ci.scope.dup(&mut self.ci.nodes); self.ci.ctrl.set( self.ci.nodes.new_node(ty::Id::VOID, Kind::Then, [if_node], self.tys), &mut self.ci.nodes, ); let lcntrl = self.expr(then).map_or(Nid::MAX, |_| self.ci.ctrl.get()); let then_scope = mem::replace(&mut self.ci.scope, else_scope); self.ci.ctrl.set( self.ci.nodes.new_node(ty::Id::VOID, Kind::Else, [if_node], self.tys), &mut self.ci.nodes, ); let rcntrl = if let Some(else_) = else_ { self.expr(else_).map_or(Nid::MAX, |_| self.ci.ctrl.get()) } else { self.ci.ctrl.get() }; self.close_if(lcntrl, rcntrl, then_scope)?; Some(Value::VOID) } Expr::Match { pos, value, branches } => { let value = self.expr(value)?; let ty::Kind::Enum(e) = value.ty.expand() else { return self.error( pos, fa!( "match operates on enums (for now), '{}' is not an enum", self.ty_display(value.ty) ), ); }; let mut covered_values = vec![Pos::MAX; self.tys.enum_field_range(e).len()]; let mut else_branch = None::; if let Kind::CInt { value: cnst } = self.ci.nodes[value.id].kind { let mut matching_branch = None::; for &MatchBranch { pat, pos: bp, body } in branches { if let Expr::Wildcard { .. } = pat { if let Some(prev) = else_branch { self.error(bp, "duplicate branch"); self.error(prev.pos(), "...first branch declared here"); } else_branch = Some(body); continue; } let pat_val = self.eval_const(self.ci.file, self.ci.parent, &pat, value.ty); if covered_values[pat_val as usize] != Pos::MAX { self.error(bp, "duplicate branch"); self.error( covered_values[pat_val as usize], "...first branch declared here", ); continue; } covered_values[pat_val as usize] = bp; if pat_val == cnst as u64 { matching_branch = Some(body); } } if let Some(body) = matching_branch.or(else_branch) { self.expr(&body)?; } } else { let mut scopes = vec![]; for &MatchBranch { pat, pos: bp, body } in branches { if let Expr::Wildcard { .. } = pat { if let Some(prev) = else_branch { self.error(bp, "duplicate branch"); self.error(prev.pos(), "...first branch declared here"); } else_branch = Some(body); continue; } let pat_val = self.eval_const(self.ci.file, self.ci.parent, &pat, value.ty); if covered_values[pat_val as usize] != Pos::MAX { self.error(bp, "duplicate branch"); self.error( covered_values[pat_val as usize], "...first branch declared here", ); continue; } covered_values[pat_val as usize] = bp; let pat_val = self.ci.nodes.new_const(value.ty, pat_val as i64); let cnd = self.ci.nodes.new_node( ty::Id::BOOL, Kind::BinOp { op: TokenKind::Eq }, [VOID, value.id, pat_val], self.tys, ); let if_node = self.ci.nodes.new_node( ty::Id::VOID, Kind::If, [self.ci.ctrl.get(), cnd], self.tys, ); let cached_scope = self.ci.scope.dup(&mut self.ci.nodes); self.ci.ctrl.set( self.ci.nodes.new_node(ty::Id::VOID, Kind::Then, [if_node], self.tys), &mut self.ci.nodes, ); let ctrl = self.expr(&body).map_or(Nid::MAX, |_| self.ci.ctrl.get()); scopes.push((ctrl, mem::replace(&mut self.ci.scope, cached_scope))); self.ci.ctrl.set( self.ci.nodes.new_node(ty::Id::VOID, Kind::Else, [if_node], self.tys), &mut self.ci.nodes, ); } let mut rcntrl = if let Some(ebr) = else_branch { self.expr(&ebr).map_or(Nid::MAX, |_| self.ci.ctrl.get()) } else { self.ci.ctrl.get() }; for (lcntrl, then_scope) in scopes.into_iter().rev() { if let Some(v) = self.close_if(lcntrl, rcntrl, then_scope) && v != VOID { rcntrl = v; } } if rcntrl == Nid::MAX { return None; } } if else_branch.is_none() { let missing_branches = covered_values .into_iter() .zip(self.tys.enum_fields(e)) .filter(|&(f, _)| f == Pos::MAX) .map(|(_, f)| self.tys.names.ident_str(f.name)) .intersperse("', '") .collect::(); if !missing_branches.is_empty() { self.error(pos, fa!("not all cases covered, missing '{missing_branches}'")); } } Some(Value::VOID) } ref e => { let ty = self.parse_ty( TyScope { file: self.ci.file, parent: self.ci.parent, ..Default::default() }, e, ); Some(self.ci.nodes.new_const_lit(ty::Id::TYPE, ty)) } } } fn spill(&mut self, pos: Pos, value: &mut Value) { debug_assert!(!value.ptr); let stck = self.new_stack(pos, value.ty); self.store_mem(stck, value.ty, value.id); value.id = stck; value.ptr = true; } fn checked_expr( &mut self, expr: &Expr, expected_ty: ty::Id, hint: impl Display, ) -> Option { let mut value = self.ptr_expr_ctx(expr, Ctx::default().with_ty(expected_ty))?; self.assert_ty(expr.pos(), &mut value, expected_ty, hint); self.strip_ptr(&mut value); Some(value) } fn gen_field( &mut self, ctx: Ctx, target: &Expr, pos: Pos, name: &str, ) -> Option> { let mut vtarget = self.ptr_expr(target)?; self.implicit_unwrap(pos, &mut vtarget); let tty = vtarget.ty; match self.tys.base_of(tty).unwrap_or(tty).expand() { ty::Kind::Module(m) => self.find_type_as_value(pos, m, name, ctx), ty::Kind::Enum(e) => { let intrnd = self.tys.names.project(name); self.gen_enum_variant(pos, e, intrnd) } ty::Kind::Union(u) => { if let Some((_, f)) = self.tys.find_union_field(u, name) { Some(Value::ptr(vtarget.id).ty(f.ty)) } else if let ty = self.find_type(pos, self.ci.file, u, name) && let ty::Kind::Func(_) = ty.expand() { return Some(Err((ty, vtarget))); } else { self.error( pos, fa!( "the '{}' does not have this field, \ but it does have '{}'", self.ty_display(tty), self.tys .union_fields(u) .iter() .map(|f| self.tys.names.ident_str(f.name)) .intersperse("', '") .collect::() ), ) } } ty::Kind::Struct(s) => { if let Some((offset, ty)) = OffsetIter::offset_of(self.tys, s, name) { Some(Value::ptr(self.offset(vtarget.id, offset)).ty(ty)) } else if let ty = self.find_type(pos, self.ci.file, s, name) && let ty::Kind::Func(_) = ty.expand() { return Some(Err((ty, vtarget))); } else { self.error( pos, fa!( "the '{}' does not have this field, \ but it does have '{}'", self.ty_display(tty), self.tys .struct_fields(s) .iter() .map(|f| self.tys.names.ident_str(f.name)) .intersperse("', '") .collect::() ), ) } } ty::Kind::TYPE => match self.ci.nodes.as_ty(vtarget.id).expand() { ty::Kind::Module(m) => self.find_type_as_value(pos, m, name, ctx), ty::Kind::Enum(e) if let intrnd = self.tys.names.project(name) && let Some(index) = self.tys.enum_fields(e).iter().position(|f| Some(f.name) == intrnd) => { Some(self.ci.nodes.new_const_lit(e.into(), index as i64)) } ty @ (ty::Kind::Struct(_) | ty::Kind::Enum(_) | ty::Kind::Union(_)) => { self.find_type_as_value(pos, ty.compress(), name, ctx) } ty @ (ty::Kind::Builtin(_) | ty::Kind::Ptr(_) | ty::Kind::Slice(_) | ty::Kind::Opt(_) | ty::Kind::Func(_) | ty::Kind::Template(_) | ty::Kind::Global(_) | ty::Kind::Tuple(_) | ty::Kind::Const(_)) => self.error( pos, fa!( "accesing scope on '{}' is not supported yet", self.ty_display(ty.compress()) ), ), }, _ => self.error( pos, fa!( "the '{}' is not a struct, or pointer to one, or enum, \ fo field access does not make sense", self.ty_display(tty) ), ), } .map(Ok) } fn close_if(&mut self, lcntrl: Nid, rcntrl: Nid, mut then_scope: Scope) -> Option { if lcntrl == Nid::MAX && rcntrl == Nid::MAX { then_scope.clear(&mut self.ci.nodes); return None; } else if lcntrl == Nid::MAX { then_scope.clear(&mut self.ci.nodes); return Some(VOID); } else if rcntrl == Nid::MAX { self.ci.scope.clear(&mut self.ci.nodes); self.ci.scope = then_scope; self.ci.ctrl.set(lcntrl, &mut self.ci.nodes); return Some(VOID); } self.ci.ctrl.set( self.ci.nodes.new_node(ty::Id::VOID, Kind::Region, [lcntrl, rcntrl], self.tys), &mut self.ci.nodes, ); self.ci.nodes.merge_scopes( &mut self.ci.loops, &self.ci.ctrl, &mut self.ci.scope, &mut then_scope, self.tys, ); then_scope.clear(&mut self.ci.nodes); Some(self.ci.ctrl.get()) } fn gen_enum_variant(&mut self, pos: Pos, e: ty::Enum, intrnd: Option) -> Option { let Some(index) = self.tys.enum_fields(e).iter().position(|f| Some(f.name) == intrnd) else { return self.error( pos, fa!( "the '{}' does not have this variant, \ but it does have '{}'", self.ty_display(e.into()), self.tys .enum_fields(e) .iter() .map(|f| self.tys.names.ident_str(f.name)) .intersperse("', '") .collect::() ), ); }; Some(self.ci.nodes.new_const_lit(e.into(), index as i64)) } fn gen_inferred_const( &mut self, ctx: Ctx, fallback: ty::Id, value: impl Into, ) -> Option { self.gen_inferred_const_low(ctx, fallback, value, false) } fn gen_inferred_const_low( &mut self, ctx: Ctx, fallback: ty::Id, value: impl Into, is_float: bool, ) -> Option { let ty = ctx .ty .map(|ty| self.tys.inner_of(ty).unwrap_or(ty)) .filter(|&ty| (ty.is_integer() && !is_float) || ty.is_float()) .unwrap_or(fallback); let value = value.into(); Some(self.ci.nodes.new_const_lit( ty, if ty.is_float() && !is_float { (value as f64).to_bits() as i64 } else { value }, )) } fn gen_call(&mut self, func: &Expr, args: &[Expr], mut inline: bool) -> Option { let (ty, mut caller) = match *func { Expr::Field { target, pos, name } => { match self.gen_field(Ctx::default(), target, pos, name)? { Ok(mut fexpr) => { self.assert_ty(func.pos(), &mut fexpr, ty::Id::TYPE, "function"); (self.ci.nodes.as_ty(fexpr.id), None) } Err((ty, val)) => (ty, Some(val)), } } ref e => (self.ty(e), None), }; let Some(fu) = self.compute_signature(ty, func.pos(), args) else { return Value::NEVER; }; let FuncData { expr, file, is_inline, parent, sig, .. } = self.tys.ins.funcs[fu]; let ast = &self.files[file]; let &Expr::Closure { args: cargs, body, .. } = expr.get(ast) else { unreachable!() }; let arg_count = args.len() + caller.is_some() as usize; if arg_count != cargs.len() { self.error( func.pos(), fa!( "expected {} function argumenr{}, got {}", cargs.len(), if cargs.len() == 1 { "" } else { "s" }, arg_count ), ); } if inline && is_inline { self.error( func.pos(), "function is declared as inline so this @inline directive only reduces readability", ); } inline |= sig.ret == ty::Id::TYPE; let (mut tys, mut args, mut cargs) = (sig.args.args(), args.iter(), cargs.iter()); if is_inline || inline { let var_base = self.ci.scope.vars.len(); let aclass_base = self.ci.scope.aclasses.len(); if let Some(caller) = &mut caller && let (Some(Arg::Value(ty)), Some(carg)) = (tys.next(self.tys), cargs.next()) { match (caller.ty.is_pointer(), ty.is_pointer()) { (true, false) => { caller.ty = self.tys.base_of(caller.ty).unwrap(); caller.ptr = true; } (false, true) => { caller.ty = self.tys.make_ptr(caller.ty); caller.ptr = false; } _ => {} } self.assert_ty(func.pos(), caller, ty, "caller"); self.ci.scope.vars.push(Variable::new( carg.id, ty, caller.ptr, caller.id, &mut self.ci.nodes, )) } while let (Some(aty), Some(arg)) = (tys.next(self.tys), args.next()) { let carg = cargs.next().unwrap(); let var = match aty { Arg::Type(id) => Variable::new( carg.id, ty::Id::TYPE, false, self.ci.nodes.new_const(ty::Id::TYPE, id), &mut self.ci.nodes, ), Arg::Value(ty) => { let mut value = self.ptr_expr_ctx(arg, Ctx::default().with_ty(ty))?; debug_assert_ne!(self.ci.nodes[value.id].kind, Kind::Stre); debug_assert_ne!(value.id, 0); self.assert_ty(arg.pos(), &mut value, ty, fa!("argument {}", carg.name)); Variable::new(carg.id, ty, value.ptr, value.id, &mut self.ci.nodes) } }; self.ci.scope.vars.push(var); } let prev_var_base = mem::replace(&mut self.ci.inline_var_base, var_base); let prev_aclass_base = mem::replace(&mut self.ci.inline_aclass_base, aclass_base); let prev_defer_base = mem::replace(&mut self.ci.inline_defer_base, self.ci.defers.len()); let prev_inline_ret = self.ci.inline_ret.take(); self.ci.inline_depth += 1; let prev_ret = self.ci.ret.replace(sig.ret); let prev_file = mem::replace(&mut self.ci.file, file); let prev_parent = mem::replace(&mut self.ci.parent, parent); let prev_ctrl = self.ci.ctrl.get(); if self.expr(body).is_some() { if sig.ret == ty::Id::VOID { self.expr(&Expr::Return { pos: body.pos(), val: None }); } else { self.error( body.pos(), "expected all paths in the fucntion to return \ or the return type to be 'void'", ); } } self.ci.ret = prev_ret; self.ci.inline_depth -= 1; self.ci.inline_var_base = prev_var_base; self.ci.inline_aclass_base = prev_aclass_base; self.ci.inline_defer_base = prev_defer_base; for var in self.ci.scope.vars.drain(var_base..) { var.remove(&mut self.ci.nodes); } for var in self.ci.scope.aclasses.drain(aclass_base..) { var.remove(&mut self.ci.nodes); } let (v, ctrl, mut scope, aclass) = mem::replace(&mut self.ci.inline_ret, prev_inline_ret)?; if is_inline && ctrl.get() != prev_ctrl && (!self.ci.nodes[ctrl.get()].kind.is_call() || self.ci.nodes[ctrl.get()].inputs[0] != prev_ctrl) { self.error(body.pos(), "function is makred inline but it contains controlflow"); } // this is here because we report error in the inline function file self.ci.file = prev_file; self.ci.parent = prev_parent; scope.vars.drain(var_base..).for_each(|v| v.remove(&mut self.ci.nodes)); scope.aclasses.drain(aclass_base..).for_each(|v| v.remove(&mut self.ci.nodes)); self.ci.nodes.unlock(v.id); self.ci.scope.clear(&mut self.ci.nodes); self.ci.scope = scope; if let Some(aclass) = aclass { let (_, reg) = self.ci.nodes.aclass_index(v.id); self.ci.nodes[reg].aclass = self.ci.scope.aclasses.len() as _; self.ci.scope.aclasses.push(aclass); } mem::replace(&mut self.ci.ctrl, ctrl).remove(&mut self.ci.nodes); Some(v) } else { self.make_func_reachable(fu); let mut inps = Vc::from([NEVER]); let mut clobbered_aliases = BitSet::default(); if let Some(caller) = &mut caller && let (Some(Arg::Value(ty)), Some(carg)) = (tys.next(self.tys), cargs.next()) { match (caller.ty.is_pointer(), ty.is_pointer()) { (true, false) => { caller.ty = self.tys.base_of(caller.ty).unwrap(); caller.ptr = true; } (false, true) => { caller.ty = self.tys.make_ptr(caller.ty); caller.ptr = false; } _ => {} } self.assert_ty(func.pos(), caller, ty, fa!("caller argument {}", carg.name)); self.strip_ptr(caller); self.add_clobbers(*caller, &mut clobbered_aliases); self.ci.nodes.lock(caller.id); inps.push(caller.id); } while let (Some(ty), Some(arg)) = (tys.next(self.tys), args.next()) { let carg = cargs.next().unwrap(); let Arg::Value(ty) = ty else { continue }; let value = self.checked_expr(arg, ty, fa!("argument {}", carg.name))?; debug_assert_ne!(self.ci.nodes[value.id].kind, Kind::Stre); self.add_clobbers(value, &mut clobbered_aliases); self.ci.nodes.lock(value.id); inps.push(value.id); } for &n in inps.iter().skip(1) { self.ci.nodes.unlock(n); } self.append_clobbers(&mut inps, &mut clobbered_aliases); let alt_value = match sig.ret.loc(self.tys) { Loc::Reg => None, Loc::Stack => { let stck = self.new_stack(func.pos(), sig.ret); clobbered_aliases.set(self.ci.nodes.aclass_index(stck).0 as _); inps.push(stck); Some(Value::ptr(stck).ty(sig.ret)) } }; inps[0] = self.ci.ctrl.get(); self.ci.ctrl.set( self.ci.nodes.new_node_nop( sig.ret, Kind::Call { func: fu, args: sig.args, unreachable: sig.ret == ty::Id::NEVER }, inps, ), &mut self.ci.nodes, ); self.add_clobber_stores(clobbered_aliases); if sig.ret == ty::Id::NEVER { self.ci.nodes.bind(self.ci.ctrl.get(), NEVER); self.ci.ctrl.set(NEVER, &mut self.ci.nodes); return None; } Some(alt_value.unwrap_or_else(|| { let ret = self.ci.nodes.new_node_nop(sig.ret, Kind::RetVal, [self.ci.ctrl.get()]); Value::new(ret).ty(sig.ret) })) } } fn gen_global(&mut self, global: ty::Global) -> Option { let gl = &self.tys.ins.globals[global]; let value = self.ci.nodes.new_node_nop(gl.ty, Kind::Global { global }, [VOID]); self.ci.nodes[value].aclass = GLOBAL_ACLASS as _; Some(Value::ptr(value).ty(gl.ty)) } fn gen_const(&mut self, cnst: ty::Const, ctx: Ctx) -> Option { let c = &self.tys.ins.consts[cnst]; let prev_file = mem::replace(&mut self.ci.file, c.file); let prev_parent = mem::replace(&mut self.ci.parent, c.parent); let f = &self.files[c.file]; let value = match c.ast.get(f) { &Expr::BinOp { left, op: TokenKind::Decl, right, .. } | &Expr::BinOp { left, op: TokenKind::Colon, right: &Expr::BinOp { right, .. }, .. } => left .find_pattern_path(c.name, right, |expr, is_ct| { debug_assert!(is_ct); self.ptr_expr_ctx(expr, ctx) }) .unwrap_or_else(|_| unreachable!()), v => self.ptr_expr_ctx(v, ctx), }?; self.ci.file = prev_file; self.ci.parent = prev_parent; Some(value) } fn add_clobbers(&mut self, value: Value, clobbered_aliases: &mut BitSet) { if let Some(base) = self.tys.base_of(value.ty) { clobbered_aliases.set(self.ci.nodes.aclass_index(value.id).0 as _); if base.has_pointers(self.tys) { clobbered_aliases.set(DEFAULT_ACLASS as _); } } else if value.ty.has_pointers(self.tys) { clobbered_aliases.set(DEFAULT_ACLASS as _); } } fn append_clobbers(&mut self, inps: &mut Vc, clobbered_aliases: &mut BitSet) { clobbered_aliases.set(GLOBAL_ACLASS as _); for clobbered in clobbered_aliases.iter() { let aclass = &mut self.ci.scope.aclasses[clobbered]; self.ci.nodes.load_loop_aclass(clobbered, aclass, &mut self.ci.loops); inps.push(aclass.last_store.get()); } } fn add_clobber_stores(&mut self, clobbered_aliases: BitSet) { for clobbered in clobbered_aliases.iter() { debug_assert_matches!(self.ci.nodes[self.ci.ctrl.get()].kind, Kind::Call { .. }); self.ci.scope.aclasses[clobbered].clobber.set(self.ci.ctrl.get(), &mut self.ci.nodes); } self.ci.nodes[self.ci.ctrl.get()].clobbers = clobbered_aliases; } fn struct_op( &mut self, pos: Pos, op: TokenKind, s: ty::Struct, dst: Nid, lhs: Nid, rhs: Nid, ) -> bool { let mut offs = OffsetIter::new(s, self.tys); while let Some((ty, off)) = offs.next_ty(self.tys) { let lhs = self.offset(lhs, off); let rhs = self.offset(rhs, off); let dst = self.offset(dst, off); match ty.expand() { _ if ty.is_pointer() || ty.is_integer() || ty == ty::Id::BOOL => { let lhs = self.load_mem(lhs, ty); let rhs = self.load_mem(rhs, ty); let res = self.ci.nodes.new_node(ty, Kind::BinOp { op }, [VOID, lhs, rhs], self.tys); self.store_mem(dst, ty, res); } ty::Kind::Struct(is) => { if !self.struct_op(pos, op, is, dst, lhs, rhs) { self.error( pos, fa!("... when appliing '{0} {op} {0}'", self.ty_display(s.into())), ); } } _ => { _ = self.error(pos, fa!("'{0} {op} {0}' is not supported", self.ty_display(ty))) } } } true } fn struct_fold_op( &mut self, pos: Pos, op: TokenKind, fold_op: TokenKind, s: ty::Struct, lhs: Nid, rhs: Nid, ) -> Option { debug_assert!(op.is_compatison()); let mut fold = None; let mut offs = OffsetIter::new(s, self.tys); while let Some((ty, off)) = offs.next_ty(self.tys) { let lhs = self.offset(lhs, off); let rhs = self.offset(rhs, off); let vl = match ty.expand() { _ if ty.is_pointer() || ty.is_integer() || ty == ty::Id::BOOL => { let lhs = self.load_mem(lhs, ty); let rhs = self.load_mem(rhs, ty); self.ci.nodes.new_node( ty::Id::BOOL, Kind::BinOp { op }, [VOID, lhs, rhs], self.tys, ) } ty::Kind::Struct(is) => match self.struct_fold_op(pos, op, fold_op, is, lhs, rhs) { Some(v) => v.id, None => { self.error( pos, fa!("...when appliing '{0} {op} {0}'", self.ty_display(s.into())), ); return None; } }, _ => { self.error(pos, fa!("'{0} {op} {0}' is not supported", self.ty_display(ty))); return None; } }; fold = Some(match fold { None => vl, Some(o) => self.ci.nodes.new_node( ty::Id::BOOL, Kind::BinOp { op: fold_op }, [VOID, o, vl], self.tys, ), }); } match op { _ if let Some(fold) = fold => Some(Value::new(fold).ty(ty::Id::BOOL)), TokenKind::Eq => Some(self.ci.nodes.new_const_lit(ty::Id::BOOL, true)), _ => Some(self.ci.nodes.new_const_lit(ty::Id::BOOL, false)), } } fn compute_signature(&mut self, func: ty::Id, pos: Pos, args: &[Expr]) -> Option { let template = match func.expand() { ty::Kind::Func(f) => return Some(f), ty::Kind::Template(t) => t, ty::Kind::NEVER => return None, _ => { self.error(pos, fa!("compiler cant (yet) call '{}'", self.ty_display(func))); return None; } }; let TemplateData { file, expr, parent, name, is_inline, .. } = self.tys.ins.templates[template]; let fast = &self.files[file]; let &Expr::Closure { pos, args: cargs, ret, .. } = expr.get(fast) else { unreachable!(); }; let arg_base = self.tys.tmp.args.len(); let base = self.ci.scope.vars.len(); for (arg, carg) in args.iter().zip(cargs) { let ty = self.ty_in(file, parent, &carg.ty); self.tys.tmp.args.push(ty); let sym = parser::find_symbol(&fast.symbols, carg.id); if ty == ty::Id::ANY_TYPE { let ty = self.infer_type(arg); *self.tys.tmp.args.last_mut().unwrap() = ty; self.ci.scope.vars.push(Variable::new( carg.id, ty, false, NEVER, &mut self.ci.nodes, )); } else if sym.flags & idfl::COMPTIME == 0 { self.ci.scope.vars.push(Variable::new( carg.id, ty, false, NEVER, &mut self.ci.nodes, )); } else { if ty != ty::Id::TYPE { self.error( arg.pos(), fa!( "arbitrary comptime types are not supported yet \ (expected '{}' got '{}')", self.ty_display(ty::Id::TYPE), self.ty_display(ty) ), ); return None; } let ty = self.ty(arg); self.tys.tmp.args.push(ty); self.ci.scope.vars.push(Variable::new( carg.id, ty::Id::TYPE, false, self.ci.nodes.new_const(ty::Id::TYPE, ty), &mut self.ci.nodes, )); } } let Some(args) = self.tys.pack_args(arg_base) else { self.error(pos, "function instance has too many arguments"); return None; }; let ret = self.ty_in(file, parent, ret); self.ci.scope.vars.drain(base..).for_each(|v| v.remove(&mut self.ci.nodes)); let sym = SymKey::Type(parent, pos, args); let ct = |ins: &mut TypeIns| { ins.funcs .push(FuncData { file, parent, name, pos, expr, sig: Sig { args, ret }, is_inline, is_generic: true, comp_state: Default::default(), }) .into() }; let ty::Kind::Func(f) = self.tys.syms.get_or_insert(sym, &mut self.tys.ins, ct).expand() else { unreachable!() }; Some(f) } fn assign_pattern(&mut self, pat: &Expr, mut right: Value) { match *pat { Expr::Ident { id, pos, .. } => { if parser::find_symbol(&self.file().symbols, id).flags & idfl::REFERENCED != 0 && !right.ptr { self.spill(pos, &mut right); } self.ci.scope.vars.push(Variable::new( id, right.ty, right.ptr, right.id, &mut self.ci.nodes, )); } Expr::Ctor { pos, fields, .. } => { let ty::Kind::Struct(idx) = right.ty.expand() else { self.error(pos, "can't use struct destruct on non struct value (TODO: shold work with modules)"); return; }; for &CtorField { pos, name, ref value } in fields { let Some((offset, ty)) = OffsetIter::offset_of(self.tys, idx, name) else { self.error(pos, format_args!("field not found: {name:?}")); continue; }; let off = self.offset(right.id, offset); self.assign_pattern(value, Value::ptr(off).ty(ty)); } } ref pat => self.error_unhandled_ast(pat, "pattern"), } } fn ptr_expr_ctx(&mut self, expr: &Expr, ctx: Ctx) -> Option { let mut n = self.raw_expr_ctx(expr, ctx)?; if mem::take(&mut n.var) { let id = (u16::MAX - n.id) as usize; n.ptr = self.ci.scope.vars[id].ptr; n.id = self.ci.scope.vars[id].value(); } Some(n) } fn expr_ctx(&mut self, expr: &Expr, ctx: Ctx) -> Option { let mut n = self.ptr_expr_ctx(expr, ctx)?; self.strip_ptr(&mut n); Some(n) } fn ptr_expr(&mut self, expr: &Expr) -> Option { self.ptr_expr_ctx(expr, Default::default()) } fn expr(&mut self, expr: &Expr) -> Option { self.expr_ctx(expr, Default::default()) } fn strip_ptr(&mut self, target: &mut Value) { if mem::take(&mut target.ptr) { target.id = self.load_mem(target.id, target.ty); } } fn offset(&mut self, val: Nid, off: Offset) -> Nid { if off == 0 { return val; } let off = self.ci.nodes.new_const(ty::Id::INT, off); let aclass = self.ci.nodes.aclass_index(val).1; let inps = [VOID, val, off]; let seted = self.ci.nodes.new_node(ty::Id::INT, Kind::BinOp { op: TokenKind::Add }, inps, self.tys); self.ci.nodes.pass_aclass(aclass, seted); seted } fn gen_defers(&mut self, base: usize) -> Option<()> { let defers = mem::take(&mut self.ci.defers); for &(_, defer) in defers.iter().skip(base).rev() { self.expr(defer.get(self.file()))?; } self.ci.defers = defers; Some(()) } fn jump_to(&mut self, pos: Pos, id: usize) -> Option { let Some(loob) = self.ci.loops.last_mut() else { self.error(pos, "break outside a loop"); return None; }; match *loob { Loop::Comptime { state: Some((_, prev)), .. } => { self.error( pos, "reached multiple control flow keywords inside an unrolled loop, \ make sure control flow is not dependant on a runtime value", ); self.error(prev, "previous reachable control flow found here"); } Loop::Comptime { state: ref mut state @ None, defer_base } => { *state = Some(([CtLoopState::Continued, CtLoopState::Terminated][id], pos)); self.gen_defers(defer_base)?; } Loop::Runtime { defer_base, .. } => { self.gen_defers(defer_base)?; let Loop::Runtime { ctrl: lctrl, ctrl_scope, scope, .. } = self.ci.loops.last_mut().unwrap() else { unreachable!() }; if lctrl[id].is_live() { lctrl[id].set( self.ci.nodes.new_node( ty::Id::VOID, Kind::Region, [self.ci.ctrl.get(), lctrl[id].get()], self.tys, ), &mut self.ci.nodes, ); let mut scope = mem::take(&mut ctrl_scope[id]); let ctrl = mem::take(&mut lctrl[id]); self.ci.nodes.merge_scopes( &mut self.ci.loops, &ctrl, &mut scope, &mut self.ci.scope, self.tys, ); let Loop::Runtime { ctrl: lctrl, ctrl_scope, .. } = self.ci.loops.last_mut().unwrap() else { unreachable!() }; ctrl_scope[id] = scope; lctrl[id] = ctrl; self.ci.ctrl.set(NEVER, &mut self.ci.nodes); } else { let term = StrongRef::new(NEVER, &mut self.ci.nodes); lctrl[id] = mem::replace(&mut self.ci.ctrl, term); ctrl_scope[id] = self.ci.scope.dup(&mut self.ci.nodes); ctrl_scope[id] .vars .drain(scope.vars.len()..) .for_each(|v| v.remove(&mut self.ci.nodes)); ctrl_scope[id] .aclasses .drain(scope.aclasses.len()..) .for_each(|v| v.remove(&mut self.ci.nodes)); } } } None } fn complete_call_graph(&mut self) -> bool { let prev_err_len = self.errors.borrow().len(); while self.ci.task_base < self.tys.tasks.len() && let Some(task_slot) = self.tys.tasks.pop() { let Some(task) = task_slot else { continue }; self.emit_func(task); } self.errors.borrow().len() == prev_err_len } fn emit_func(&mut self, FTask { file, id, ct }: FTask) { let func = &mut self.tys.ins.funcs[id]; debug_assert_eq!(func.file, file); let cct = self.ct.active(); debug_assert_eq!(cct, ct); func.comp_state[cct as usize] = CompState::Compiled.into(); let sig = func.sig; let ast = &self.files[file]; let expr = func.expr.get(ast); self.pool.push_ci(file, func.parent, Some(sig.ret), 0, &mut self.ci); let prev_err_len = self.errors.borrow().len(); log::info!("{}", self.ast_display(expr)); let &Expr::Closure { body, args, pos, .. } = expr else { unreachable!("{}", self.ast_display(expr)) }; self.ci.pos.push(pos); let mut tys = sig.args.args(); let mut argsi = args.iter(); while let Some(aty) = tys.next(self.tys) { let arg = argsi.next().unwrap(); match aty { Arg::Type(_) => {} Arg::Value(ty) => { let mut deps = Vc::from([VOID]); if ty.loc(self.tys) == Loc::Stack && self.tys.size_of(ty) <= 16 { deps.push(MEM); } // TODO: whe we not using the deps? let value = self.ci.nodes.new_node_nop(ty, Kind::Arg, deps); let ptr = ty.loc(self.tys) == Loc::Stack; self.ci.scope.vars.push(Variable::new( arg.id, ty, ptr, value, &mut self.ci.nodes, )); if ty.loc(self.tys) == Loc::Stack { self.ci.nodes[value].aclass = self.ci.scope.aclasses.len() as _; self.ci.scope.aclasses.push(AClass::new(&mut self.ci.nodes)); } } } } let mut tys = sig.args.args(); let mut args = args.iter(); while let Some(aty) = tys.next(self.tys) { let arg = args.next().unwrap(); match aty { Arg::Type(ty) => { self.ci.scope.vars.push(Variable::new( arg.id, ty::Id::TYPE, false, self.ci.nodes.new_const(ty::Id::TYPE, ty), &mut self.ci.nodes, )); } Arg::Value(_) => {} } } if self.expr(body).is_some() { if sig.ret == ty::Id::VOID { self.expr(&Expr::Return { pos: body.pos(), val: None }); } else { self.error( body.pos(), fa!( "expected all paths in the fucntion to return \ or the return type to be 'void' (return type is '{}')", self.ty_display(sig.ret), ), ); } } self.ci.scope.vars.drain(..).for_each(|v| v.remove_ignore_arg(&mut self.ci.nodes)); if self.finalize(prev_err_len) { let backend = if !cct { &mut *self.backend } else { &mut *self.ct_backend }; backend.emit_body(id, &self.ci.nodes, self.tys, self.files); } self.ci.pos.pop(); self.pool.pop_ci(&mut self.ci); } fn finalize(&mut self, prev_err_len: usize) -> bool { use {AssertKind as AK, CondOptRes as CR}; self.ci.finalize(&mut self.pool.scratch1, self.tys, self.files); //let mut to_remove = vec![]; for (id, node) in self.ci.nodes.iter() { let Kind::Assert { kind, pos } = node.kind else { continue }; let res = self.ci.nodes.try_match_cond(id); // TODO: highlight the pin position let msg = match (kind, res) { (AK::UnwrapCheck, CR::Known { value: false, .. }) => { "unwrap is not needed since the value is (provably) never null, \ remove it, or replace with '@as(, )'" } (AK::UnwrapCheck, CR::Known { value: true, .. }) => { "unwrap is incorrect since the value is (provably) always null, \ make sure your logic is correct" } (AK::NullCheck, CR::Known { value: true, .. }) => { "the value is always null, some checks might need to be inverted" } (AK::NullCheck, CR::Unknown) => { "can't prove the value is not 'null', \ use '@unwrap()' if you believe compiler is stupid, \ or explicitly check for null and handle it \ ('if == null { /* handle */ } else { /* use opt */ }')" } _ => unreachable!(), }; self.error(pos, msg); } for &node in self.ci.nodes[NEVER].inputs.iter() { if let Kind::Return { file } = self.ci.nodes[node].kind && let (_, stck) = self.ci.nodes.aclass_index(self.ci.nodes[node].inputs[1]) && self.ci.nodes[stck].kind == Kind::Stck { let pfile = mem::replace(&mut self.ci.file, file); debug_assert!(self.ci.nodes[node].pos != 0); self.error( self.ci.nodes[node].pos, "returning value with local provenance \ (pointer will be invalid after function returns)", ); self.error( self.ci.nodes[stck].pos, "...the pointer points to stack allocation created here", ); self.ci.file = pfile; } } if self.errors.borrow().len() == prev_err_len { self.ci.nodes.check_final_integrity(self.ty_display(ty::Id::VOID)); self.ci.nodes.graphviz(self.ty_display(ty::Id::VOID)); self.ci.nodes.gcm( &mut self.pool.scratch1, &mut self.pool.scratch2, &mut self.pool.nid_set, ); self.ci.nodes.check_loop_depth_integrity(self.ty_display(ty::Id::VOID)); self.ci.nodes.basic_blocks(); self.ci.nodes.graphviz(self.ty_display(ty::Id::VOID)); } else { //self.ci.nodes.graphviz_in_browser(self.ty_display(ty::Id::VOID)); } self.errors.borrow().len() == prev_err_len } fn ty(&mut self, expr: &Expr) -> ty::Id { self.ty_in(self.ci.file, self.ci.parent, expr) } fn ty_in(&mut self, file: Module, parent: ty::Id, expr: &Expr) -> ty::Id { self.parse_ty(TyScope { file, parent, alloc_const: true, ..Default::default() }, expr) } fn ty_display(&self, ty: ty::Id) -> ty::Display { ty::Display::new(self.tys, self.files, ty) } fn ast_display(&self, ast: &'a Expr<'a>) -> parser::Display<'a> { parser::Display::new(&self.file().file, ast) } #[must_use] #[track_caller] fn binop_ty( &mut self, pos: Pos, lhs: &mut Value, rhs: &mut Value, op: TokenKind, ) -> (ty::Id, Nid) { if let Some(upcasted) = lhs.ty.try_upcast_low(rhs.ty, true) { let to_correct = if lhs.ty != upcasted { Some((lhs, rhs)) } else if rhs.ty != upcasted { Some((rhs, lhs)) } else { None }; if let Some((oper, other)) = to_correct { if self.tys.size_of(upcasted) > self.tys.size_of(oper.ty) { self.extend(oper, upcasted); } if matches!(op, TokenKind::Add | TokenKind::Sub) && let Some(elem) = self.tys.base_of(upcasted) { let cnst = self.ci.nodes.new_const(ty::Id::INT, self.tys.size_of(elem)); oper.id = self.ci.nodes.new_node( upcasted, Kind::BinOp { op: TokenKind::Mul }, [VOID, oper.id, cnst], self.tys, ); return (upcasted, self.ci.nodes.aclass_index(other.id).1); } } (upcasted, VOID) } else { let ty = self.ty_display(lhs.ty); let expected = self.ty_display(rhs.ty); self.error(pos, fa!("'{ty} {op} {expected}' is not supported")); (ty::Id::NEVER, VOID) } } fn wrap_in_opt(&mut self, pos: Pos, val: &mut Value) { debug_assert!(!val.var); let oty = self.tys.make_opt(val.ty); if let Some((uninit, ..)) = self.tys.nieche_of(val.ty) { self.strip_ptr(val); val.ty = oty; assert!(!uninit, "TODO"); return; } let OptLayout { flag_ty, flag_offset, payload_offset } = self.tys.opt_layout(val.ty); self.strip_ptr(val); match oty.loc(self.tys) { Loc::Reg => { // registers have inverted offsets so that accessing the inner type is a noop let flag_offset = self.tys.size_of(oty) * 8 - flag_offset * 8 - 1; let fill = self.ci.nodes.new_const(oty, 1i64 << flag_offset); val.id = self.ci.nodes.new_node( oty, Kind::BinOp { op: TokenKind::Bor }, [VOID, val.id, fill], self.tys, ); val.ty = oty; } Loc::Stack => { let stack = self.new_stack(pos, oty); let fill = self.ci.nodes.new_const(flag_ty, 1); self.store_mem(stack, flag_ty, fill); let off = self.offset(stack, payload_offset); self.store_mem(off, val.ty, val.id); val.id = stack; val.ptr = true; val.ty = oty; } } } fn implicit_unwrap(&mut self, pos: Pos, opt: &mut Value) { self.unwrap_low(pos, opt, AssertKind::NullCheck); } fn explicit_unwrap(&mut self, pos: Pos, opt: &mut Value) { self.unwrap_low(pos, opt, AssertKind::UnwrapCheck); } fn unwrap_low(&mut self, pos: Pos, opt: &mut Value, kind: AssertKind) { let Some(ty) = self.tys.inner_of(opt.ty) else { return }; let null_check = self.gen_null_check(*opt, ty, TokenKind::Eq); let oty = mem::replace(&mut opt.ty, ty); self.unwrap_opt_unchecked(ty, oty, opt); // TODO: extract the if check int a fucntion let ass = self.ci.nodes.new_node_nop(oty, Kind::Assert { kind, pos }, [ self.ci.ctrl.get(), null_check, opt.id, ]); self.ci.nodes.pass_aclass(self.ci.nodes.aclass_index(opt.id).1, ass); opt.id = ass; } fn unwrap_opt_unchecked(&mut self, ty: ty::Id, oty: ty::Id, opt: &mut Value) { if self.tys.nieche_of(ty).is_some() { return; } let OptLayout { payload_offset, .. } = self.tys.opt_layout(ty); match oty.loc(self.tys) { Loc::Reg => {} Loc::Stack => { opt.id = self.offset(opt.id, payload_offset); } } } fn gen_null_check(&mut self, mut cmped: Value, ty: ty::Id, op: TokenKind) -> Nid { let OptLayout { flag_ty, flag_offset, .. } = self.tys.opt_layout(ty); debug_assert!(cmped.ty.is_optional()); match cmped.ty.loc(self.tys) { Loc::Reg => { self.strip_ptr(&mut cmped); let inps = [VOID, cmped.id, self.ci.nodes.new_const(cmped.ty, 0)]; self.ci.nodes.new_node(ty::Id::BOOL, Kind::BinOp { op }, inps, self.tys) } Loc::Stack => { cmped.id = self.offset(cmped.id, flag_offset); cmped.ty = flag_ty; debug_assert!(cmped.ptr); self.strip_ptr(&mut cmped); let inps = [VOID, cmped.id, self.ci.nodes.new_const(flag_ty, 0)]; self.ci.nodes.new_node(ty::Id::BOOL, Kind::BinOp { op }, inps, self.tys) } } } #[track_caller] fn assert_ty( &mut self, pos: Pos, src: &mut Value, expected: ty::Id, hint: impl fmt::Display, ) -> bool { if let Some(upcasted) = src.ty.try_upcast(expected) && upcasted == expected { if src.ty.is_never() { return true; } if src.ty != upcasted { if let Some(inner) = self.tys.inner_of(upcasted) { if inner != src.ty { self.assert_ty(pos, src, inner, hint); } self.wrap_in_opt(pos, src); } else { debug_assert!( src.ty.is_integer() || src.ty == ty::Id::BOOL, "{} {}", self.ty_display(src.ty), self.ty_display(upcasted) ); debug_assert!( upcasted.is_integer() || src.ty == ty::Id::BOOL, "{} {}", self.ty_display(src.ty), self.ty_display(upcasted) ); self.extend(src, upcasted); } } true } else { if let Some(inner) = self.tys.inner_of(src.ty) && inner.try_upcast(expected) == Some(expected) { self.implicit_unwrap(pos, src); return self.assert_ty(pos, src, expected, hint); } let ty = self.ty_display(src.ty); let expected = self.ty_display(expected); self.error(pos, fa!("expected {hint} to be of type {expected}, got {ty}")); false } } fn extend(&mut self, value: &mut Value, to: ty::Id) { self.strip_ptr(value); let inps = [VOID, value.id]; *value = self.ci.nodes.new_node_lit(to, Kind::UnOp { op: TokenKind::Number }, inps, self.tys); value.ty = to; } #[track_caller] fn warn(&self, pos: Pos, msg: impl core::fmt::Display) { let mut buf = self.warnings.borrow_mut(); write!(buf, "(W) {}", self.file().report(pos, msg)).unwrap(); } #[track_caller] fn error(&self, pos: Pos, msg: impl core::fmt::Display) -> Option { let mut buf = self.errors.borrow_mut(); write!(buf, "{}", self.file().report(pos, msg)).unwrap(); Value::NEVER } #[track_caller] fn error_unhandled_ast(&self, ast: &Expr, hint: impl Display) { log::info!("{ast:#?}"); self.error(ast.pos(), fa!("compiler does not (yet) know how to handle ({hint})")); } fn file(&self) -> &'a parser::Ast { &self.files[self.ci.file] } fn eval_const(&mut self, file: Module, parent: ty::Id, expr: &Expr, ret: ty::Id) -> u64 { self.ct.activate(); let prev = self.pool.push_ci(file, parent, Some(ret), self.tys.tasks.len(), &mut self.ci); prev.scope .vars .iter() .filter(|v| v.ty == ty::Id::TYPE) .map(|v| (prev.nodes.as_ty(v.value()), v.id)) .map(|(v, id)| { Variable::new( id, ty::Id::TYPE, false, self.ci.nodes.new_const(ty::Id::TYPE, v), &mut self.ci.nodes, ) }) .collect_into(&mut self.ci.scope.vars); let prev_err_len = self.errors.borrow().len(); self.expr(&Expr::Return { pos: expr.pos(), val: Some(expr) }); let res = if self.finalize(prev_err_len) { self.emit_and_eval(file, ret, &mut []) } else { i64::from(ty::Id::UNDECLARED) as _ }; self.pool.pop_ci(&mut self.ci); self.ct.deactivate(); res } fn infer_type(&mut self, expr: &Expr) -> ty::Id { self.pool.save_ci(&self.ci); let ty = self.expr(expr).map_or(ty::Id::NEVER, |v| v.ty); self.pool.restore_ci(&mut self.ci); ty } fn on_reuse(&mut self, existing: ty::Id) { let state_slot = self.ct.active() as usize; if let ty::Kind::Func(id) = existing.expand() && let func = &mut self.tys.ins.funcs[id] && let CompState::Queued(idx) = func.comp_state[state_slot].into() && idx < self.tys.tasks.len() { func.comp_state[state_slot] = CompState::Queued(self.tys.tasks.len()).into(); let task = self.tys.tasks[idx].take(); self.tys.tasks.push(task); } } fn eval_global(&mut self, file: Module, parent: ty::Id, name: Ident, expr: &Expr) -> ty::Id { self.ct.activate(); let gid = self.tys.ins.globals.push(GlobalData { file, name, ..Default::default() }); self.pool.push_ci(file, parent, None, self.tys.tasks.len(), &mut self.ci); let prev_err_len = self.errors.borrow().len(); self.expr(&(Expr::Return { pos: expr.pos(), val: Some(expr) })); let ret = self.ci.ret.expect("for return type to be infered"); if self.finalize(prev_err_len) { let mut mem = vec![0u8; self.tys.size_of(ret) as usize]; self.emit_and_eval(file, ret, &mut mem); self.tys.ins.globals[gid].data = mem; } self.pool.pop_ci(&mut self.ci); self.tys.ins.globals[gid].ty = ret; self.ct.deactivate(); gid.into() } fn error_low(&self, file: Module, pos: Pos, msg: impl Display) -> ty::Id { let mut buf = self.errors.borrow_mut(); write!(buf, "{}", self.files[file].report(pos, msg)).unwrap(); ty::Id::NEVER } fn warn_low(&self, file: Module, pos: Pos, msg: impl Display) -> ty::Id { let mut buf = self.warnings.borrow_mut(); write!(buf, "(W) {}", self.files[file].report(pos, msg)).unwrap(); ty::Id::NEVER } fn find_local_ty(&mut self, ident: CapturedIdent) -> Option { self.ci .scope .vars .iter() .rfind(|v| v.id == ident.id && (!ident.is_ct || v.ty == ty::Id::TYPE)) .map(|v| if ident.is_ct { self.ci.nodes.as_ty(v.value()) } else { v.ty }) } fn find_type_as_value( &mut self, pos: Pos, parent: impl Into, id: impl Into, ctx: Ctx, ) -> Option { match self.find_type(pos, self.ci.file, parent, id).expand() { ty::Kind::NEVER => Value::NEVER, ty::Kind::Global(global) => self.gen_global(global), ty::Kind::Const(cnst) => self.gen_const(cnst, ctx), v => Some(self.ci.nodes.new_const_lit(ty::Id::TYPE, v.compress())), } } fn find_type( &mut self, pos: Pos, from_file: Module, parent: impl Into, id: impl Into, ) -> ty::Id { self.find_type_low(pos, from_file, parent.into(), id.into()) } fn find_type_low(&mut self, pos: Pos, from_file: Module, parent: ty::Id, id: DeclId) -> ty::Id { let file = match parent.expand() { ty::Kind::Module(m) => m, _ => self.tys.type_base_of(parent).unwrap().file, }; let ty = if let DeclId::Ident(id) = id && let Some(ty) = self.find_local_ty(CapturedIdent { id, is_ct: true }) { ty } else if let DeclId::Ident(id) = id && let Some(&ty) = self.tys.syms.get(SymKey::Decl(parent, id), &self.tys.ins) { self.on_reuse(ty); ty } else { let f = &self.files[file]; let mut piter = parent; let Some((expr @ Expr::BinOp { left, right, .. }, name)) = (loop { if let Some(f) = parser::find_decl(self.tys.scope_of(piter, f).expect("TODO"), &f.file, id) { break Some(f); } if let Some((captures, capture_tuple)) = self.tys.captures_of(piter, f) && let Some(idx) = captures.iter().position(|&cid| cid.is_ct && DeclId::Ident(cid.id) == id) { return self.tys.ins.args[capture_tuple.range().start + idx]; } piter = match self.tys.parent_of(piter) { Some(p) => p, None => { break None; } }; }) else { return match id { DeclId::Ident(id) => { debug_assert_eq!(from_file, file, "{}", f.ident_str(id)); self.error_low(file, pos, "somehow this was not found") } DeclId::Name("main") => self.error_low( from_file, pos, format_args!( "missing main function in '{}', compiler can't \ emmit libraries since such concept is not defined \ (minimal main function: `main := fn(): void {{}}`)", f.path ), ), DeclId::Name(name) => self.error_low( from_file, pos, format_args!("undefined indentifier: {name}"), ), }; }; let ty = if let Some(&ty) = self.tys.syms.get(SymKey::Decl(piter, name), &self.tys.ins) { ty } else { let ty = left .find_pattern_path(name, right, |right, is_ct| { if is_ct && !matches!(right, Expr::Closure { .. }) { self.tys .ins .consts .push(ConstData { ast: ExprRef::new(expr), name, file, parent: piter, }) .into() } else { self.parse_ty( TyScope { file, parent: piter, name: Some(name), alloc_const: true, is_ct, }, right, ) } }) .unwrap_or_else(|_| unreachable!()); self.tys.syms.insert(SymKey::Decl(piter, name), ty, &self.tys.ins); ty }; if let Err(proper_case) = self.tys.case(ty, self.files)(f.ident_str(name)) { self.warn_low( from_file, pos, format_args!( "the declaration does not have conventional \ casing, expected '{proper_case}', \ because the declared type is '{}'", self.ty_display(ty), ), ); } ty }; if let ty::Kind::Global(g) = ty.expand() { let g = &self.tys.ins.globals[g]; if g.ty == ty::Id::TYPE { return ty::Id::from( u32::from_ne_bytes(g.data.as_slice().try_into().unwrap()) as u64 ); } } ty } /// returns none if comptime eval is required fn parse_ty(&mut self, sc: TyScope, expr: &Expr) -> ty::Id { match *expr { Expr::Slf { .. } => sc.parent, Expr::Mod { id, .. } => id.into(), Expr::UnOp { op: TokenKind::Xor, val, .. } => { let base = self.parse_ty(sc.anon(), val); self.tys.make_ptr(base) } Expr::UnOp { op: TokenKind::Que, val, .. } => { let base = self.parse_ty(sc.anon(), val); self.tys.make_opt(base) } Expr::Ident { id, .. } if let Ok(bt) = ty::Builtin::try_from(id) => bt.into(), Expr::Ident { id, pos, .. } => self.find_type(pos, sc.file, sc.parent, id), Expr::Field { target, pos, name } if let ty::Kind::Module(inside) = self.parse_ty(sc.anon(), target).expand() => { self.find_type(pos, sc.file, inside, name) } Expr::Directive { name: "Any", args: [], .. } => ty::Id::ANY_TYPE, Expr::Directive { name: "TypeOf", args: [expr], .. } => self.infer_type(expr), Expr::Directive { name: "ChildOf", args: [aty], .. } => { let ty = self.parse_ty(sc.anon(), aty); let Some(ty) = self.tys.base_of(ty).or(self.tys.inner_of(ty)).or(self.tys.elem_of(ty)) else { return self.error_low( sc.file, aty.pos(), fa!("only work for pointers and optionals, not '{}'", self.ty_display(ty)), ); }; ty } Expr::Slice { size: None, item, .. } => { let ty = self.parse_ty(sc.anon(), item); self.tys.make_array(ty, ArrayLen::MAX) } Expr::Slice { size: Some(&Expr::Number { value, .. }), item, .. } => { let ty = self.parse_ty(sc.anon(), item); self.tys.make_array(ty, value as _) } Expr::Slice { size, item, .. } => { let ty = self.parse_ty(sc.anon(), item); let len = size.map_or(ArrayLen::MAX, |expr| { self.eval_const(sc.file, sc.parent, expr, ty::Id::U32) as _ }); self.tys.make_array(ty, len) } Expr::Struct { pos, fields, packed, captured, .. } => self.parse_base_ty( pos, expr, captured, fields, sc, |s| [&mut s.ins.struct_fields, &mut s.tmp.struct_fields], |s, field| { let ty = s.parse_ty(sc.anon(), &field.ty); StructField { name: s.tys.names.intern(field.name), ty, default_value: field.default_value.as_ref().map(|expr| { s.tys.ins.consts.push(ConstData { ast: ExprRef::new(expr), name: Default::default(), file: sc.file, parent: Default::default(), }) }), } }, |s, base| { let str = s.ins.structs.push(StructData { base, explicit_alignment: packed.then_some(1), ..Default::default() }); s.ins.struct_fields[s.struct_field_range(str)] .iter() .filter_map(|f| f.default_value) .for_each(|c| s.ins.consts[c].parent = str.into()); str }, ), Expr::Enum { pos, variants, captured, .. } => self.parse_base_ty( pos, expr, captured, variants, sc, |s| [&mut s.ins.enum_fields, &mut s.tmp.enum_fields], |s, field| EnumField { name: s.tys.names.intern(field.name) }, |s, base| s.ins.enums.push(EnumData { base }), ), Expr::Union { pos, fields, captured, .. } => self.parse_base_ty( pos, expr, captured, fields, sc, |s| [&mut s.ins.union_fields, &mut s.tmp.union_fields], |s, field| { let ty = s.parse_ty(sc.anon(), &field.ty); UnionField { name: s.tys.names.intern(field.name), ty } }, |s, base| s.ins.unions.push(UnionData { base, ..Default::default() }), ), Expr::Closure { pos, args, ret, .. } if let Some(name) = sc.name => { let sig = 'b: { let arg_base = self.tys.tmp.args.len(); for arg in args { let sym = parser::find_symbol(&self.files[sc.file].symbols, arg.id); if sym.flags & idfl::COMPTIME != 0 { self.tys.tmp.args.truncate(arg_base); break 'b None; } let ty = self.parse_ty(sc.anon(), &arg.ty); if ty == ty::Id::ANY_TYPE { break 'b None; } self.tys.tmp.args.push(ty); } let Some(args) = self.tys.pack_args(arg_base) else { return self.error_low(sc.file, pos, "function has too many argumnets"); }; let ret = self.parse_ty(sc.anon(), ret); Some(Sig { args, ret }) }; //let returns_type = matches!(ret, &Expr::Ident { id, .. } if ); match sig { Some(sig) => { let func = FuncData { file: sc.file, parent: sc.parent, name, pos, sig, expr: ExprRef::new(expr), is_inline: sc.is_ct, is_generic: false, comp_state: Default::default(), }; self.tys.ins.funcs.push(func).into() } None => { let template = TemplateData { file: sc.file, parent: sc.parent, name, expr: ExprRef::new(expr), is_inline: sc.is_ct, }; self.tys.ins.templates.push(template).into() } } } _ if sc.alloc_const && let Some(name) = sc.name => { self.eval_global(sc.file, sc.parent, name, expr) } _ if sc.alloc_const => { let prev_file = mem::replace(&mut self.ci.file, sc.file); let prev_parent = mem::replace(&mut self.ci.parent, sc.parent); let id = self.expr(expr).unwrap().id; self.ci.file = prev_file; self.ci.parent = prev_parent; self.ci.nodes.as_ty(id) } ref e => { self.error_unhandled_ast(e, "bruh"); ty::Id::NEVER } } } #[expect(clippy::too_many_arguments)] fn parse_base_ty>( &mut self, pos: Pos, expr: &Expr, captured: &[CapturedIdent], fields: FieldList, sc: TyScope, get_fields: impl Fn(&mut Types) -> [&mut Vec; 2], check_field: impl Fn(&mut Self, &A) -> F, check: impl Fn(&mut Types, TypeBase) -> T, ) -> ty::Id { let captures_start = self.tys.tmp.args.len(); for &cp in captured { let ty = self.find_local_ty(cp).expect("TODO"); self.tys.tmp.args.push(ty); } let captured = self.tys.pack_args(captures_start).expect("TODO"); let sym = SymKey::Type(sc.parent, pos, captured); if let Some(&ty) = self.tys.syms.get(sym, &self.tys.ins) { return ty; } let prev_tmp = get_fields(self.tys)[1].len(); for field in fields.iter().filter_map(CommentOr::or).map(Result::as_ref).filter_map(Result::ok) { let field = check_field(self, field); get_fields(self.tys)[1].push(field); } let base = TypeBase { file: sc.file, parent: sc.parent, pos, captured, name: sc.name.unwrap_or_default(), field_start: get_fields(self.tys)[0].len() as _, ast: ExprRef::new(expr), }; let [ins, tmp] = get_fields(self.tys); ins.extend(tmp.drain(prev_tmp..)); let ty = check(self.tys, base).into(); self.tys.syms.insert(sym, ty, &self.tys.ins); ty } fn create_string_global(&mut self, data: &[u8]) -> (Nid, ty::Id) { let ty = self.tys.make_ptr(ty::Id::U8); let global = self .tys .strings .get_or_insert(data, &mut self.tys.ins.globals, |globals| { StringRef(globals.push(GlobalData { data: data.to_vec(), ty, ..Default::default() })) }) .0; let global = self.ci.nodes.new_node_nop(ty, Kind::Global { global }, [VOID]); self.ci.nodes[global].aclass = GLOBAL_ACLASS as _; (global, ty) } } #[derive(Clone, Copy, Default)] struct TyScope { file: Module, parent: ty::Id, name: Option, alloc_const: bool, is_ct: bool, } impl TyScope { fn anon(self) -> Self { Self { name: None, is_ct: false, ..self } } } #[cfg(test)] mod tests { use { crate::{ backend::hbvm::{self, HbvmBackend}, son::CodegenCtx, ty, }, alloc::{string::String, vec::Vec}, core::fmt::Write, }; fn generate(ident: &'static str, input: &'static str, output: &mut String) { _ = log::set_logger(&crate::fs::Logger); log::set_max_level(log::LevelFilter::Info); //log::set_max_level(log::LevelFilter::Trace); let mut ctx = CodegenCtx::default(); let (ref files, embeds) = crate::test_parse_files(ident, input, &mut ctx.parser); let mut backend = HbvmBackend::default(); let mut codegen = super::Codegen::new(&mut backend, files, &mut ctx); codegen.push_embeds(embeds); codegen.generate(ty::Module::MAIN); { let errors = codegen.errors.borrow(); if !errors.is_empty() { output.push_str(&errors); return; } } let mut out = Vec::new(); codegen.assemble(&mut out); let err = codegen.disasm(output, &out); if let Err(e) = err { writeln!(output, "!!! asm is invalid: {e}").unwrap(); } else { log::info!("================ running {ident} =============="); log::trace!("{output}"); hbvm::test_run_vm(&out, output); } } crate::run_tests! { generate: // Tour Examples main_fn; arithmetic; floating_point_arithmetic; functions; comments; if_statements; variables; hex_octal_binary_literals; loops; pointers; structs; tuples; struct_scopes; enums; unions; nullable_types; struct_operators; global_variables; constants; directives; c_strings; struct_patterns; arrays; inline; idk; generic_functions; die; defer; unrolled_loops; // Incomplete Examples; //comptime_pointers; generic_types; fb_driver; // Purely Testing Examples; proper_ident_propagation; method_receiver_by_value; comparing_floating_points; pointer_comparison; different_function_destinations; triggering_store_in_divergent_branch; wrong_dead_code_elimination; memory_swap; very_nested_loops; generic_type_mishap; storing_into_nullable_struct; scheduling_block_did_dirty; null_check_returning_small_global; null_check_in_the_loop; stack_provenance; advanced_floating_point_arithmetic; nullable_structure; needless_unwrap; inlining_issues; null_check_test; only_break_loop; reading_idk; nonexistent_ident_import; big_array_crash; returning_global_struct; small_struct_bitcast; small_struct_assignment; intcast_store; string_flip; signed_to_unsigned_upcast; wide_ret; comptime_min_reg_leak; different_types; struct_return_from_module_function; sort_something_viredly; struct_in_register; comptime_function_from_another_file; inline_test; inlined_generic_functions; some_generic_code; integer_inference_issues; writing_into_string; request_page; tests_ptr_to_ptr_copy; global_variable_wiredness; inline_return_stack; // Just Testing Optimizations; elide_stack_offsets_for_parameters_correctly; const_folding_with_arg; branch_assignments; exhaustive_loop_testing; pointer_opts; conditional_stores; loop_stores; dead_code_in_loop; infinite_loop_after_peephole; aliasing_overoptimization; global_aliasing_overptimization; overwrite_aliasing_overoptimization; more_if_opts; optional_from_eca; returning_optional_issues; } }