use { self::{ hbvm::{Comptime, HbvmBackend}, strong_ref::StrongRef, }, crate::{ ctx_map::CtxEntry, debug, lexer::{self, TokenKind}, parser::{ self, idfl::{self}, CommentOr, CtorField, Expr, ExprRef, MatchBranch, Pos, }, ty::{self, Arg, ArrayLen, Loc, Module, Tuple}, utils::{BitSet, Ent, Vc}, CompState, Const, Enum, EnumField, FTask, Func, Global, Ident, Offset, OffsetIter, OptLayout, Sig, StringRef, Struct, StructField, SymKey, Types, }, alloc::{string::String, vec::Vec}, core::{ assert_matches::debug_assert_matches, cell::{Cell, RefCell}, fmt::{self, Debug, Display, Write}, format_args as fa, mem, ops::{self, Range}, }, hashbrown::hash_map, hbbytecode::DisasmError, }; const VOID: Nid = 0; const NEVER: Nid = 1; const ENTRY: Nid = 2; const MEM: Nid = 3; const LOOPS: Nid = 4; const ARG_START: usize = 3; const DEFAULT_ACLASS: usize = 0; const GLOBAL_ACLASS: usize = 1; pub mod hbvm; type Nid = u16; type AClassId = i16; pub struct AssemblySpec { entry: u32, code_length: u64, data_length: u64, } pub trait Backend { fn assemble_reachable( &mut self, from: ty::Func, types: &Types, to: &mut Vec, ) -> AssemblySpec; fn disasm<'a>( &'a self, sluce: &[u8], eca_handler: &mut dyn FnMut(&mut &[u8]), types: &'a Types, files: &'a [parser::Ast], output: &mut String, ) -> Result<(), hbbytecode::DisasmError<'a>>; fn emit_body(&mut self, id: ty::Func, ci: &mut Nodes, tys: &Types, files: &[parser::Ast]); fn emit_ct_body(&mut self, id: ty::Func, ci: &mut Nodes, tys: &Types, files: &[parser::Ast]) { self.emit_body(id, ci, tys, files); } fn assemble_bin(&mut self, from: ty::Func, types: &Types, to: &mut Vec) { self.assemble_reachable(from, types, to); } } type Lookup = crate::ctx_map::CtxMap; impl crate::ctx_map::CtxEntry for Nid { type Ctx = [Result]; type Key<'a> = (Kind, &'a [Nid], ty::Id); fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> { ctx[*self as usize].as_ref().unwrap_or_else(|(_, t)| panic!("{t:#?}")).key() } } 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; }; }; } #[derive(Clone)] pub struct Nodes { values: Vec>, queued_peeps: Vec, free: Nid, lookup: Lookup, } impl Default for Nodes { fn default() -> Self { Self { values: Default::default(), queued_peeps: Default::default(), free: Nid::MAX, lookup: Default::default(), } } } impl Nodes { fn loop_depth(&self, target: Nid, scheds: Option<&[Nid]>) -> LoopDepth { self[target].loop_depth.set(match self[target].kind { Kind::Region | Kind::Entry | Kind::Then | Kind::Else | Kind::Call { .. } | Kind::If => { if self[target].loop_depth.get() != 0 { return self[target].loop_depth.get(); } self.loop_depth(self[target].inputs[0], scheds) } Kind::Loop => { if self[target].loop_depth.get() == self.loop_depth(self[target].inputs[0], scheds) + 1 { return self[target].loop_depth.get(); } let depth = self.loop_depth(self[target].inputs[0], scheds) + 1; self[target].loop_depth.set(depth); let mut cursor = self[target].inputs[1]; while cursor != target { self[cursor].loop_depth.set(depth); let next = self.idom(cursor, scheds); debug_assert_ne!(next, 0); if matches!(self[cursor].kind, Kind::Then | Kind::Else) { debug_assert_eq!(self[next].kind, Kind::If); let other = self[next].outputs[(self[next].outputs[0] == cursor) as usize]; self[other].loop_depth.set(depth - 1); } cursor = next; } depth } Kind::Start | Kind::End | Kind::Die | Kind::Return { .. } => 1, u => unreachable!("{u:?}"), }); self[target].loop_depth.get() } fn idepth(&self, target: Nid, scheds: Option<&[Nid]>) -> IDomDepth { if target == VOID { return 0; } if self[target].depth.get() == 0 { let depth = match self[target].kind { Kind::End | Kind::Start => unreachable!("{:?}", self[target].kind), Kind::Region => self .idepth(self[target].inputs[0], scheds) .max(self.idepth(self[target].inputs[1], scheds)), _ if self[target].kind.is_pinned() => self.idepth(self[target].inputs[0], scheds), _ if let Some(scheds) = scheds => { self.idepth(scheds[target as usize], Some(scheds)) } _ => self.idepth(self[target].inputs[0], scheds), } + 1; self[target].depth.set(depth); } self[target].depth.get() } fn fix_loops(&mut self, stack: &mut Vec, seen: &mut BitSet) { debug_assert!(stack.is_empty()); stack.push(NEVER); while let Some(node) = stack.pop() { if seen.set(node) && self.is_cfg(node) { stack.extend(self[node].inputs.iter()); } } for l in self[LOOPS].outputs.clone() { if !seen.get(l) { self[l].outputs.push(NEVER); self[NEVER].inputs.push(l); } } } fn push_up_impl(&self, node: Nid, visited: &mut BitSet, scheds: &mut [Nid]) { if !visited.set(node) { return; } for &inp in &self[node].inputs[1..] { if !self[inp].kind.is_pinned() { self.push_up_impl(inp, visited, scheds); } } if self[node].kind.is_pinned() { return; } let mut deepest = self[node].inputs[0]; for &inp in self[node].inputs[1..].iter() { if self.idepth(inp, Some(scheds)) > self.idepth(deepest, Some(scheds)) { if self[inp].kind.is_call() { deepest = inp; } else { debug_assert!(!self.is_cfg(inp)); deepest = self.idom(inp, Some(scheds)); } } } scheds[node as usize] = deepest; } fn collect_rpo(&self, node: Nid, rpo: &mut Vec, visited: &mut BitSet) { if !self.is_cfg(node) || !visited.set(node) { return; } for &n in self[node].outputs.iter() { self.collect_rpo(n, rpo, visited); } rpo.push(node); } fn push_up(&self, rpo: &mut Vec, visited: &mut BitSet, scheds: &mut [Nid]) { debug_assert!(rpo.is_empty()); self.collect_rpo(VOID, rpo, visited); for &node in rpo.iter().rev() { self.loop_depth(node, Some(scheds)); for i in 0..self[node].inputs.len() { self.push_up_impl(self[node].inputs[i], visited, scheds); } if matches!(self[node].kind, Kind::Loop | Kind::Region) { for i in 0..self[node].outputs.len() { let usage = self[node].outputs[i]; if self[usage].kind == Kind::Phi { self.push_up_impl(usage, visited, scheds); } } } } debug_assert_eq!( self.iter() .map(|(n, _)| n) .filter(|&n| !visited.get(n) && !matches!(self[n].kind, Kind::Arg | Kind::Mem | Kind::Loops)) .collect::>(), vec![], "{:?}", self.iter() .filter(|&(n, nod)| !visited.get(n) && !matches!(nod.kind, Kind::Arg | Kind::Mem | Kind::Loops)) .collect::>() ); rpo.clear(); } fn better(&self, is: Nid, then: Nid, scheds: Option<&[Nid]>) -> bool { debug_assert_ne!(self.idepth(is, scheds), self.idepth(then, scheds), "{is} {then}"); self.loop_depth(is, scheds) < self.loop_depth(then, scheds) || self.idepth(is, scheds) > self.idepth(then, scheds) || self[then].kind == Kind::If } fn is_forward_edge(&self, usage: Nid, def: Nid) -> bool { match self[usage].kind { Kind::Phi => { self[usage].inputs[2] != def || self[self[usage].inputs[0]].kind != Kind::Loop } Kind::Loop => self[usage].inputs[1] != def, _ => true, } } fn push_down( &self, node: Nid, visited: &mut BitSet, antideps: &mut [Nid], scheds: &mut [Nid], antidep_bounds: &mut Vec, ) { if !visited.set(node) { return; } for usage in self[node].outputs.clone() { if self.is_forward_edge(usage, node) && self[node].kind == Kind::Stre { self.push_down(usage, visited, antideps, scheds, antidep_bounds); } } for usage in self[node].outputs.clone() { if self.is_forward_edge(usage, node) { self.push_down(usage, visited, antideps, scheds, antidep_bounds); } } if self[node].kind.is_pinned() { return; } let mut min = None::; for i in 0..self[node].outputs.len() { let usage = self[node].outputs[i]; let ub = self.use_block(node, usage, Some(scheds)); min = min.map(|m| self.common_dom(ub, m, Some(scheds))).or(Some(ub)); } let mut min = min.unwrap(); debug_assert!(self.dominates(scheds[node as usize], min, Some(scheds))); let mut cursor = min; let mut fuel = self.values.len(); while cursor != scheds[node as usize] { debug_assert!(fuel != 0); fuel -= 1; cursor = self.idom(cursor, Some(scheds)); if self.better(cursor, min, Some(scheds)) { min = cursor; } } if self[node].kind == Kind::Load { min = self.find_antideps(node, min, antideps, scheds, antidep_bounds); } if self[min].kind.ends_basic_block() { min = self.idom(min, Some(scheds)); } self.assert_dominance(node, min, true, Some(scheds)); debug_assert!( self.idepth(min, Some(scheds)) >= self.idepth(scheds[node as usize], Some(scheds)) ); scheds[node as usize] = min; } fn find_antideps( &self, load: Nid, mut min: Nid, antideps: &mut [Nid], scheds: &[Nid], antidep_bounds: &mut Vec, ) -> Nid { debug_assert!(self[load].kind == Kind::Load); let (aclass, _) = self.aclass_index(self[load].inputs[1]); let mut cursor = min; while cursor != scheds[load as usize] { antideps[cursor as usize] = load; if self[cursor].clobbers.get(aclass as _) { min = self[cursor].inputs[0]; break; } cursor = self.idom(cursor, Some(scheds)); } if self[load].inputs[2] == MEM { return min; } for out in self[self[load].inputs[2]].outputs.clone() { match self[out].kind { Kind::Stre => { let mut cursor = scheds[out as usize]; while cursor != scheds[load as usize] && self.idepth(cursor, Some(scheds)) > self.idepth(scheds[load as usize], Some(scheds)) { if antideps[cursor as usize] == load { min = self.common_dom(min, cursor, Some(scheds)); if min == cursor { antidep_bounds.extend([load, out]); } break; } cursor = self.idom(cursor, Some(scheds)); } break; } Kind::Phi => { let n = self[out].inputs[1..] .iter() .position(|&n| n == self[load].inputs[2]) .unwrap(); let mut cursor = self[self[out].inputs[0]].inputs[n]; while cursor != scheds[load as usize] && self.idepth(cursor, Some(scheds)) > self.idepth(scheds[load as usize], Some(scheds)) { if antideps[cursor as usize] == load { min = self.common_dom(min, cursor, Some(scheds)); break; } cursor = self.idom(cursor, Some(scheds)); } } _ => {} } } min } fn bind(&mut self, from: Nid, to: Nid) { debug_assert_ne!(to, 0); debug_assert_ne!(self[to].kind, Kind::Phi); self[from].outputs.push(to); self[to].inputs.push(from); } fn use_block(&self, target: Nid, from: Nid, scheds: Option<&[Nid]>) -> Nid { if self[from].kind != Kind::Phi { return self.idom(from, scheds); } let index = self[from].inputs.iter().position(|&n| n == target).unwrap_or_else(|| { panic!("from {from} {:?} target {target} {:?}", self[from], self[target]) }); self[self[from].inputs[0]].inputs[index - 1] } fn idom(&self, target: Nid, scheds: Option<&[Nid]>) -> Nid { match self[target].kind { Kind::Start => unreachable!(), Kind::End => unreachable!(), Kind::Region => { let &[lcfg, rcfg] = self[target].inputs.as_slice() else { unreachable!() }; self.common_dom(lcfg, rcfg, scheds) } _ if self[target].kind.is_pinned() => self[target].inputs[0], _ if let Some(scheds) = scheds => scheds[target as usize], _ => self[target].inputs[0], } } fn common_dom(&self, mut a: Nid, mut b: Nid, scheds: Option<&[Nid]>) -> Nid { while a != b { let [ldepth, rdepth] = [self.idepth(a, scheds), self.idepth(b, scheds)]; if ldepth >= rdepth { a = self.idom(a, scheds); } if ldepth <= rdepth { b = self.idom(b, scheds); } } a } 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 graphviz_low(&self, disp: ty::Display, out: &mut String) -> core::fmt::Result { use core::fmt::Write; writeln!(out)?; writeln!(out, "digraph G {{")?; writeln!(out, "rankdir=BT;")?; writeln!(out, "concentrate=true;")?; writeln!(out, "compound=true;")?; for (i, node) in self.iter() { let color = match () { _ if node.lock_rc.get() == Nid::MAX => "orange", _ if node.lock_rc.get() == Nid::MAX - 1 => "blue", _ if node.lock_rc.get() != 0 => "red", _ if node.outputs.is_empty() => "purple", _ if node.is_mem() => "green", _ if self.is_cfg(i) => "yellow", _ => "white", }; if node.ty != ty::Id::VOID { writeln!( out, " node{i}[label=\"{i} {} {} {}\" color={color}]", node.kind, disp.rety(node.ty), node.aclass, )?; } else { writeln!( out, " node{i}[label=\"{i} {} {}\" color={color}]", node.kind, node.aclass, )?; } for (j, &o) in node.outputs.iter().enumerate() { let color = if self.is_cfg(i) && self.is_cfg(o) { "red" } else { "lightgray" }; let index = self[o].inputs.iter().position(|&inp| i == inp).unwrap(); let style = if index == 0 && !self.is_cfg(o) { "style=dotted" } else { "" }; writeln!( out, " node{o} -> node{i}[color={color} taillabel={index} headlabel={j} {style}]", )?; } } writeln!(out, "}}")?; Ok(()) } fn graphviz(&self, disp: ty::Display) { let out = &mut String::new(); _ = self.graphviz_low(disp, out); log::info!("{out}"); } fn graphviz_in_browser(&self, _disp: ty::Display) { #[cfg(all(test, feature = "std"))] { let out = &mut String::new(); _ = self.graphviz_low(_disp, out); if !std::process::Command::new("brave") .arg(format!("https://dreampuf.github.io/GraphvizOnline/#{out}")) .status() .unwrap() .success() { log::error!("{out}"); } } } fn gcm(&mut self, scratch: &mut Vec, bind_buf: &mut Vec, visited: &mut BitSet) { visited.clear(self.values.len()); self.fix_loops(bind_buf, visited); debug_assert!(bind_buf.is_empty()); debug_assert!(scratch.is_empty()); scratch.resize(self.values.len() * 2, Nid::MAX); let (antideps, scheds) = scratch.split_at_mut(self.values.len()); visited.clear(self.values.len()); self.push_up(bind_buf, visited, scheds); visited.clear(self.values.len()); self.push_down(VOID, visited, antideps, scheds, bind_buf); for &[from, to] in bind_buf.array_chunks() { self.bind(from, to); } bind_buf.clear(); self[VOID].outputs = self[VOID].outputs.iter().filter(|&&n| self[n].kind.is_at_start()).copied().collect(); for (&shed, n) in scheds.iter().zip(0u16..) { if shed == Nid::MAX { continue; } let prev = mem::replace(&mut self[n].inputs[0], shed); if prev != VOID { let index = self[prev].outputs.iter().position(|&o| o == n).unwrap(); self[prev].outputs.swap_remove(index); } self[shed].outputs.push(n); } scratch.clear(); } fn clear(&mut self) { self.values.clear(); self.lookup.clear(); self.free = Nid::MAX; } fn new_node_nop(&mut self, ty: ty::Id, kind: Kind, inps: impl Into) -> Nid { let node = Node { inputs: inps.into(), kind, ty, ..Default::default() }; if node.kind == Kind::Phi && node.ty != ty::Id::VOID { debug_assert_ne!( self[node.inputs[1]].ty, ty::Id::VOID, "{:?} {:?}", self[node.inputs[1]], node.ty.expand(), ); if self[node.inputs[0]].kind != Kind::Loop { debug_assert_ne!( self[node.inputs[2]].ty, ty::Id::VOID, "{:?} {:?}", self[node.inputs[2]], node.ty.expand(), ); } debug_assert!(!matches!(node.ty.expand(), ty::Kind::Struct(_))); } let mut lookup_meta = None; if !node.is_not_gvnd() { let (raw_entry, hash) = self.lookup.entry(node.key(), &self.values); let entry = match raw_entry { hash_map::RawEntryMut::Occupied(o) => return o.get_key_value().0.value, hash_map::RawEntryMut::Vacant(v) => v, }; lookup_meta = Some((entry, hash)); } if self.free == Nid::MAX { self.free = self.values.len() as _; self.values.push(Err((Nid::MAX, debug::trace()))); } let free = self.free; for &d in node.inputs.as_slice() { debug_assert_ne!(d, free); self.values[d as usize].as_mut().unwrap_or_else(|_| panic!("{d}")).outputs.push(free); } self.free = mem::replace(&mut self.values[free as usize], Ok(node)).unwrap_err().0; if let Some((entry, hash)) = lookup_meta { entry.insert(crate::ctx_map::Key { value: free, hash }, ()); } free } fn remove_node_lookup(&mut self, target: Nid) { if !self[target].is_not_gvnd() { self.lookup .remove(&target, &self.values) .unwrap_or_else(|| panic!("{:?}", self[target])); } } fn new_node(&mut self, ty: ty::Id, kind: Kind, inps: impl Into, tys: &Types) -> Nid { let id = self.new_node_nop(ty, kind, inps); if let Some(opt) = self.peephole(id, tys) { debug_assert_ne!(opt, id); for peep in mem::take(&mut self.queued_peeps) { self.unlock(peep); } self.lock(opt); self.remove(id); self.unlock(opt); opt } else { id } } fn new_const(&mut self, ty: ty::Id, value: impl Into) -> Nid { self.new_node_nop(ty, Kind::CInt { value: value.into() }, [VOID]) } 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) } fn is_locked(&self, target: Nid) -> bool { self[target].lock_rc.get() != 0 } fn is_unlocked(&self, target: Nid) -> bool { self[target].lock_rc.get() == 0 } fn lock(&self, target: Nid) { self[target].lock_rc.set(self[target].lock_rc.get() + 1); } #[track_caller] fn unlock(&self, target: Nid) { self[target].lock_rc.set(self[target].lock_rc.get() - 1); } fn remove(&mut self, target: Nid) -> bool { if !self[target].is_dangling() { return false; } for i in 0..self[target].inputs.len() { let inp = self[target].inputs[i]; let index = self[inp].outputs.iter().position(|&p| p == target).unwrap(); self[inp].outputs.swap_remove(index); self.remove(inp); } self.remove_node_lookup(target); if cfg!(debug_assertions) { mem::replace(&mut self.values[target as usize], Err((Nid::MAX, debug::trace()))) .unwrap(); } else { mem::replace(&mut self.values[target as usize], Err((self.free, debug::trace()))) .unwrap(); self.free = target; } true } fn late_peephole(&mut self, target: Nid, tys: &Types) -> Option { if let Some(id) = self.peephole(target, tys) { for peep in mem::take(&mut self.queued_peeps) { self.unlock(peep); } self.replace(target, id); return None; } None } fn iter_peeps(&mut self, mut fuel: usize, stack: &mut Vec, tys: &Types) { debug_assert!(stack.is_empty()); debug_assert!(self.queued_peeps.is_empty()); self.iter() .filter_map(|(id, node)| node.kind.is_peeped().then_some(id)) .collect_into(stack); stack.iter().for_each(|&s| self.lock(s)); while fuel != 0 && let Some(node) = stack.pop() { fuel -= 1; if self.unlock_remove(node) { continue; } if let Some(new) = self.peephole(node, tys) { self.replace(node, new); self.push_adjacent_nodes(new, stack); } stack.append(&mut self.queued_peeps); //debug_assert_matches!( // self.iter().find(|(i, n)| n.lock_rc.get() != 0 // && n.kind.is_peeped() // && !stack.contains(i)), // None //); } debug_assert!(self.queued_peeps.is_empty()); stack.drain(..).for_each(|s| _ = self.unlock_remove(s)); } fn push_adjacent_nodes(&mut self, of: Nid, stack: &mut Vec) { let prev_len = stack.len(); for &i in self[of] .outputs .iter() .chain(self[of].inputs.iter()) .chain(self[of].peep_triggers.iter()) { if self.values[i as usize].is_ok() && self[i].kind.is_peeped() && self[i].lock_rc.get() == 0 { stack.push(i); } } self[of].peep_triggers = Vc::default(); stack.iter().skip(prev_len).for_each(|&n| self.lock(n)); } fn aclass_index(&self, region: Nid) -> (usize, Nid) { if self[region].aclass >= 0 { (self[region].aclass as _, region) } else { ( self[self[region].aclass.unsigned_abs() - 1].aclass as _, self[region].aclass.unsigned_abs() - 1, ) } } fn pass_aclass(&mut self, from: Nid, to: Nid) { debug_assert!(self[from].aclass >= 0); if from != to { self[to].aclass = -(from as AClassId + 1); } } fn peephole(&mut self, target: Nid, tys: &Types) -> Option { use {Kind as K, TokenKind as T}; match self[target].kind { K::BinOp { op } => { let &[ctrl, mut lhs, mut rhs] = self[target].inputs.as_slice() else { unreachable!() }; let ty = self[target].ty; let is_float = self[lhs].ty.is_float(); if let (&K::CInt { value: a }, &K::CInt { value: b }) = (&self[lhs].kind, &self[rhs].kind) { return Some(self.new_const(ty, op.apply_binop(a, b, is_float))); } if lhs == rhs { match op { T::Sub => return Some(self.new_const(ty, 0)), T::Add => { let rhs = self.new_const(ty, 2); return Some(self.new_node( ty, K::BinOp { op: T::Mul }, [ctrl, lhs, rhs], tys, )); } _ => {} } } // this is more general the pushing constants to left to help deduplicate expressions more let mut changed = false; if op.is_comutative() && self[lhs].key() < self[rhs].key() { mem::swap(&mut lhs, &mut rhs); changed = true; } if let K::CInt { value } = self[rhs].kind { match (op, value) { (T::Eq, 0) if self[lhs].ty.is_pointer() || self[lhs].kind == Kind::Stck => { return Some(self.new_const(ty::Id::BOOL, 0)); } (T::Ne, 0) if self[lhs].ty.is_pointer() || self[lhs].kind == Kind::Stck => { return Some(self.new_const(ty::Id::BOOL, 1)); } (T::Add | T::Sub | T::Shl, 0) | (T::Mul | T::Div, 1) => return Some(lhs), (T::Mul, 0) => return Some(rhs), _ => {} } } if op.is_comutative() && self[lhs].kind == (K::BinOp { op }) { let &[_, a, b] = self[lhs].inputs.as_slice() else { unreachable!() }; if let K::CInt { value: av } = self[b].kind && let K::CInt { value: bv } = self[rhs].kind { // (a op #b) op #c => a op (#b op #c) let new_rhs = self.new_const(ty, op.apply_binop(av, bv, is_float)); return Some(self.new_node(ty, K::BinOp { op }, [ctrl, a, new_rhs], tys)); } if self.is_const(b) { // (a op #b) op c => (a op c) op #b let new_lhs = self.new_node(ty, K::BinOp { op }, [ctrl, a, rhs], tys); return Some(self.new_node(ty, K::BinOp { op }, [ctrl, new_lhs, b], tys)); } self.add_trigger(b, target); } if op == T::Add && self[lhs].kind == (K::BinOp { op: T::Mul }) && self[lhs].inputs[1] == rhs && let K::CInt { value } = self[self[lhs].inputs[2]].kind { // a * #n + a => a * (#n + 1) let new_rhs = self.new_const(ty, value + 1); return Some(self.new_node( ty, K::BinOp { op: T::Mul }, [ctrl, rhs, new_rhs], tys, )); } if op == T::Sub && self[lhs].kind == (K::BinOp { op: T::Add }) && let K::CInt { value: a } = self[rhs].kind && let K::CInt { value: b } = self[self[lhs].inputs[2]].kind { let new_rhs = self.new_const(ty, b - a); return Some(self.new_node( ty, K::BinOp { op: T::Add }, [ctrl, self[lhs].inputs[1], new_rhs], tys, )); } if op == T::Sub && self[lhs].kind == (K::BinOp { op }) { // (a - b) - c => a - (b + c) let &[_, a, b] = self[lhs].inputs.as_slice() else { unreachable!() }; let c = rhs; let new_rhs = self.new_node(ty, K::BinOp { op: T::Add }, [ctrl, b, c], tys); return Some(self.new_node(ty, K::BinOp { op }, [ctrl, a, new_rhs], tys)); } if changed { return Some(self.new_node(ty, self[target].kind, [ctrl, lhs, rhs], tys)); } } K::UnOp { op } => { let &[_, oper] = self[target].inputs.as_slice() else { unreachable!() }; let ty = self[target].ty; if matches!(op, TokenKind::Number | TokenKind::Float) && tys.size_of(self[oper].ty) == tys.size_of(ty) && self[oper].ty.is_integer() && ty.is_integer() { return Some(oper); } if let K::CInt { value } = self[oper].kind { let is_float = self[oper].ty.is_float(); return Some(self.new_const(ty, op.apply_unop(value, is_float))); } } K::If => { if self[target].inputs[0] == NEVER { return Some(NEVER); } if self[target].ty == ty::Id::VOID { match self.try_match_cond(target) { CondOptRes::Unknown => {} CondOptRes::Known { value, .. } => { let ty = if value { ty::Id::RIGHT_UNREACHABLE } else { ty::Id::LEFT_UNREACHABLE }; return Some(self.new_node_nop(ty, K::If, self[target].inputs.clone())); } } } } K::Then => { if self[target].inputs[0] == NEVER { return Some(NEVER); } if self[self[target].inputs[0]].ty == ty::Id::LEFT_UNREACHABLE { return Some(NEVER); } else if self[self[target].inputs[0]].ty == ty::Id::RIGHT_UNREACHABLE { return Some(self[self[target].inputs[0]].inputs[0]); } } K::Else => { if self[target].inputs[0] == NEVER { return Some(NEVER); } if self[self[target].inputs[0]].ty == ty::Id::RIGHT_UNREACHABLE { return Some(NEVER); } else if self[self[target].inputs[0]].ty == ty::Id::LEFT_UNREACHABLE { return Some(self[self[target].inputs[0]].inputs[0]); } } K::Region => { let (ctrl, side) = match self[target].inputs.as_slice() { [NEVER, NEVER] => return Some(NEVER), &[NEVER, ctrl] => (ctrl, 2), &[ctrl, NEVER] => (ctrl, 1), _ => return None, }; self.lock(target); for i in self[target].outputs.clone() { if self[i].kind == Kind::Phi { for o in self[i].outputs.clone() { if self.is_unlocked(o) { self.lock(o); self.queued_peeps.push(o); } } self.replace(i, self[i].inputs[side]); } } self.unlock(target); return Some(ctrl); } K::Call { .. } => { if self[target].inputs[0] == NEVER { return Some(NEVER); } } K::Return { file } => { if self[target].inputs[0] == NEVER { return Some(NEVER); } let mut new_inps = Vc::from(&self[target].inputs[..2]); 'a: for &n in self[target].inputs.clone().iter().skip(2) { if self[n].kind != Kind::Stre { new_inps.push(n); continue; } if let Some(&load) = self[n].outputs.iter().find(|&&n| self[n].kind == Kind::Load) { self.add_trigger(load, target); continue; } let mut cursor = n; let class = self.aclass_index(self[cursor].inputs[2]); if self[class.1].kind != Kind::Stck { new_inps.push(n); continue; } if self[class.1].outputs.iter().any(|&n| { self[n].kind != Kind::Stre && self[n].outputs.iter().any(|&n| self[n].kind != Kind::Stre) }) { new_inps.push(n); continue; } cursor = self[cursor].inputs[3]; while cursor != MEM { debug_assert_eq!(self[cursor].kind, Kind::Stre); if self.aclass_index(self[cursor].inputs[2]) != class { new_inps.push(n); continue 'a; } if let Some(&load) = self[cursor].outputs.iter().find(|&&n| self[n].kind == Kind::Load) { self.add_trigger(load, target); continue 'a; } cursor = self[cursor].inputs[3]; } } if new_inps.as_slice() != self[target].inputs.as_slice() { let ret = self.new_node_nop(ty::Id::VOID, Kind::Return { file }, new_inps); self[ret].pos = self[target].pos; return Some(ret); } } K::Phi => { let &[ctrl, lhs, rhs] = self[target].inputs.as_slice() else { unreachable!() }; if rhs == target || lhs == rhs { return Some(lhs); } if self[lhs].kind == Kind::Stre && self[rhs].kind == Kind::Stre && self[lhs].ty == self[rhs].ty && self[lhs].ty.loc(tys) == Loc::Reg && self[lhs].inputs[2] == self[rhs].inputs[2] && self[lhs].inputs[3] == self[rhs].inputs[3] { let pick_value = self.new_node( self[lhs].ty, Kind::Phi, [ctrl, self[lhs].inputs[1], self[rhs].inputs[1]], tys, ); let mut vc = self[lhs].inputs.clone(); vc[1] = pick_value; return Some(self.new_node(self[lhs].ty, Kind::Stre, vc, tys)); } // broken //let ty = self[target].ty; //if let Kind::BinOp { op } = self[lhs].kind // && self[rhs].kind == (Kind::BinOp { op }) //{ // debug_assert!(ty != ty::Id::VOID); // debug_assert_eq!( // self[lhs].ty.simple_size(), // ty.simple_size(), // "{:?} {:?}", // self[lhs].ty.expand(), // ty.expand() // ); // debug_assert_eq!( // self[rhs].ty.simple_size(), // ty.simple_size(), // "{:?} {:?}", // self[rhs].ty.expand(), // ty.expand() // ); // let inps = [ctrl, self[lhs].inputs[1], self[rhs].inputs[1]]; // let nlhs = self.new_node(ty, Kind::Phi, inps, tys); // let inps = [ctrl, self[lhs].inputs[2], self[rhs].inputs[2]]; // let nrhs = self.new_node(ty, Kind::Phi, inps, tys); // return Some(self.new_node(ty, Kind::BinOp { op }, [VOID, nlhs, nrhs], tys)); //} } K::Stck => { if let &[mut a, mut b] = self[target].outputs.as_slice() { if self[a].kind == Kind::Load { mem::swap(&mut a, &mut b); } if self[a].kind.is_call() && self[a].inputs.last() == Some(&target) && self[b].kind == Kind::Load && let &[store] = self[b].outputs.as_slice() && self[store].kind == Kind::Stre { let len = self[a].inputs.len(); let stre = self[store].inputs[3]; if stre != MEM { self[a].inputs.push(stre); self[a].inputs.swap(len - 1, len); self[stre].outputs.push(a); } return Some(self[store].inputs[2]); } } } K::Stre => { let &[_, value, region, store, ..] = self[target].inputs.as_slice() else { unreachable!() }; if self[value].kind == Kind::Load && self[value].inputs[1] == region { return Some(store); } let mut cursor = target; while self[cursor].kind == Kind::Stre && self[cursor].inputs[1] != VOID && let &[next_store] = self[cursor].outputs.as_slice() { if self[next_store].inputs[2] == region && self[next_store].ty == self[target].ty { return Some(store); } cursor = next_store; } 'eliminate: { if self[target].outputs.is_empty() { break 'eliminate; } if self[value].kind != Kind::Load || self[value].outputs.iter().any(|&n| self[n].kind != Kind::Stre) { for &ele in self[value].outputs.clone().iter().filter(|&&n| n != target) { self.add_trigger(ele, target); } break 'eliminate; } let &[_, stack, last_store] = self[value].inputs.as_slice() else { unreachable!() }; if self[stack].ty != self[value].ty || self[stack].kind != Kind::Stck { break 'eliminate; } let mut unidentifed = self[stack].outputs.clone(); let load_idx = unidentifed.iter().position(|&n| n == value).unwrap(); unidentifed.swap_remove(load_idx); let mut saved = Vc::default(); let mut cursor = last_store; let mut first_store = last_store; while cursor != MEM && self[cursor].kind == Kind::Stre { let mut contact_point = cursor; let mut region = self[cursor].inputs[2]; if let Kind::BinOp { op } = self[region].kind { debug_assert_matches!(op, TokenKind::Add | TokenKind::Sub); contact_point = region; region = self[region].inputs[1] } if region != stack { break; } let Some(index) = unidentifed.iter().position(|&n| n == contact_point) else { break 'eliminate; }; if self[self[cursor].inputs[1]].kind == Kind::Load && self[value].outputs.iter().any(|&n| { self.aclass_index(self[self[cursor].inputs[1]].inputs[1]).0 == self.aclass_index(self[n].inputs[2]).0 }) { break 'eliminate; } unidentifed.remove(index); saved.push(contact_point); first_store = cursor; cursor = *self[cursor].inputs.get(3).unwrap_or(&MEM); if unidentifed.is_empty() { break; } } if !unidentifed.is_empty() { break 'eliminate; } debug_assert_matches!( self[last_store].kind, Kind::Stre | Kind::Mem, "{:?}", self[last_store] ); debug_assert_matches!( self[first_store].kind, Kind::Stre | Kind::Mem, "{:?}", self[first_store] ); // FIXME: when the loads and stores become parallel we will need to get saved // differently let mut prev_store = store; for mut oper in saved.into_iter().rev() { let mut region = region; if let Kind::BinOp { op } = self[oper].kind { debug_assert_eq!(self[oper].outputs.len(), 1); debug_assert_eq!(self[self[oper].outputs[0]].kind, Kind::Stre); let new_region = self.new_node( self[oper].ty, Kind::BinOp { op }, [VOID, region, self[oper].inputs[2]], tys, ); self.pass_aclass(self.aclass_index(region).1, new_region); region = new_region; oper = self[oper].outputs[0]; } let mut inps = self[oper].inputs.clone(); debug_assert_eq!(inps.len(), 4); inps[2] = region; inps[3] = prev_store; prev_store = self.new_node_nop(self[oper].ty, Kind::Stre, inps); if self.is_unlocked(prev_store) { self.lock(prev_store); self.queued_peeps.push(prev_store); } } return Some(prev_store); } if let Some(&load) = self[target].outputs.iter().find(|&&n| self[n].kind == Kind::Load) { self.add_trigger(load, target); } else if value != VOID && self[value].kind != Kind::Load && self[store].kind == Kind::Stre && self[store].inputs[2] == region { if self[store].inputs[1] == value { return Some(store); } let mut inps = self[target].inputs.clone(); inps[3] = self[store].inputs[3]; return Some(self.new_node_nop(self[target].ty, Kind::Stre, inps)); } } K::Load => { fn range_of(s: &Nodes, mut region: Nid, ty: ty::Id, tys: &Types) -> Range { let loc = s.aclass_index(region).1; let full_size = tys.size_of( if matches!(s[loc].kind, Kind::Stck | Kind::Arg | Kind::Global { .. }) { s[loc].ty } else if let Some(ptr) = tys.base_of(s[loc].ty) { ptr } else { return 0..usize::MAX; }, ); let size = tys.size_of(ty); let mut offset = 0; loop { match s[region].kind { _ if region == loc => { break offset as usize..offset as usize + size as usize } Kind::Assert { kind: AssertKind::NullCheck, .. } => { region = s[region].inputs[2] } Kind::BinOp { op: TokenKind::Add | TokenKind::Sub } if let Kind::CInt { value } = s[s[region].inputs[2]].kind => { offset += value; region = s[region].inputs[1]; } _ => break 0..full_size as usize, }; } } let &[ctrl, region, store] = self[target].inputs.as_slice() else { unreachable!() }; let load_range = range_of(self, region, self[target].ty, tys); let mut cursor = store; while cursor != MEM && self[cursor].kind != Kind::Phi { if self[cursor].inputs[0] == ctrl && self[cursor].inputs[2] == region && self[cursor].ty == self[target].ty && (self[self[cursor].inputs[1]].kind != Kind::Load || (!self[target].outputs.is_empty() && self[target].outputs.iter().all(|&n| { self[n].kind != Kind::Stre || self .aclass_index(self[self[cursor].inputs[1]].inputs[1]) .0 != self.aclass_index(self[n].inputs[2]).0 }))) { return Some(self[cursor].inputs[1]); } let range = range_of(self, self[cursor].inputs[2], self[cursor].ty, tys); if range.start >= load_range.end || range.end <= load_range.start { cursor = self[cursor].inputs[3]; } else { let reg = self.aclass_index(self[cursor].inputs[2]).1; self.add_trigger(reg, target); break; } } if store != cursor { return Some(self.new_node( self[target].ty, Kind::Load, [ctrl, region, cursor], tys, )); } } K::Loop => { if self[target].inputs[1] == NEVER || self[target].inputs[0] == NEVER { self.lock(target); for o in self[target].outputs.clone() { if self[o].kind == Kind::Phi { self.remove_node_lookup(target); let prev = self[o].inputs[2]; self[o].inputs[2] = VOID; self[VOID].outputs.push(o); let index = self[prev].outputs.iter().position(|&n| n == o).unwrap(); self[prev].outputs.swap_remove(index); self.lock(o); self.remove(prev); self.unlock(o); for o in self[o].outputs.clone() { if self.is_unlocked(o) { self.lock(o); self.queued_peeps.push(o); } } self.replace(o, self[o].inputs[1]); } } self.unlock(target); return Some(self[target].inputs[0]); } } K::Die => { if self[target].inputs[0] == NEVER { return Some(NEVER); } } K::Assert { kind, .. } => 'b: { let pin = match (kind, self.try_match_cond(target)) { (AssertKind::NullCheck, CondOptRes::Known { value: false, pin }) => pin, (AssertKind::UnwrapCheck, CondOptRes::Unknown) => None, _ => break 'b, } .unwrap_or(self[target].inputs[0]); for out in self[target].outputs.clone() { if !self[out].kind.is_pinned() && self[out].inputs[0] != pin { self.modify_input(out, 0, pin); } } return Some(self[target].inputs[2]); } K::Start => {} _ if self.is_cfg(target) && self.idom(target, None) == NEVER => panic!(), K::Entry | K::Mem | K::Loops | K::End | K::CInt { .. } | K::Arg | K::Global { .. } | K::Join => {} } None } fn try_match_cond(&self, target: Nid) -> CondOptRes { let &[ctrl, cond, ..] = self[target].inputs.as_slice() else { unreachable!() }; if let Kind::CInt { value } = self[cond].kind { return CondOptRes::Known { value: value != 0, pin: None }; } let mut cursor = ctrl; while cursor != ENTRY { let ctrl = &self[cursor]; // TODO: do more inteligent checks on the condition if matches!(ctrl.kind, Kind::Then | Kind::Else) { if self[ctrl.inputs[0]].kind == Kind::End { return CondOptRes::Unknown; } debug_assert_eq!(self[ctrl.inputs[0]].kind, Kind::If); let other_cond = self[ctrl.inputs[0]].inputs[1]; if let Some(value) = self.matches_cond(cond, other_cond) { return CondOptRes::Known { value: (ctrl.kind == Kind::Then) ^ !value, pin: Some(cursor), }; } } cursor = self.idom(cursor, None); } CondOptRes::Unknown } fn matches_cond(&self, to_match: Nid, matches: Nid) -> Option { use TokenKind as K; let [tn, mn] = [&self[to_match], &self[matches]]; match (tn.kind, mn.kind) { _ if to_match == matches => Some(true), (Kind::BinOp { op: K::Ne }, Kind::BinOp { op: K::Eq }) | (Kind::BinOp { op: K::Eq }, Kind::BinOp { op: K::Ne }) if tn.inputs[1..] == mn.inputs[1..] => { Some(false) } (_, Kind::BinOp { op: K::Band }) => self .matches_cond(to_match, mn.inputs[1]) .or(self.matches_cond(to_match, mn.inputs[2])), (_, Kind::BinOp { op: K::Bor }) => match ( self.matches_cond(to_match, mn.inputs[1]), self.matches_cond(to_match, mn.inputs[2]), ) { (None, Some(a)) | (Some(a), None) => Some(a), (Some(b), Some(a)) if a == b => Some(a), _ => None, }, _ => None, } } fn is_const(&self, id: Nid) -> bool { matches!(self[id].kind, Kind::CInt { .. }) } fn replace(&mut self, target: Nid, with: Nid) { debug_assert_ne!(target, with, "{:?}", self[target]); for out in self[target].outputs.clone() { let index = self[out].inputs.iter().position(|&p| p == target).unwrap(); self.modify_input(out, index, with); } } fn modify_input(&mut self, target: Nid, inp_index: usize, with: Nid) -> Nid { self.remove_node_lookup(target); debug_assert_ne!( self[target].inputs[inp_index], with, "{:?} {:?}", self[target], self[with] ); if self[target].is_not_gvnd() && (self[target].kind != Kind::Phi || with == 0) { let prev = self[target].inputs[inp_index]; self[target].inputs[inp_index] = with; self[with].outputs.push(target); let index = self[prev].outputs.iter().position(|&o| o == target).unwrap(); self[prev].outputs.swap_remove(index); self.remove(prev); target } else { let prev = self[target].inputs[inp_index]; self[target].inputs[inp_index] = with; let (entry, hash) = self.lookup.entry(target.key(&self.values), &self.values); match entry { hash_map::RawEntryMut::Occupied(other) => { let rpl = other.get_key_value().0.value; self[target].inputs[inp_index] = prev; self.lookup.insert(target.key(&self.values), target, &self.values); self.replace(target, rpl); rpl } hash_map::RawEntryMut::Vacant(slot) => { slot.insert(crate::ctx_map::Key { value: target, hash }, ()); let index = self[prev].outputs.iter().position(|&o| o == target).unwrap(); self[prev].outputs.swap_remove(index); self[with].outputs.push(target); self.remove(prev); target } } } } #[track_caller] fn unlock_remove(&mut self, id: Nid) -> bool { self.unlock(id); self.remove(id) } fn iter(&self) -> impl DoubleEndedIterator { self.values.iter().enumerate().filter_map(|(i, s)| Some((i as _, s.as_ref().ok()?))) } #[expect(clippy::format_in_format_args)] fn basic_blocks_instr(&self, out: &mut String, node: Nid) -> core::fmt::Result { match self[node].kind { Kind::Assert { .. } | Kind::Start => unreachable!("{} {out}", self[node].kind), Kind::End => return Ok(()), Kind::If => write!(out, " if: "), Kind::Region | Kind::Loop => writeln!(out, " goto: {node}"), Kind::Return { .. } => write!(out, " ret: "), Kind::Die => write!(out, " die: "), Kind::CInt { value } => write!(out, "cint: #{value:<4}"), Kind::Phi => write!(out, " phi: "), Kind::Arg => write!( out, " arg: {:<5}", self[VOID].outputs.iter().position(|&n| n == node).unwrap() - 2 ), Kind::BinOp { op } | Kind::UnOp { op } => { write!(out, "{:>4}: ", op.name()) } Kind::Call { func, args: _ } => { write!(out, "call: {func} {} ", self[node].depth.get()) } Kind::Global { global } => write!(out, "glob: {global:<5}"), Kind::Entry => write!(out, "ctrl: {:<5}", "entry"), Kind::Then => write!(out, "ctrl: {:<5}", "then"), Kind::Else => write!(out, "ctrl: {:<5}", "else"), Kind::Stck => write!(out, "stck: "), Kind::Load => write!(out, "load: "), Kind::Stre => write!(out, "stre: "), Kind::Mem => write!(out, " mem: "), Kind::Loops => write!(out, "loops: "), Kind::Join => write!(out, "join: "), }?; if self[node].kind != Kind::Loop && self[node].kind != Kind::Region { writeln!( out, " {:<3} {:<14} {}", node, format!("{:?}", self[node].inputs), format!("{:?}", self[node].outputs) )?; } Ok(()) } fn basic_blocks_low( &self, out: &mut String, mut node: Nid, visited: &mut BitSet, ) -> core::fmt::Result { let iter = |nodes: &Nodes, node| nodes[node].outputs.clone().into_iter().rev(); while visited.set(node) { match self[node].kind { Kind::Start => { writeln!(out, "start: {}", self[node].depth.get())?; let mut cfg_index = Nid::MAX; for o in iter(self, node) { self.basic_blocks_instr(out, o)?; if self[o].kind.is_cfg() { cfg_index = o; } } node = cfg_index; } Kind::End => break, Kind::If => { self.basic_blocks_low(out, self[node].outputs[0], visited)?; node = self[node].outputs[1]; } Kind::Region => { writeln!( out, "region{node}: {} {} {:?}", self[node].depth.get(), self[node].loop_depth.get(), self[node].inputs )?; let mut cfg_index = Nid::MAX; for o in iter(self, node) { self.basic_blocks_instr(out, o)?; if self.is_cfg(o) { cfg_index = o; } } node = cfg_index; } Kind::Loop => { writeln!( out, "loop{node}: {} {} {:?}", self[node].depth.get(), self[node].loop_depth.get(), self[node].outputs )?; let mut cfg_index = Nid::MAX; for o in iter(self, node) { self.basic_blocks_instr(out, o)?; if self.is_cfg(o) { cfg_index = o; } } node = cfg_index; } Kind::Return { .. } | Kind::Die => { node = self[node].outputs[0]; } Kind::Then | Kind::Else | Kind::Entry => { writeln!( out, "b{node}: {} {} {:?}", self[node].depth.get(), self[node].loop_depth.get(), self[node].outputs )?; let mut cfg_index = Nid::MAX; for o in iter(self, node) { self.basic_blocks_instr(out, o)?; if self.is_cfg(o) { cfg_index = o; } } node = cfg_index; } Kind::Call { .. } => { let mut cfg_index = Nid::MAX; let mut print_ret = true; for o in iter(self, node) { if self[o].inputs[0] == node && (self[node].outputs[0] != o || mem::take(&mut print_ret)) { self.basic_blocks_instr(out, o)?; } if self.is_cfg(o) { cfg_index = o; } } node = cfg_index; } _ => unreachable!(), } } Ok(()) } fn basic_blocks(&self) { let mut out = String::new(); let mut visited = BitSet::default(); self.basic_blocks_low(&mut out, VOID, &mut visited).unwrap(); log::info!("{out}"); } fn is_cfg(&self, o: Nid) -> bool { self[o].kind.is_cfg() } fn check_final_integrity(&self, disp: ty::Display) { if !cfg!(debug_assertions) { return; } let mut failed = false; for (id, node) in self.iter() { if self.is_locked(id) { log::error!("{} {} {:?}", node.lock_rc.get(), 0, node.kind); failed = true; } if !matches!(node.kind, Kind::End | Kind::Mem | Kind::Arg | Kind::Loops) && node.outputs.is_empty() { log::error!("outputs are empry {id} {:?}", node); failed = true; } if node.inputs.first() == Some(&NEVER) && id != NEVER { log::error!("is unreachable but still present {id} {:?}", node.kind); failed = true; } if node.outputs.contains(&id) && !matches!(node.kind, Kind::Loop | Kind::End) { log::error!("node depends on it self and its not a loop {id} {:?}", node); failed = true; } } if failed { self.graphviz_in_browser(disp); panic!() } } fn check_loop_depth_integrity(&self, disp: ty::Display) { if !cfg!(debug_assertions) { return; } let mut failed = false; for &loob in self[LOOPS].outputs.iter() { let mut stack = vec![self[loob].inputs[1]]; let mut seen = BitSet::default(); seen.set(loob); let depth = self.loop_depth(loob, None); while let Some(nid) = stack.pop() { if seen.set(nid) { if depth > self.loop_depth(nid, None) { failed = true; log::error!("{depth} {} {nid} {:?}", self.loop_depth(nid, None), self[nid]); } match self[nid].kind { Kind::Loop | Kind::Region => { stack.extend(&self[nid].inputs[..2]); } _ => stack.push(self[nid].inputs[0]), } } } } if failed { self.graphviz_in_browser(disp); panic!() } } 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 node = loob.node; let lvar = &mut loob.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 node = loob.node; let lvar = &mut loob.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 assert_dominance(&self, nd: Nid, min: Nid, check_outputs: bool, scheds: Option<&[Nid]>) { if !cfg!(debug_assertions) { return; } let node = self[nd].clone(); for &i in &node.inputs[1..] { let dom = self.idom(i, scheds); debug_assert!( self.dominates(dom, min, scheds), "{dom} {min} {node:?} {:?}", self.basic_blocks() ); } if check_outputs { for &o in node.outputs.iter() { let dom = self.use_block(nd, o, scheds); debug_assert!( self.dominates(min, dom, scheds), "{min} {dom} {node:?} {:?}", self.basic_blocks() ); } } } fn dominates(&self, dominator: Nid, mut dominated: Nid, scheds: Option<&[Nid]>) -> bool { loop { if dominator == dominated { break true; } debug_assert!(dominated != VOID); if self.idepth(dominator, scheds) > self.idepth(dominated, scheds) { break false; } dominated = self.idom(dominated, scheds); } } fn is_data_dep(&self, val: Nid, user: Nid) -> bool { match self[user].kind { Kind::Return { .. } => self[user].inputs[1] == val, _ if self.is_cfg(user) && !matches!(self[user].kind, Kind::Call { .. } | Kind::If) => { false } Kind::Join => false, Kind::Stre => { debug_assert_eq!( self[user].inputs[4..] .iter() .filter(|&&v| self[v].kind != Kind::Load) .copied() .collect::>(), vec![] ); debug_assert_matches!( self[self[user].inputs[3]].kind, Kind::Stre | Kind::Mem | Kind::Phi ); self[user].inputs.iter().position(|&v| v == val).is_some_and(|v| v < 3) } Kind::Load => self[user].inputs[2] != val, _ => self[user].inputs[0] != val || self[user].inputs[1..].contains(&val), } } fn this_or_delegates<'a>(&'a self, source: Nid, target: &'a Nid) -> (Nid, &'a [Nid]) { if self.is_unlocked(*target) { (source, core::slice::from_ref(target)) } else { (*target, self[*target].outputs.as_slice()) } } fn is_hard_zero(&self, nid: Nid) -> bool { self[nid].kind == Kind::CInt { value: 0 } && self[nid].outputs.iter().all(|&n| self[n].kind != Kind::Phi) } fn add_trigger(&mut self, blocker: Nid, target: Nid) { if !self[blocker].peep_triggers.contains(&target) { self[blocker].peep_triggers.push(target); } } } enum CondOptRes { Unknown, Known { value: bool, pin: Option }, } impl ops::Index for Nodes { type Output = Node; fn index(&self, index: Nid) -> &Self::Output { self.values[index as usize].as_ref().unwrap_or_else(|(_, bt)| panic!("{index} {bt:#?}")) } } impl ops::IndexMut for Nodes { fn index_mut(&mut self, index: Nid) -> &mut Self::Output { self.values[index as usize].as_mut().unwrap_or_else(|(_, bt)| panic!("{index} {bt:#?}")) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum AssertKind { NullCheck, UnwrapCheck, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] #[repr(u8)] pub enum Kind { #[default] Start, // [ctrl] Entry, // [VOID] Mem, // [VOID] Loops, // [terms...] End, // [ctrl, cond] If, Then, Else, // [lhs, rhs] Region, // [entry, back] Loop, // [ctrl, ?value] Return { file: ty::Module, }, // [ctrl] Die, // [ctrl] CInt { value: i64, }, // [ctrl, lhs, rhs] Phi, Arg, // [ctrl, oper] UnOp { op: lexer::TokenKind, }, // [ctrl, lhs, rhs] BinOp { op: lexer::TokenKind, }, // [ctrl] Global { global: ty::Global, }, // [ctrl, ...args] Call { func: ty::Func, args: ty::Tuple, }, // [ctrl, cond, value] Assert { kind: AssertKind, pos: Pos, }, // [ctrl] Stck, // [ctrl, memory] Load, // [ctrl, value, memory] Stre, // [ctrl, a, b] Join, } impl Kind { fn is_call(&self) -> bool { matches!(self, Kind::Call { .. }) } fn is_eca(&self) -> bool { matches!(self, Kind::Call { func: ty::Func::ECA, .. }) } fn is_pinned(&self) -> bool { self.is_cfg() || self.is_at_start() || matches!(self, Self::Phi | Kind::Assert { .. }) } fn is_at_start(&self) -> bool { matches!(self, Self::Arg | Self::Mem | Self::Loops | Self::Entry) } fn is_cfg(&self) -> bool { matches!( self, Self::Start | Self::End | Self::Return { .. } | Self::Die | Self::Entry | Self::Then | Self::Else | Self::Call { .. } | Self::If | Self::Region | Self::Loop ) } fn ends_basic_block(&self) -> bool { matches!(self, Self::Return { .. } | Self::If | Self::End | Self::Die) } fn starts_basic_block(&self) -> bool { matches!(self, Self::Region | Self::Loop | Self::Start | Kind::Then | Kind::Else) } fn is_peeped(&self) -> bool { !matches!(self, Self::End | Self::Arg | Self::Mem | Self::Loops) } } impl fmt::Display for Kind { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Kind::CInt { value } => write!(f, "#{value}"), Kind::Entry => write!(f, "ctrl[entry]"), Kind::Then => write!(f, "ctrl[then]"), Kind::Else => write!(f, "ctrl[else]"), Kind::BinOp { op } => write!(f, "{op}"), Kind::Call { func, .. } => write!(f, "call {func}"), slf => write!(f, "{slf:?}"), } } } #[derive(Debug, Default, Clone)] pub struct Node { kind: Kind, inputs: Vc, outputs: Vc, peep_triggers: Vc, clobbers: BitSet, ty: ty::Id, pos: Pos, depth: Cell, lock_rc: Cell, loop_depth: Cell, aclass: AClassId, } impl Node { fn is_dangling(&self) -> bool { self.outputs.is_empty() && self.lock_rc.get() == 0 && self.kind != Kind::Arg } fn key(&self) -> (Kind, &[Nid], ty::Id) { (self.kind, &self.inputs, self.ty) } fn is_lazy_phi(&self, loob: Nid) -> bool { self.kind == Kind::Phi && self.inputs[2] == 0 && self.inputs[0] == loob } fn is_not_gvnd(&self) -> bool { (self.kind == Kind::Phi && self.inputs[2] == 0) || matches!(self.kind, Kind::Arg | Kind::Stck | Kind::Stre) || self.kind.is_cfg() } fn is_mem(&self) -> bool { matches!(self.kind, Kind::Stre | Kind::Load | Kind::Stck) } fn is_data_phi(&self) -> bool { self.kind == Kind::Phi && self.ty != ty::Id::VOID } fn has_no_value(&self) -> bool { (self.kind.is_cfg() && (!self.kind.is_call() || self.ty == ty::Id::VOID)) || matches!(self.kind, Kind::Stre) } } type LoopDepth = u16; type LockRc = u16; type IDomDepth = u16; #[derive(Clone)] struct Loop { node: Nid, ctrl: [StrongRef; 2], ctrl_scope: [Scope; 2], scope: Scope, } 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, pos: Vec, ret: Option, task_base: usize, inline_var_base: usize, inline_aclass_base: usize, inline_depth: u16, inline_ret: Option<(Value, StrongRef, Scope, Option)>, nodes: Nodes, ctrl: StrongRef, loops: Vec, scope: Scope, } impl ItemCtx { fn init(&mut self, file: Module, ret: Option, task_base: usize) { debug_assert_eq!(self.loops.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); self.file = file; 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: &[parser::Ast]) { 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); } } fn write_reloc(doce: &mut [u8], offset: usize, value: i64, size: u16) { let value = value.to_ne_bytes(); doce[offset..offset + size as usize].copy_from_slice(&value[..size as usize]); } #[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, nid_set: BitSet, } impl Pool { fn push_ci( &mut self, file: Module, ret: Option, task_base: usize, target: &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, ret, task_base); 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); 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 [parser::Ast], 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, 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, Err("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 = Global { 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(Func { file, sig: Some(Sig { args: Tuple::empty(), ret }), ..Default::default() }); self.ct_backend.emit_ct_body(fuc, &mut 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) ); 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 fuc.comp_state[state_slot] == CompState::Dead { fuc.comp_state[state_slot] = CompState::Queued(self.tys.tasks.len() as _); 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, .. } if let Some(ty) = ctx.ty && ty.is_float() => { Some(self.ci.nodes.new_const_lit(ty, (value as f64).to_bits() as i64)) } Expr::Number { value, .. } => { self.gen_inferred_const(ctx, ty::Id::DINT, value, ty::Id::is_integer) } Expr::Float { value, .. } => { self.gen_inferred_const(ctx, ty::Id::F32, value as i64, ty::Id::is_float) } 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, pos, .. } => { let decl = self.find_type(pos, self.ci.file, self.ci.file, Ok(id)); match decl.expand() { ty::Kind::NEVER => Value::NEVER, ty::Kind::Global(global) => self.gen_global(global), ty::Kind::Const(cnst) => self.gen_const(cnst, ctx), _ => Some(Value::new(Nid::MAX).ty(decl)), } } Expr::Comment { .. } => Some(Value::VOID), Expr::String { pos, literal } => { let literal = &literal[1..literal.len() - 1]; let report = |bytes: &core::str::Bytes, message: &str| { self.error(pos + (literal.len() - bytes.len()) as u32 - 1, message) }; let mut data = Vec::::with_capacity(literal.len()); crate::endoce_string(literal, &mut data, report).unwrap(); let ty = self.tys.make_ptr(ty::Id::U8); let global = match self.tys.strings.entry(&data, &self.tys.ins.globals) { (hash_map::RawEntryMut::Occupied(occupied_entry), _) => { occupied_entry.get_key_value().0.value.0 } (hash_map::RawEntryMut::Vacant(vacant_entry), hash) => { let global = self.tys.ins.globals.push(Global { data, ty, ..Default::default() }); vacant_entry .insert(crate::ctx_map::Key { value: StringRef(global), hash }, ()) .0 .value .0 } }; let global = self.ci.nodes.new_node_nop(ty, Kind::Global { global }, [VOID]); self.ci.nodes[global].aclass = GLOBAL_ACLASS as _; Some(Value::new(global).ty(ty)) } Expr::Return { pos, val } => { let mut value = if let Some(val) = val { self.raw_expr_ctx(val, Ctx { ty: self.ci.ret })? } else { Value { ty: ty::Id::VOID, ..Default::default() } }; self.strip_var(&mut value); let expected = *self.ci.ret.get_or_insert(value.ty); self.assert_ty(pos, &mut value, expected, "return value"); self.strip_ptr(&mut value); 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((pv, ctrl, scope, aclass)) = &mut self.ci.inline_ret { 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, 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); } else { 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(*self.ci.nodes[value.id].inputs.get(1).unwrap_or(&VOID)); 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[NEVER].inputs.push(self.ci.ctrl.get()); self.ci.nodes[self.ci.ctrl.get()].outputs.push(NEVER); None } Expr::Field { target, name, pos } => { let mut vtarget = self.raw_expr(target)?; self.strip_var(&mut vtarget); self.implicit_unwrap(pos, &mut vtarget); let tty = vtarget.ty; match self.tys.base_of(tty).unwrap_or(tty).expand() { ty::Kind::Module(m) => { match self.find_type(pos, self.ci.file, m, Err(name)).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(Value::new(Nid::MAX).ty(v.compress())), } } ty::Kind::Enum(e) => { let intrnd = self.tys.names.project(name); self.gen_enum_variant(pos, e, intrnd) } ty::Kind::Struct(s) => { let Some((offset, ty)) = OffsetIter::offset_of(self.tys, s, name) else { let field_list = self .tys .struct_fields(s) .iter() .map(|f| self.tys.names.ident_str(f.name)) .intersperse("', '") .collect::(); self.error( pos, fa!( "the '{}' does not have this field, \ but it does have '{field_list}'", self.ty_display(tty) ), ); return Value::NEVER; }; Some(Value::ptr(self.offset(vtarget.id, offset)).ty(ty)) } _ => { 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) ), ); 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.raw_expr_ctx(val, ctx)?; self.strip_var(&mut val); 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 { self.error( pos, fa!("the '{}' can not be dereferneced", self.ty_display(vl.ty)), ); return Value::NEVER; }; 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 { self.error( pos, fa!("expected inferred type to be enum but got '{}'", self.ty_display(ty)), ); return Value::NEVER; }; 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))); Value::NEVER } } 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))); Value::NEVER } } Expr::BinOp { left, op: TokenKind::Decl, right, pos } => { let mut right = self.expr(right)?; if right.ty.loc(self.tys) == Loc::Stack { let stck = self.new_stack(pos, right.ty); self.store_mem(stck, right.ty, right.id); right.id = stck; right.ptr = true; } 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 mut value = self.expr_ctx(right, Ctx::default().with_ty(dest.ty))?; self.assert_ty(pos, &mut value, 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"); Value::NEVER } Expr::BinOp { left, op: op @ (TokenKind::Eq | TokenKind::Ne), right: Expr::Null { .. }, .. } => { let mut cmped = self.raw_expr(left)?; self.strip_var(&mut cmped); let Some(ty) = self.tys.inner_of(cmped.ty) else { self.error( left.pos(), fa!("'{}' is never null, remove this check", self.ty_display(cmped.ty)), ); return Value::NEVER; }; Some(Value::new(self.gen_null_check(cmped, ty, op)).ty(ty::Id::BOOL)) } Expr::BinOp { left, pos, op, right } if !matches!(op, TokenKind::Assign | TokenKind::Decl) => { let mut lhs = self.raw_expr_ctx(left, ctx)?; self.strip_var(&mut lhs); self.implicit_unwrap(left.pos(), &mut lhs); match lhs.ty.expand() { _ if lhs.ty.is_pointer() || lhs.ty.is_integer() || lhs.ty == ty::Id::BOOL || (lhs.ty == ty::Id::TYPE && matches!(op, TokenKind::Eq | TokenKind::Ne)) || (lhs.ty.is_float() && op.is_supported_float_op()) => { 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.strip_var(&mut 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) } ty::Kind::Struct(s) if op.is_homogenous() => { self.ci.nodes.lock(lhs.id); let rhs = self.raw_expr_ctx(right, Ctx::default().with_ty(lhs.ty)); self.ci.nodes.unlock(lhs.id); let mut rhs = rhs?; self.strip_var(&mut rhs); 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.raw_expr_ctx(right, Ctx::default().with_ty(lhs.ty)); self.ci.nodes.unlock(lhs.id); let mut rhs = rhs?; self.strip_var(&mut 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) } _ => { self.error( pos, fa!("'{} {op} _' is not supported", self.ty_display(lhs.ty)), ); Value::NEVER } } } Expr::Index { base, index } => { let mut bs = self.raw_expr(base)?; self.strip_var(&mut bs); if let Some(base) = self.tys.base_of(bs.ty) { bs.ptr = true; bs.ty = base; } let ty::Kind::Slice(s) = bs.ty.expand() else { self.error( base.pos(), fa!( "cant index into '{}' which is not array nor slice", self.ty_display(bs.ty) ), ); return Value::NEVER; }; let elem = self.tys.ins.slices[s].elem; let mut idx = self.expr_ctx(index, Ctx::default().with_ty(ty::Id::DINT))?; self.assert_ty(index.pos(), &mut idx, ty::Id::DINT, "subscript"); 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)) } 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: "sizeof", args: [ty], .. } => { let ty = self.ty(ty); self.gen_inferred_const(ctx, ty::Id::DINT, self.tys.size_of(ty), ty::Id::is_integer) } 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, ty::Id::is_integer) } Expr::Directive { name: "bitcast", args: [val], pos } => { let mut val = self.raw_expr(val)?; self.strip_var(&mut 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 => { let stack = self.new_stack(pos, ty); self.store_mem(stack, val.ty, val.id); val.id = stack; val.ptr = true; } _ => {} } val.ty = ty; Some(val) } Expr::Directive { name: "unwrap", args: [expr], .. } => { let mut val = self.raw_expr(expr)?; self.strip_var(&mut val); if !val.ty.is_optional() { self.error( expr.pos(), fa!( "only optional types can be unwrapped ('{}' is not optional)", self.ty_display(val.ty) ), ); return Value::NEVER; }; 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() { self.error( expr.pos(), fa!( "only integers can be truncated ('{}' is not an integer)", self.ty_display(val.ty) ), ); return Value::NEVER; } 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() { self.error( expr.pos(), fa!( "only floats can be truncated ('{}' is not a float)", self.ty_display(val.ty) ), ); return Value::NEVER; } 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, _ => { self.error( expr.pos(), fa!("expected float ('{}' is not a float)", self.ty_display(val.ty)), ); return Value::NEVER; } }; 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], .. } => { let mut val = self.expr_ctx(expr, Ctx::default().with_ty(ty::Id::INT))?; let (ret_ty, expected) = match val.ty.simple_size().unwrap() { 8 => (ty::Id::F64, ty::Id::INT), _ => (ty::Id::F32, ty::Id::INT), }; self.assert_ty(expr.pos(), &mut val, expected, "converted integer"); Some(self.ci.nodes.new_node_lit( ret_ty, 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.raw_expr_ctx(expr, Ctx::default().with_ty(ty))?; if let Some(ity) = ctx.ty && ity.try_upcast(ty) == Some(ty) && val.ty == ity { self.error(pos, "the type is known at this point, remove the hint"); } self.strip_var(&mut val); self.assert_ty(expr.pos(), &mut val, ty, "hinted expr"); Some(val) } Expr::Directive { pos, name: "eca", args } => { inference!(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 ty.loc(self.tys) { Loc::Reg => None, Loc::Stack => { let stck = self.new_stack(pos, ty); inps.push(stck); Some(Value::ptr(stck).ty(ty)) } }; inps[0] = self.ci.ctrl.get(); self.ci.ctrl.set( self.ci.nodes.new_node_nop(ty, Kind::Call { func: ty::Func::ECA, args }, inps), &mut self.ci.nodes, ); self.add_clobber_stores(clobbered_aliases); alt_value.or(Some(Value::new(self.ci.ctrl.get()).ty(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::Tupl { 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 or slice", ".(...)"); match sty.expand() { ty::Kind::Struct(s) => { let mem = self.new_stack(pos, sty); 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 mut value = self.expr_ctx(field, Ctx::default().with_ty(ty))?; _ = self.assert_ty(field.pos(), &mut value, 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(sty)) } ty::Kind::Slice(s) => { let slice = &self.tys.ins.slices[s]; let len = slice.len().unwrap_or(fields.len()); let elem = slice.elem; let elem_size = self.tys.size_of(elem); let aty = slice .len() .map_or_else(|| self.tys.make_array(elem, len as ArrayLen), |_| sty); if len != fields.len() { self.error( pos, fa!( "expected '{}' but constructor has {} elements", self.ty_display(aty), fields.len() ), ); return Value::NEVER; } let mem = self.new_stack(pos, aty); for (field, offset) in fields.iter().zip((0u32..).step_by(elem_size as usize)) { let mut value = self.expr_ctx(field, Ctx::default().with_ty(elem))?; _ = self.assert_ty(field.pos(), &mut value, elem, "array value"); let mem = self.offset(mem, offset); self.store_mem(mem, elem, value.id); } Some(Value::ptr(mem).ty(aty)) } _ => { let inferred = if ty.is_some() { "" } else { "inferred " }; self.error( pos, fa!( "the {inferred}type of the constructor is `{}`, \ but thats not a struct nor slice or array", self.ty_display(sty) ), ); Value::NEVER } } } 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", ".{...}"); let ty::Kind::Struct(s) = sty.expand() else { let inferred = if ty.is_some() { "" } else { "inferred " }; self.error( pos, fa!( "the {inferred}type of the constructor is `{}`, \ but thats not a struct", self.ty_display(sty) ), ); return Value::NEVER; }; // TODO: dont allocate 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 mut value = self.expr_ctx(&field.value, Ctx::default().with_ty(ty))?; self.assert_ty(field.pos, &mut value, ty, fa!("field {}", field.name)); let mem = self.offset(mem, offset); self.store_mem(mem, ty, value.id); } 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)) } Expr::Block { stmts, .. } => { let base = self.ci.scope.vars.len(); let aclass_base = self.ci.scope.aclasses.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(mut 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) ), ); } self.assert_ty(stmt.pos(), &mut id, ty::Id::VOID, "statement"); } else { break; } } 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 { body, .. } => { 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 { 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), }); 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 Loop { ctrl: [con, ..], ctrl_scope: [cons, ..], .. } = self.ci.loops.last_mut().unwrap(); 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 Loop { node, ctrl: [.., bre], ctrl_scope: [.., mut bres], mut scope } = self.ci.loops.pop().unwrap(); 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 mut cnd = self.expr_ctx(cond, Ctx::default().with_ty(ty::Id::BOOL))?; self.assert_ty(cond.pos(), &mut cnd, 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 { self.error( pos, fa!( "match operates on enums (for now), '{}' is not an enum", self.ty_display(value.ty) ), ); return Value::NEVER; }; let mut covered_values = vec![Pos::MAX; self.tys.enum_field_range(e).len()]; let mut scopes = vec![]; let mut else_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, &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 { 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}'")); } 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; } Some(Value::VOID) } ref e => { self.error_unhandled_ast(e, "bruh"); Value::NEVER } } } 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 { let field_list = self .tys .enum_fields(e) .iter() .map(|f| self.tys.names.ident_str(f.name)) .intersperse("', '") .collect::(); self.error( pos, fa!( "the '{}' does not have this variant, \ but it does have '{field_list}'", self.ty_display(e.into()) ), ); return Value::NEVER; }; 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, filter: impl Fn(ty::Id) -> bool, ) -> Option { Some( self.ci.nodes.new_const_lit( ctx.ty .map(|ty| self.tys.inner_of(ty).unwrap_or(ty)) .filter(|&ty| filter(ty)) .unwrap_or(fallback), value, ), ) } fn gen_call(&mut self, func: &Expr, args: &[Expr], inline: bool) -> Option { let ty = self.ty(func); let ty::Kind::Func(mut fu) = ty.expand() else { self.error(func.pos(), fa!("compiler cant (yet) call '{}'", self.ty_display(ty))); return Value::NEVER; }; let Some(sig) = self.compute_signature(&mut fu, func.pos(), args) else { return Value::NEVER; }; let Func { expr, file, is_inline, .. } = self.tys.ins.funcs[fu]; let ast = &self.files[file.index()]; let &Expr::Closure { args: cargs, body, .. } = expr.get(ast) else { unreachable!() }; if args.len() != cargs.len() { self.error( func.pos(), fa!( "expected {} function argumenr{}, got {}", cargs.len(), if cargs.len() == 1 { "" } else { "s" }, args.len() ), ); } if inline && is_inline { self.error( func.pos(), "function is declared as inline so this @inline directive only reduces readability", ); } 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(); 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.raw_expr_ctx(arg, Ctx::default().with_ty(ty))?; self.strip_var(&mut value); 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_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_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.file = prev_file; self.ci.inline_depth -= 1; self.ci.inline_var_base = prev_var_base; self.ci.inline_aclass_base = prev_aclass_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_eca() || self.ci.nodes[ctrl.get()].inputs[0] != prev_ctrl) { self.error(body.pos(), "function is makred inline but it contains controlflow"); } 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(); 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 mut value = self.raw_expr_ctx(arg, Ctx::default().with_ty(ty))?; self.strip_var(&mut value); debug_assert_ne!(self.ci.nodes[value.id].kind, Kind::Stre); self.assert_ty(arg.pos(), &mut value, ty, fa!("argument {}", carg.name)); self.strip_ptr(&mut value); 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 }, inps), &mut self.ci.nodes, ); self.add_clobber_stores(clobbered_aliases); alt_value.or(Some(Value::new(self.ci.ctrl.get()).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 = mem::replace(&mut self.ci.file, c.file); let f = &self.files[c.file.index()]; let Expr::BinOp { left, right, .. } = c.ast.get(f) else { unreachable!() }; let mut value = left .find_pattern_path(c.name, right, |expr, is_ct| { debug_assert!(is_ct); self.raw_expr_ctx(expr, ctx) }) .unwrap_or_else(|_| unreachable!())?; self.strip_var(&mut value); self.ci.file = prev; 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: &mut ty::Func, pos: Pos, args: &[Expr]) -> Option { let Func { file, expr, sig, .. } = self.tys.ins.funcs[*func]; let fast = &self.files[file.index()]; let &Expr::Closure { args: cargs, ret, .. } = expr.get(fast) else { unreachable!(); }; Some(if let Some(sig) = sig { sig } else { 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, &carg.ty); self.tys.tmp.args.push(ty); let sym = parser::find_symbol(&fast.symbols, carg.id); let ty = if sym.flags & idfl::COMPTIME == 0 { // FIXME: could fuck us continue; } 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); 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, ret); self.ci.scope.vars.drain(base..).for_each(|v| v.remove(&mut self.ci.nodes)); let sym = SymKey::FuncInst(*func, args); let ct = |ins: &mut crate::TypeIns| { let fuc = ins.funcs[*func]; debug_assert!(fuc.comp_state.iter().all(|&s| s == CompState::default())); ins.funcs .push(Func { base: Some(*func), sig: Some(Sig { args, ret }), ..fuc }) .into() }; let ty::Kind::Func(f) = self.tys.syms.get_or_insert(sym, &mut self.tys.ins, ct).expand() else { unreachable!() }; *func = f; Sig { args, ret } }) } fn assign_pattern(&mut self, pat: &Expr, right: Value) { match *pat { Expr::Ident { id, .. } => { 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 expr_ctx(&mut self, expr: &Expr, ctx: Ctx) -> Option { let mut n = self.raw_expr_ctx(expr, ctx)?; self.strip_var(&mut n); self.strip_ptr(&mut n); Some(n) } 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 strip_var(&mut self, n: &mut Value) { 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(); } } fn jump_to(&mut self, pos: Pos, id: usize) -> Option { let Some(mut loob) = self.ci.loops.last_mut() else { self.error(pos, "break outside a loop"); return None; }; if loob.ctrl[id].is_live() { loob.ctrl[id].set( self.ci.nodes.new_node( ty::Id::VOID, Kind::Region, [self.ci.ctrl.get(), loob.ctrl[id].get()], self.tys, ), &mut self.ci.nodes, ); let mut scope = mem::take(&mut loob.ctrl_scope[id]); let ctrl = mem::take(&mut loob.ctrl[id]); self.ci.nodes.merge_scopes( &mut self.ci.loops, &ctrl, &mut scope, &mut self.ci.scope, self.tys, ); loob = self.ci.loops.last_mut().unwrap(); loob.ctrl_scope[id] = scope; loob.ctrl[id] = ctrl; self.ci.ctrl.set(NEVER, &mut self.ci.nodes); } else { let term = StrongRef::new(NEVER, &mut self.ci.nodes); loob.ctrl[id] = mem::replace(&mut self.ci.ctrl, term); loob.ctrl_scope[id] = self.ci.scope.dup(&mut self.ci.nodes); loob.ctrl_scope[id] .vars .drain(loob.scope.vars.len()..) .for_each(|v| v.remove(&mut self.ci.nodes)); loob.ctrl_scope[id] .aclasses .drain(loob.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; let sig = func.sig.expect("to emmit only concrete functions"); let ast = &self.files[file.index()]; let expr = func.expr.get(ast); self.pool.push_ci(file, 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, &mut 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, expr) } fn ty_in(&mut self, file: Module, expr: &Expr) -> ty::Id { self.parse_ty(file, expr, None) } 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 was_ptr = val.ptr; 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); match oty.loc(self.tys) { Loc::Reg => { self.strip_ptr(val); // 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 => { self.strip_ptr(val); 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; } } if !was_ptr { self.strip_ptr(val); } } 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::NEVER, "{} {}", self.ty_display(src.ty), self.ty_display(upcasted) ); debug_assert!( upcasted.is_integer() || src.ty == ty::Id::NEVER, "{} {}", 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) { let mut buf = self.errors.borrow_mut(); write!(buf, "{}", self.file().report(pos, msg)).unwrap(); } #[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.index()] } fn eval_const(&mut self, file: Module, expr: &Expr, ret: ty::Id) -> u64 { self.ct.activate(); let scope = self .ci .scope .vars .iter() .filter(|v| v.ty == ty::Id::TYPE) .map(|v| match self.ci.nodes[v.value.get()].kind { Kind::CInt { value } => (value, v.id), _ => unreachable!(), }) .collect::>(); self.pool.push_ci(file, Some(ret), self.tys.tasks.len(), &mut self.ci); self.ci.scope.vars = scope .into_iter() .map(|(v, id)| { Variable::new( id, ty::Id::TYPE, false, self.ci.nodes.new_const(ty::Id::TYPE, v), &mut self.ci.nodes, ) }) .collect(); 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 { 1 }; 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] && idx < self.tys.tasks.len() { func.comp_state[state_slot] = CompState::Queued(self.tys.tasks.len()); let task = self.tys.tasks[idx].take(); self.tys.tasks.push(task); } } fn eval_global(&mut self, file: Module, name: Ident, expr: &Expr) -> ty::Id { self.ct.activate(); let gid = self.tys.ins.globals.push(Global { file, name, ..Default::default() }); self.pool.push_ci(file, 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.index()].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.index()].report(pos, msg)).unwrap(); ty::Id::NEVER } fn find_local_ty(&mut self, ident: Ident) -> Option { self.ci.scope.vars.iter().rfind(|v| (v.id == ident && v.ty == ty::Id::TYPE)).map(|v| { match self.ci.nodes[v.value.get()].kind { Kind::CInt { value } => ty::Id::from(value as u64), k => unreachable!("{k:?}"), } }) } fn find_type( &mut self, pos: Pos, from_file: Module, file: Module, id: Result, ) -> ty::Id { let ty = if let Ok(id) = id && let Some(ty) = self.find_local_ty(id) { ty } else if let Ok(id) = id && let Some(&ty) = self.tys.syms.get(SymKey::Decl(file, id), &self.tys.ins) { self.on_reuse(ty); ty } else { let f = &self.files[file.index()]; let Some((expr @ Expr::BinOp { left, right, .. }, name)) = f.find_decl(id) else { return match id { Ok(_) => { debug_assert_eq!(from_file, file); self.error_low(file, pos, "somehow this was not found") } Err("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 ), ), Err(name) => self.error_low( from_file, pos, format_args!("undefined indentifier: {name}"), ), }; }; let ty = if let Some(&ty) = self.tys.syms.get(SymKey::Decl(file, name), &self.tys.ins) { ty } else { let (is_ct, ty) = left .find_pattern_path(name, right, |right, is_ct| { ( is_ct, if is_ct && !matches!(right, Expr::Closure { .. }) { self.tys .ins .consts .push(Const { ast: ExprRef::new(expr), name, file }) .into() } else { self.parse_ty(file, right, Some(name)) }, ) }) .unwrap_or_else(|_| unreachable!()); if let ty::Kind::Func(f) = ty.expand() && is_ct { self.tys.ins.funcs[f].is_inline = true; } self.tys.syms.insert(SymKey::Decl(file, name), ty, &self.tys.ins); ty }; if let Err(proper_case) = self.tys.case(ty)(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, file: Module, expr: &Expr, name: Option) -> ty::Id { match *expr { Expr::Mod { id, .. } => id.into(), Expr::UnOp { op: TokenKind::Xor, val, .. } => { let base = self.parse_ty(file, val, None); self.tys.make_ptr(base) } Expr::UnOp { op: TokenKind::Que, val, .. } => { let base = self.parse_ty(file, val, None); 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, file, file, Ok(id)), Expr::Field { target, pos, name } if let ty::Kind::Module(inside) = self.parse_ty(file, target, None).expand() => { self.find_type(pos, file, inside, Err(name)) } Expr::Directive { name: "TypeOf", args: [expr], .. } => self.infer_type(expr), Expr::Slice { size: None, item, .. } => { let ty = self.parse_ty(file, item, None); self.tys.make_array(ty, ArrayLen::MAX) } Expr::Slice { size: Some(&Expr::Number { value, .. }), item, .. } => { let ty = self.parse_ty(file, item, None); self.tys.make_array(ty, value as _) } Expr::Slice { size, item, .. } => { let ty = self.parse_ty(file, item, None); let len = size .map_or(ArrayLen::MAX, |expr| self.eval_const(file, expr, ty::Id::U32) as _); self.tys.make_array(ty, len) } Expr::Struct { pos, fields, packed, captured, .. } => { 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::Struct(file, pos, captured); if let Some(&ty) = self.tys.syms.get(sym, &self.tys.ins) { return ty; } let prev_tmp = self.tys.tmp.struct_fields.len(); for field in fields.iter().filter_map(CommentOr::or) { let ty = self.parse_ty(file, &field.ty, None); let field = StructField { name: self.tys.names.intern(field.name), ty }; self.tys.tmp.struct_fields.push(field); } let ty = self .tys .ins .structs .push(Struct { file, pos, name: name.unwrap_or_default(), field_start: self.tys.ins.struct_fields.len() as _, explicit_alignment: packed.then_some(1), ..Default::default() }) .into(); self.tys.ins.struct_fields.extend(self.tys.tmp.struct_fields.drain(prev_tmp..)); self.tys.syms.insert(sym, ty, &self.tys.ins); ty } Expr::Enum { pos, variants, .. } => { let sym = SymKey::Enum(file, pos); if let Some(&ty) = self.tys.syms.get(sym, &self.tys.ins) { return ty; } let prev_tmp = self.tys.tmp.enum_fields.len(); for field in variants.iter().filter_map(CommentOr::or) { let field = EnumField { name: self.tys.names.intern(field.name) }; self.tys.tmp.enum_fields.push(field); } let ty = self .tys .ins .enums .push(Enum { file, pos, name: name.unwrap_or_default(), field_start: self.tys.ins.enum_fields.len() as _, }) .into(); self.tys.ins.enum_fields.extend(self.tys.tmp.enum_fields.drain(prev_tmp..)); self.tys.syms.insert(sym, ty, &self.tys.ins); ty } Expr::Closure { pos, args, ret, .. } if let Some(name) = name => { let func = Func { file, name, sig: 'b: { let arg_base = self.tys.tmp.args.len(); for arg in args { let sym = parser::find_symbol(&self.files[file.index()].symbols, arg.id); if sym.flags & idfl::COMPTIME != 0 { self.tys.tmp.args.truncate(arg_base); break 'b None; } let ty = self.parse_ty(file, &arg.ty, None); self.tys.tmp.args.push(ty); } let Some(args) = self.tys.pack_args(arg_base) else { return self.error_low(file, pos, "function has too many argumnets"); }; let ret = self.parse_ty(file, ret, None); Some(Sig { args, ret }) }, expr: ExprRef::new(expr), returns_type: matches!(ret, &Expr::Ident { id, .. } if ty::Builtin::try_from(id) == Ok(ty::Builtin::TYPE)), ..Default::default() }; self.tys.ins.funcs.push(func).into() } _ if let Some(name) = name => self.eval_global(file, name, expr), _ => ty::Id::from(self.eval_const(file, expr, ty::Id::TYPE)), } } } #[cfg(test)] mod tests { use { super::{hbvm::HbvmBackend, CodegenCtx}, crate::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}"); super::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; enums; nullable_types; struct_operators; global_variables; constants; directives; c_strings; struct_patterns; arrays; inline; idk; generic_functions; die; // Incomplete Examples; //comptime_pointers; generic_types; fb_driver; // Purely Testing Examples; 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; 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; } }