From 48a0c8d0b980a86073d2067580837d566ded1700 Mon Sep 17 00:00:00 2001 From: Jakub Doka Date: Sun, 15 Dec 2024 17:17:41 +0100 Subject: [PATCH] POC for removeing needless stack offset computes when only value is used TBD: there are far more cases where this will apply Signed-off-by: Jakub Doka --- lang/src/backend/hbvm.rs | 276 +-------------- lang/src/backend/hbvm/regalloc.rs | 331 ++++++++++++++---- lang/src/son.rs | 31 +- lang/tests/son_tests_directives.txt | 13 +- lang/tests/son_tests_inlining_issues.txt | 29 +- lang/tests/son_tests_memory_swap.txt | 4 +- .../son_tests_method_receiver_by_value.txt | 28 +- lang/tests/son_tests_nullable_types.txt | 4 +- lang/tests/son_tests_structs.txt | 15 +- 9 files changed, 314 insertions(+), 417 deletions(-) diff --git a/lang/src/backend/hbvm.rs b/lang/src/backend/hbvm.rs index 5c989f58..616b4560 100644 --- a/lang/src/backend/hbvm.rs +++ b/lang/src/backend/hbvm.rs @@ -3,8 +3,8 @@ use { crate::{ lexer::TokenKind, parser, - son::{Kind, Nid, Node, Nodes, MEM, VOID}, - ty::{self, Arg, Loc, Module, Offset, Sig, Size, Types}, + son::{Kind, Nid, Nodes, MEM}, + ty::{self, Loc, Module, Offset, Size, Types}, utils::{EntSlice, EntVec}, }, alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec}, @@ -417,7 +417,7 @@ impl Nodes { Kind::BinOp { op: TokenKind::Add | TokenKind::Sub } => { self.is_locked(node.inputs[1]) || (self.is_const(node.inputs[2]) - && node.outputs.iter().all(|&n| self[n].uses_direct_offset_of(nid, tys))) + && node.outputs.iter().all(|&n| self.uses_direct_offset_of(n, nid, tys))) } Kind::BinOp { op } => { op.cond_op(self[node.inputs[1]].ty).is_some() @@ -425,7 +425,7 @@ impl Nodes { } Kind::Stck if tys.size_of(node.ty) == 0 => true, Kind::Stck | Kind::Arg => node.outputs.iter().all(|&n| { - self[n].uses_direct_offset_of(nid, tys) + self.uses_direct_offset_of(n, nid, tys) || (matches!(self[n].kind, Kind::BinOp { op: TokenKind::Add }) && self.is_never_used(n, tys)) }), @@ -433,18 +433,17 @@ impl Nodes { _ => false, } } -} -struct InstrCtx<'a> { - nid: Nid, - sig: Sig, - is_last_block: bool, - is_next_block: bool, - retl: Option, - allocs: &'a [u8], - nodes: &'a Nodes, - tys: &'a Types, - files: &'a EntSlice, + fn uses_direct_offset_of(&self, user: Nid, target: Nid, tys: &Types) -> bool { + let node = &self[user]; + ((node.kind == Kind::Stre && node.inputs[2] == target) + || (node.kind == Kind::Load && node.inputs[1] == target)) + && (node.ty.loc(tys) == Loc::Reg + // this means the struct is actually loaded into a register so no BMC needed + || (node.kind == Kind::Load + && !matches!(tys.parama(node.ty).0, Some(PLoc::Ref(..))) + && node.outputs.iter().all(|&o| self[o].kind.is_call()))) + } } impl HbvmBackend { @@ -477,253 +476,6 @@ impl HbvmBackend { } }); } - - fn emit_instr( - &mut self, - InstrCtx { - nid, - sig, - is_last_block, - is_next_block, - allocs, - nodes, - tys, - files, - retl, - }: InstrCtx, - ) { - let node = &nodes[nid]; - - match node.kind { - Kind::If => { - let &[_, cnd] = node.inputs.as_slice() else { unreachable!() }; - if let Some((op, swapped)) = nodes.cond_op(cnd) { - let &[lhs, rhs] = allocs else { unreachable!() }; - let &[_, lh, rh] = nodes[cnd].inputs.as_slice() else { unreachable!() }; - - self.extend(nodes[lh].ty, nodes[lh].ty.extend(), lhs, tys, files); - self.extend(nodes[rh].ty, nodes[rh].ty.extend(), rhs, tys, files); - - let rel = Reloc::new(self.code.len(), 3, 2); - self.jump_relocs.push((node.outputs[!swapped as usize], rel)); - self.emit(op(lhs, rhs, 0)); - } else { - debug_assert_eq!(nodes[node.outputs[0]].kind, Kind::Then); - self.extend(nodes[cnd].ty, nodes[cnd].ty.extend(), allocs[0], tys, files); - let rel = Reloc::new(self.code.len(), 3, 2); - self.jump_relocs.push((node.outputs[0], rel)); - self.emit(instrs::jne(allocs[0], reg::ZERO, 0)); - } - } - Kind::Loop | Kind::Region => { - if !is_next_block { - let rel = Reloc::new(self.code.len(), 1, 4); - self.jump_relocs.push((nid, rel)); - self.emit(instrs::jmp(0)); - } - } - Kind::Return { .. } => { - match retl { - Some(PLoc::Reg(r, size)) if sig.ret.loc(tys) == Loc::Stack => { - self.emit(instrs::ld(r, allocs[0], 0, size)) - } - None | Some(PLoc::Reg(..)) => {} - Some(PLoc::WideReg(r, size)) => self.emit(instrs::ld(r, allocs[0], 0, size)), - Some(PLoc::Ref(_, size)) => { - let [src, dst] = [allocs[0], allocs[1]]; - if let Ok(size) = u16::try_from(size) { - self.emit(instrs::bmc(src, dst, size)); - } else { - for _ in 0..size / u16::MAX as u32 { - self.emit(instrs::bmc(src, dst, u16::MAX)); - self.emit(instrs::addi64(src, src, u16::MAX as _)); - self.emit(instrs::addi64(dst, dst, u16::MAX as _)); - } - self.emit(instrs::bmc(src, dst, size as u16)); - self.emit(instrs::addi64(src, src, size.wrapping_neg() as _)); - self.emit(instrs::addi64(dst, dst, size.wrapping_neg() as _)); - } - } - } - - if !is_last_block { - let rel = Reloc::new(self.code.len(), 1, 4); - self.ret_relocs.push(rel); - self.emit(instrs::jmp(0)); - } - } - Kind::Die => { - self.emit(instrs::un()); - } - Kind::CInt { value: 0 } => self.emit(instrs::cp(allocs[0], reg::ZERO)), - Kind::CInt { value } if node.ty == ty::Id::F32 => { - self.emit(instrs::li32(allocs[0], (f64::from_bits(value as _) as f32).to_bits())); - } - Kind::CInt { value } => self.emit(match tys.size_of(node.ty) { - 1 => instrs::li8(allocs[0], value as _), - 2 => instrs::li16(allocs[0], value as _), - 4 => instrs::li32(allocs[0], value as _), - _ => instrs::li64(allocs[0], value as _), - }), - Kind::UnOp { op } => { - let op = op - .unop( - node.ty, - tys.inner_of(nodes[node.inputs[1]].ty).unwrap_or(nodes[node.inputs[1]].ty), - ) - .unwrap_or_else(|| { - panic!( - "TODO: unary operator not supported: {op} {} {}", - ty::Display::new(tys, files, node.ty), - ty::Display::new( - tys, - files, - tys.inner_of(nodes[node.inputs[1]].ty) - .unwrap_or(nodes[node.inputs[1]].ty) - ) - ) - }); - let &[dst, oper] = allocs else { unreachable!() }; - self.emit(op(dst, oper)); - } - Kind::BinOp { op } => { - let &[.., rh] = node.inputs.as_slice() else { unreachable!() }; - - if let Kind::CInt { value } = nodes[rh].kind - && nodes.is_locked(rh) - && let Some(op) = op.imm_binop(node.ty) - { - let &[dst, lhs] = allocs else { unreachable!() }; - self.emit(op(dst, lhs, value as _)); - } else if let Some(against) = op.cmp_against() { - let op_ty = nodes[rh].ty; - let &[dst, lhs, rhs] = allocs else { unreachable!() }; - if let Some(op) = op.float_cmp(op_ty) { - self.emit(op(dst, lhs, rhs)); - } else if op_ty.is_float() && matches!(op, TokenKind::Le | TokenKind::Ge) { - let op = match op { - TokenKind::Le => TokenKind::Gt, - TokenKind::Ge => TokenKind::Lt, - _ => unreachable!(), - }; - let op_fn = op.float_cmp(op_ty).unwrap(); - self.emit(op_fn(dst, lhs, rhs)); - self.emit(instrs::not(dst, dst)); - } else { - let op_fn = if op_ty.is_signed() { instrs::cmps } else { instrs::cmpu }; - self.emit(op_fn(dst, lhs, rhs)); - self.emit(instrs::cmpui(dst, dst, against)); - if matches!(op, TokenKind::Eq | TokenKind::Lt | TokenKind::Gt) { - self.emit(instrs::not(dst, dst)); - } - } - } else if let Some(op) = op.binop(node.ty) { - let &[dst, lhs, rhs] = allocs else { unreachable!() }; - self.emit(op(dst, lhs, rhs)); - } else { - todo!("unhandled operator: {op}"); - } - } - Kind::Call { args, func } => { - let (ret, mut parama) = tys.parama(node.ty); - let has_ret = ret.is_some() as usize; - let mut args = args.args(); - let mut allocs = allocs[has_ret..].iter(); - while let Some(arg) = args.next(tys) { - let Arg::Value(ty) = arg else { continue }; - let Some(loc) = parama.next(ty, tys) else { continue }; - - let &arg = allocs.next().unwrap(); - let (rg, size) = match loc { - PLoc::Reg(rg, size) if ty.loc(tys) == Loc::Stack => (rg, size), - PLoc::WideReg(rg, size) => (rg, size), - PLoc::Ref(..) | PLoc::Reg(..) => continue, - }; - if size > 8 { - allocs.next().unwrap(); - } - self.emit(instrs::ld(rg, arg, 0, size)); - } - - debug_assert!(!matches!(ret, Some(PLoc::Ref(..))) || allocs.next().is_some()); - - if func == ty::Func::ECA { - self.emit(instrs::eca()); - } else { - self.relocs.push(TypedReloc { - target: func.into(), - reloc: Reloc::new(self.code.len(), 3, 4), - }); - self.emit(instrs::jal(reg::RET_ADDR, reg::ZERO, 0)); - } - - if node.ty.loc(tys) == Loc::Stack - && let Some(PLoc::Reg(r, size) | PLoc::WideReg(r, size)) = ret - { - self.emit(instrs::st(r, *allocs.last().unwrap(), 0, size)); - } - } - Kind::Global { global } => { - let reloc = Reloc::new(self.code.len(), 3, 4); - self.relocs.push(TypedReloc { target: global.into(), reloc }); - self.emit(instrs::lra(allocs[0], 0, 0)); - } - Kind::Stck => { - let base = reg::STACK_PTR; - let offset = self.offsets[nid as usize]; - self.emit(instrs::addi64(allocs[0], base, offset as _)); - } - Kind::Load => { - let (region, offset) = nodes.strip_offset(node.inputs[1], node.ty, tys); - let size = tys.size_of(node.ty); - if node.ty.loc(tys) != Loc::Stack { - let (base, offset) = match nodes[region].kind { - Kind::Stck => (reg::STACK_PTR, self.offsets[region as usize] + offset), - _ => (allocs[1], offset), - }; - self.emit(instrs::ld(allocs[0], base, offset as _, size as _)); - } - } - Kind::Stre if node.inputs[1] == VOID => {} - Kind::Stre => { - let (region, offset) = nodes.strip_offset(node.inputs[2], node.ty, tys); - let size = u16::try_from(tys.size_of(node.ty)).expect("TODO"); - let (base, offset, src) = match nodes[region].kind { - Kind::Stck if node.ty.loc(tys) == Loc::Reg => { - (reg::STACK_PTR, self.offsets[region as usize] + offset, allocs[0]) - } - _ => ((allocs[0]), offset, allocs[1]), - }; - - match node.ty.loc(tys) { - Loc::Reg => self.emit(instrs::st(src, base, offset as _, size)), - Loc::Stack => { - debug_assert_eq!(offset, 0); - self.emit(instrs::bmc(src, base, size)) - } - } - } - e @ (Kind::Start - | Kind::Assert { .. } - | Kind::Entry - | Kind::Mem - | Kind::End - | Kind::Loops - | Kind::Then - | Kind::Else - | Kind::Phi - | Kind::Arg - | Kind::Join) => unreachable!("{e:?}"), - } - } -} - -impl Node { - fn uses_direct_offset_of(&self, nid: Nid, tys: &Types) -> bool { - ((self.kind == Kind::Stre && self.inputs[2] == nid) - || (self.kind == Kind::Load && self.inputs[1] == nid)) - && self.ty.loc(tys) == Loc::Reg - } } type CondRet = Option<(fn(u8, u8, i16) -> EncodedInstr, bool)>; diff --git a/lang/src/backend/hbvm/regalloc.rs b/lang/src/backend/hbvm/regalloc.rs index 55019be3..009e2b4c 100644 --- a/lang/src/backend/hbvm/regalloc.rs +++ b/lang/src/backend/hbvm/regalloc.rs @@ -2,15 +2,16 @@ use { crate::{ backend::hbvm::{ reg::{self, Reg}, - HbvmBackend, Nid, Nodes, PLoc, + HbvmBackend, Nid, Nodes, PLoc, Reloc, TypedReloc, }, + lexer::TokenKind, parser, quad_sort, son::{Kind, ARG_START, MEM, VOID}, - ty::{self, Arg, Loc, Module, Sig, Types}, + ty::{self, Arg, Loc, Module, Offset, Sig, Types}, utils::{BitSet, EntSlice}, }, alloc::{borrow::ToOwned, vec::Vec}, - core::{assert_matches::debug_assert_matches, mem, ops::Range}, + core::{assert_matches::debug_assert_matches, mem, ops::Range, usize}, hbbytecode::{self as instrs}, }; @@ -31,7 +32,7 @@ impl HbvmBackend { let mut res = mem::take(&mut self.ralloc); - Regalloc::run(nodes, &mut res); + Regalloc::run(nodes, tys, &mut res); '_open_function: { self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, 0)); @@ -106,8 +107,7 @@ impl HbvmBackend { let node = &nodes[nid]; alloc_buf.clear(); - let atr = |allc: Nid| { - let allc = strip_load(allc); + let assert_alloc_use = |allc: Nid| { debug_assert!( nodes.is_unlocked(allc), "{:?} {}", @@ -125,18 +125,46 @@ impl HbvmBackend { nodes[nid], nodes[allc] ); + }; + + let atr = |allc: Nid| { + let allc = strip_load(allc); + assert_alloc_use(allc); res.node_to_reg[allc as usize] }; - let mut is_next_block = false; + let offset_atr = |allc: Nid, offsets: &[Offset]| { + let allc = strip_load(allc); + if nodes.is_locked(allc) && nodes[allc].kind == Kind::Stck { + return (reg::STACK_PTR, offsets[allc as usize] as u64); + } + + assert_alloc_use(allc); + (res.node_to_reg[allc as usize], 0) + }; + match node.kind { + Kind::Mem => self.emit(instrs::cp(atr(MEM), reg::RET)), + Kind::Arg => {} Kind::If => { let &[_, cnd] = node.inputs.as_slice() else { unreachable!() }; - if nodes.cond_op(cnd).is_some() { + if let Some((op, swapped)) = nodes.cond_op(cnd) { let &[_, lh, rh] = nodes[cnd].inputs.as_slice() else { unreachable!() }; - alloc_buf.extend([atr(lh), atr(rh)]); + let [lhs, rhs] = [atr(lh), atr(rh)]; + + self.extend(nodes[lh].ty, nodes[lh].ty.extend(), lhs, tys, files); + self.extend(nodes[rh].ty, nodes[rh].ty.extend(), rhs, tys, files); + + let rel = Reloc::new(self.code.len(), 3, 2); + self.jump_relocs.push((node.outputs[!swapped as usize], rel)); + self.emit(op(lhs, rhs, 0)); } else { - alloc_buf.push(atr(cnd)); + let cd = atr(cnd); + debug_assert_eq!(nodes[node.outputs[0]].kind, Kind::Then); + self.extend(nodes[cnd].ty, nodes[cnd].ty.extend(), cd, tys, files); + let rel = Reloc::new(self.code.len(), 3, 2); + self.jump_relocs.push((node.outputs[0], rel)); + self.emit(instrs::jne(cd, reg::ZERO, 0)); } } Kind::Loop | Kind::Region => { @@ -217,39 +245,132 @@ impl HbvmBackend { } } - is_next_block = res.backrefs[nid as usize] as usize == i + 1; + if res.backrefs[nid as usize] as usize != i + 1 { + let rel = Reloc::new(self.code.len(), 1, 4); + self.jump_relocs.push((nid, rel)); + self.emit(instrs::jmp(0)); + } } Kind::Return { .. } => { let &[_, ret, ..] = node.inputs.as_slice() else { unreachable!() }; match retl { - Some(PLoc::Reg(r, _)) if sig.ret.loc(tys) == Loc::Reg => { + None => {} + Some(PLoc::Reg(r, size)) if sig.ret.loc(tys) == Loc::Stack => { + // TODO: handle the stack load + self.emit(instrs::ld(r, atr(ret), 0, size)) + } + Some(PLoc::WideReg(r, size)) => { + // TODO: handle the stack load + self.emit(instrs::ld(r, atr(ret), 0, size)) + } + Some(PLoc::Reg(r, _)) => { alloc_buf.push(atr(ret)); self.emit(instrs::cp(r, atr(ret))); } - Some(PLoc::Ref(..)) => alloc_buf.extend([atr(ret), atr(MEM)]), - Some(_) => alloc_buf.push(atr(ret)), - None => {} + Some(PLoc::Ref(_, size)) => { + let [src, dst] = [atr(ret), atr(MEM)]; + if let Ok(size) = u16::try_from(size) { + self.emit(instrs::bmc(src, dst, size)); + } else { + for _ in 0..size / u16::MAX as u32 { + self.emit(instrs::bmc(src, dst, u16::MAX)); + self.emit(instrs::addi64(src, src, u16::MAX as _)); + self.emit(instrs::addi64(dst, dst, u16::MAX as _)); + } + self.emit(instrs::bmc(src, dst, size as u16)); + self.emit(instrs::addi64(src, src, size.wrapping_neg() as _)); + self.emit(instrs::addi64(dst, dst, size.wrapping_neg() as _)); + } + } + } + + if i != res.blocks.len() - 1 { + let rel = Reloc::new(self.code.len(), 1, 4); + self.ret_relocs.push(rel); + self.emit(instrs::jmp(0)); } } - Kind::Die => {} - Kind::CInt { .. } => alloc_buf.push(atr(nid)), - Kind::UnOp { .. } => alloc_buf.extend([atr(nid), atr(node.inputs[1])]), + Kind::Die => { + self.emit(instrs::un()); + } + Kind::CInt { value: 0 } => self.emit(instrs::cp(atr(nid), reg::ZERO)), + Kind::CInt { value } if node.ty == ty::Id::F32 => { + self.emit(instrs::li32( + atr(nid), + (f64::from_bits(value as _) as f32).to_bits(), + )); + } + Kind::CInt { value } => self.emit(match tys.size_of(node.ty) { + 1 => instrs::li8(atr(nid), value as _), + 2 => instrs::li16(atr(nid), value as _), + 4 => instrs::li32(atr(nid), value as _), + _ => instrs::li64(atr(nid), value as _), + }), + Kind::UnOp { op } => { + let op = op + .unop( + node.ty, + tys.inner_of(nodes[node.inputs[1]].ty) + .unwrap_or(nodes[node.inputs[1]].ty), + ) + .unwrap_or_else(|| { + panic!( + "TODO: unary operator not supported: {op} {} {}", + ty::Display::new(tys, files, node.ty), + ty::Display::new( + tys, + files, + tys.inner_of(nodes[node.inputs[1]].ty) + .unwrap_or(nodes[node.inputs[1]].ty) + ) + ) + }); + self.emit(op(atr(nid), atr(node.inputs[1]))); + } Kind::BinOp { op } => { let &[.., lhs, rhs] = node.inputs.as_slice() else { unreachable!() }; - if let Kind::CInt { .. } = nodes[rhs].kind + if let Kind::CInt { value } = nodes[rhs].kind && nodes.is_locked(rhs) - && op.imm_binop(node.ty).is_some() + && let Some(op) = op.imm_binop(node.ty) { - alloc_buf.extend([atr(nid), atr(lhs)]); + self.emit(op(atr(nid), atr(lhs), value as _)); + } else if let Some(against) = op.cmp_against() { + let op_ty = nodes[rhs].ty; + let [dst, lhs, rhs] = [atr(nid), atr(lhs), atr(rhs)]; + if let Some(op) = op.float_cmp(op_ty) { + self.emit(op(dst, lhs, rhs)); + } else if op_ty.is_float() + && matches!(op, TokenKind::Le | TokenKind::Ge) + { + let op = match op { + TokenKind::Le => TokenKind::Gt, + TokenKind::Ge => TokenKind::Lt, + _ => unreachable!(), + }; + let op_fn = op.float_cmp(op_ty).unwrap(); + self.emit(op_fn(dst, lhs, rhs)); + self.emit(instrs::not(dst, dst)); + } else { + let op_fn = + if op_ty.is_signed() { instrs::cmps } else { instrs::cmpu }; + self.emit(op_fn(dst, lhs, rhs)); + self.emit(instrs::cmpui(dst, dst, against)); + if matches!(op, TokenKind::Eq | TokenKind::Lt | TokenKind::Gt) { + self.emit(instrs::not(dst, dst)); + } + } + } else if let Some(op) = op.binop(node.ty) { + let [dst, lhs, rhs] = [atr(nid), atr(lhs), atr(rhs)]; + self.emit(op(dst, lhs, rhs)); } else { - alloc_buf.extend([atr(nid), atr(lhs), atr(rhs)]); + todo!("unhandled operator: {op}"); } } - Kind::Call { args, .. } => { + Kind::Call { args, func } => { let (ret, mut parama) = tys.parama(node.ty); - if ret.is_some() { - alloc_buf.push(atr(nid)); + if let Some(PLoc::Ref(r, ..)) = ret { + self.emit(instrs::cp(r, atr(*node.inputs.last().unwrap()))) } let mut args = args.args(); let mut allocs = node.inputs[1..].iter(); @@ -257,68 +378,90 @@ impl HbvmBackend { let Arg::Value(ty) = arg else { continue }; let Some(loc) = parama.next(ty, tys) else { continue }; - let arg = *allocs.next().unwrap(); - alloc_buf.push(atr(arg)); - match loc { - PLoc::Reg(..) if ty.loc(tys) == Loc::Stack => {} - PLoc::WideReg(..) => alloc_buf.push(0), - PLoc::Reg(r, ..) | PLoc::Ref(r, ..) => { - self.emit(instrs::cp(r, atr(arg))) + let &arg = allocs.next().unwrap(); + let (rg, size) = match loc { + PLoc::Reg(rg, size) if ty.loc(tys) == Loc::Stack => (rg, size), + PLoc::WideReg(rg, size) => (rg, size), + PLoc::Ref(r, ..) | PLoc::Reg(r, ..) => { + self.emit(instrs::cp(r, atr(arg))); + continue; } }; + + let (src, off) = offset_atr(arg, &self.offsets); + self.emit(instrs::ld(rg, src, off, size)); } - if node.ty.loc(tys) == Loc::Stack { - alloc_buf.push(atr(*node.inputs.last().unwrap())); + if func == ty::Func::ECA { + self.emit(instrs::eca()); + } else { + self.relocs.push(TypedReloc { + target: func.into(), + reloc: Reloc::new(self.code.len(), 3, 4), + }); + self.emit(instrs::jal(reg::RET_ADDR, reg::ZERO, 0)); } - if let Some(PLoc::Ref(r, ..)) = ret { - self.emit(instrs::cp(r, *alloc_buf.last().unwrap())) + if node.ty.loc(tys) == Loc::Stack + && let Some(PLoc::Reg(r, size) | PLoc::WideReg(r, size)) = ret + { + self.emit(instrs::st(r, atr(*node.inputs.last().unwrap()), 0, size)); } } - Kind::Stck | Kind::Global { .. } => alloc_buf.push(atr(nid)), + Kind::Global { global } => { + let reloc = Reloc::new(self.code.len(), 3, 4); + self.relocs.push(TypedReloc { target: global.into(), reloc }); + self.emit(instrs::lra(atr(nid), 0, 0)); + } + Kind::Stck => { + let base = reg::STACK_PTR; + let offset = self.offsets[nid as usize]; + self.emit(instrs::addi64(atr(nid), base, offset as _)); + } Kind::Load => { - let (region, _) = nodes.strip_offset(node.inputs[1], node.ty, tys); + let (region, offset) = nodes.strip_offset(node.inputs[1], node.ty, tys); + let size = tys.size_of(node.ty); if node.ty.loc(tys) != Loc::Stack { - alloc_buf.push(atr(nid)); - match nodes[region].kind { - Kind::Stck => {} - _ => alloc_buf.push(atr(region)), - } + let (base, offset) = match nodes[region].kind { + Kind::Stck => { + (reg::STACK_PTR, self.offsets[region as usize] + offset) + } + _ => (atr(region), offset), + }; + self.emit(instrs::ld(atr(nid), base, offset as _, size as _)); } } - Kind::Stre if node.inputs[1] == VOID => {} Kind::Stre => { - let (region, _) = nodes.strip_offset(node.inputs[2], node.ty, tys); - match nodes[region].kind { - Kind::Stck if node.ty.loc(tys) == Loc::Reg => { - alloc_buf.push(atr(node.inputs[1])) + debug_assert_ne!(node.inputs[1], VOID); + let (region, offset) = nodes.strip_offset(node.inputs[2], node.ty, tys); + let size = u16::try_from(tys.size_of(node.ty)).expect("TODO"); + let (base, offset, src) = match nodes[region].kind { + Kind::Stck if node.ty.loc(tys) == Loc::Reg => ( + reg::STACK_PTR, + self.offsets[region as usize] + offset, + atr(node.inputs[1]), + ), + _ => (atr(region), offset, atr(node.inputs[1])), + }; + + match node.ty.loc(tys) { + Loc::Reg => self.emit(instrs::st(src, base, offset as _, size)), + Loc::Stack => { + debug_assert_eq!(offset, 0); + self.emit(instrs::bmc(src, base, size)) } - _ => alloc_buf.extend([atr(region), atr(node.inputs[1])]), } } - Kind::Mem => { - self.emit(instrs::cp(atr(MEM), reg::RET)); - continue; - } - Kind::Arg => { - continue; - } - _ => {} + e @ (Kind::Start + | Kind::Assert { .. } + | Kind::Entry + | Kind::End + | Kind::Loops + | Kind::Then + | Kind::Else + | Kind::Phi + | Kind::Join) => unreachable!("{e:?}"), } - - self.emit_instr(super::InstrCtx { - nid, - sig, - is_next_block, - is_last_block: i == res.blocks.len() - 1, - retl, - allocs: &alloc_buf, - nodes, - tys, - files, - }); - if let Kind::Call { .. } = node.kind { let (ret, ..) = tys.parama(node.ty); @@ -537,6 +680,37 @@ impl Nodes { self.len() } + pub fn is_data_dep(&self, val: Nid, user: Nid, #[expect(unused)] types: &Types) -> 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::Call { .. } => { + // self[val].kind != Kind::Load + // || matches!(types.parama(self[val].ty).0, Some(PLoc::Ref(..))) + //} + Kind::Load => self[user].inputs[2] != val, + _ => self[user].inputs[0] != val || self[user].inputs[1..].contains(&val), + } + } + fn use_block_of(&self, inst: Nid, uinst: Nid) -> Nid { let mut block = self.use_block(inst, uinst, None); while !self[block].kind.starts_basic_block() { @@ -568,7 +742,11 @@ impl Nodes { nid } - fn uses_of(&self, nid: Nid) -> impl Iterator + use<'_> { + fn uses_of<'a>( + &'a self, + nid: Nid, + types: &'a Types, + ) -> impl Iterator + use<'a> { if self[nid].kind.is_cfg() && !matches!(self[nid].kind, Kind::Call { .. }) { return None.into_iter().flatten(); } @@ -577,10 +755,10 @@ impl Nodes { self[nid] .outputs .iter() - .filter(move |&&n| self.is_data_dep(nid, n)) + .filter(move |&&n| self.is_data_dep(nid, n, types)) .map(move |n| self.this_or_delegates(nid, n)) .flat_map(|(p, ls)| ls.iter().map(move |l| (p, l))) - .filter(|&(o, &n)| self.is_data_dep(o, n)) + .filter(|&(o, &n)| self.is_data_dep(o, n, types)) .map(|(p, &n)| (self.use_block_of(p, n), n)) .inspect(|&(_, n)| debug_assert!(self.is_unlocked(n))), ) @@ -591,6 +769,7 @@ impl Nodes { struct Regalloc<'a> { nodes: &'a Nodes, + tys: &'a Types, res: &'a mut Res, } @@ -608,8 +787,8 @@ impl<'a> Regalloc<'a> { self.res.backrefs[nid as usize] } - fn run(ctx: &'a Nodes, res: &'a mut Res) { - Self { nodes: ctx, res }.run_low(); + fn run(ctx: &'a Nodes, tys: &'a Types, res: &'a mut Res) { + Self { nodes: ctx, tys, res }.run_low(); } fn run_low(&mut self) { @@ -657,7 +836,7 @@ impl<'a> Regalloc<'a> { fn collect_bundle(&mut self, inst: Nid, into: &mut Bundle) { let dom = self.nodes.idom_of(inst); self.res.dfs_seem.clear(self.nodes.len()); - for (cursor, uinst) in self.nodes.uses_of(inst) { + for (cursor, uinst) in self.nodes.uses_of(inst, self.tys) { if !self.res.dfs_seem.set(uinst) { continue; } diff --git a/lang/src/son.rs b/lang/src/son.rs index 1b1d3e83..0a8727e4 100644 --- a/lang/src/son.rs +++ b/lang/src/son.rs @@ -1898,7 +1898,7 @@ impl Nodes { log::info!("{out}"); } - fn is_cfg(&self, o: Nid) -> bool { + pub fn is_cfg(&self, o: Nid) -> bool { self[o].kind.is_cfg() } @@ -2054,33 +2054,6 @@ impl Nodes { } } - pub 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), - } - } - pub 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)) @@ -2193,7 +2166,7 @@ pub enum Kind { } impl Kind { - fn is_call(&self) -> bool { + pub fn is_call(&self) -> bool { matches!(self, Kind::Call { .. }) } diff --git a/lang/tests/son_tests_directives.txt b/lang/tests/son_tests_directives.txt index b2e84f91..3581c8ca 100644 --- a/lang/tests/son_tests_directives.txt +++ b/lang/tests/son_tests_directives.txt @@ -1,22 +1,21 @@ main: ADDI64 r254, r254, -16d LI64 r13, 10d - ADDI64 r14, r254, 0d ST r13, r254, 0a, 8h LI64 r13, 20d ST r13, r254, 8a, 8h LI64 r13, 6d - LI64 r15, 5d - LI64 r16, 1d - CP r2, r16 - CP r5, r15 + LI64 r14, 5d + LI64 r15, 1d + CP r2, r15 + LD r3, r254, 0a, 16h + CP r5, r14 CP r6, r13 - LD r3, r14, 0a, 16h ECA CP r1, r0 ADDI64 r254, r254, 16d JALA r0, r31, 0a ev: Ecall -code size: 154 +code size: 143 ret: 0 status: Ok(()) diff --git a/lang/tests/son_tests_inlining_issues.txt b/lang/tests/son_tests_inlining_issues.txt index b6fa85df..f842f6c2 100644 --- a/lang/tests/son_tests_inlining_issues.txt +++ b/lang/tests/son_tests_inlining_issues.txt @@ -1,29 +1,26 @@ main: - ADDI64 r254, r254, -122d - ST r31, r254, 58a, 64h + ADDI64 r254, r254, -98d + ST r31, r254, 58a, 40h ADDI64 r32, r254, 33d ADDI64 r33, r254, 34d - ADDI64 r34, r254, 1d - ADDI64 r35, r254, 17d ST r32, r254, 34a, 8h - LI64 r36, 100d - ADDI64 r37, r254, 0d - LI8 r38, 1b + LI64 r34, 100d + LI8 r35, 1b ST r0, r254, 1a, 8h ST r0, r254, 17a, 8h - ST r36, r254, 42a, 8h - ST r38, r254, 0a, 1h + ST r34, r254, 42a, 8h + ST r35, r254, 0a, 1h ST r0, r254, 9a, 8h ST r0, r254, 25a, 8h - ST r36, r254, 50a, 8h + ST r34, r254, 50a, 8h ST r0, r254, 33a, 1h CP r2, r33 - LD r3, r35, 0a, 16h - LD r5, r34, 0a, 16h - LD r7, r37, 0a, 1h + LD r3, r254, 17a, 16h + LD r5, r254, 1a, 16h + LD r7, r254, 0a, 1h JAL r31, r0, :put_filled_rect - LD r31, r254, 58a, 64h - ADDI64 r254, r254, 122d + LD r31, r254, 58a, 40h + ADDI64 r254, r254, 98d JALA r0, r31, 0a put_filled_rect: ADDI64 r254, r254, -108d @@ -99,6 +96,6 @@ put_filled_rect: JMP :3 2: ADDI64 r254, r254, 108d JALA r0, r31, 0a -code size: 875 +code size: 842 ret: 0 status: Ok(()) diff --git a/lang/tests/son_tests_memory_swap.txt b/lang/tests/son_tests_memory_swap.txt index ceccb911..1a7fba7e 100644 --- a/lang/tests/son_tests_memory_swap.txt +++ b/lang/tests/son_tests_memory_swap.txt @@ -13,14 +13,14 @@ main: ADDI64 r254, r254, -120d ST r31, r254, 72a, 48h ADDI64 r32, r254, 48d - CP r2, r0 CP r1, r32 + CP r2, r0 JAL r31, r0, :decide ADDI64 r34, r254, 24d BMC r32, r34, 24h LI64 r35, 1d - CP r2, r35 CP r1, r34 + CP r2, r35 JAL r31, r0, :decide ADDI64 r36, r254, 0d BMC r32, r36, 24h diff --git a/lang/tests/son_tests_method_receiver_by_value.txt b/lang/tests/son_tests_method_receiver_by_value.txt index 01bbd7f3..479e3dd4 100644 --- a/lang/tests/son_tests_method_receiver_by_value.txt +++ b/lang/tests/son_tests_method_receiver_by_value.txt @@ -1,21 +1,19 @@ main: ADDI64 r254, r254, -72d ST r31, r254, 32a, 40h - ADDI64 r32, r254, 8d - LRA r33, r0, :"Goodbye, World!\0" - ADDI64 r34, r254, 24d - LRA r35, r0, :"Hello, World!\0" - ST r33, r254, 8a, 8h - ST r35, r254, 24a, 8h - LD r2, r34, 0a, 8h - LD r3, r32, 0a, 8h + LRA r32, r0, :"Goodbye, World!\0" + LRA r33, r0, :"Hello, World!\0" + ST r32, r254, 8a, 8h + ST r33, r254, 24a, 8h + LD r2, r254, 24a, 8h + LD r3, r254, 8a, 8h JAL r31, r0, :print - ADDI64 r32, r254, 0d - ADDI64 r34, r254, 16d - ST r33, r254, 0a, 8h - ST r35, r254, 16a, 8h - CP r2, r34 - CP r3, r32 + ADDI64 r34, r254, 0d + ADDI64 r35, r254, 16d + ST r32, r254, 0a, 8h + ST r33, r254, 16a, 8h + CP r2, r35 + CP r3, r34 JAL r31, r0, :print2 LD r31, r254, 32a, 40h ADDI64 r254, r254, 72d @@ -56,6 +54,6 @@ Hello, World! Goodbye, World! Hello, World! Goodbye, World! -code size: 457 +code size: 435 ret: 0 status: Ok(()) diff --git a/lang/tests/son_tests_nullable_types.txt b/lang/tests/son_tests_nullable_types.txt index dfd596a5..5dbf749b 100644 --- a/lang/tests/son_tests_nullable_types.txt +++ b/lang/tests/son_tests_nullable_types.txt @@ -59,8 +59,8 @@ main: CP r1, r32 JMP :3 10: LRA r36, r0, :"foo\0" - CP r4, r36 LD r2, r33, 0a, 16h + CP r4, r36 JAL r31, r0, :use_foo ADDI64 r33, r254, 0d JAL r31, r0, :no_foo @@ -78,8 +78,8 @@ main: CP r1, r32 JMP :3 13: ADDI64 r35, r254, 16d - CP r2, r34 CP r1, r35 + CP r2, r34 JAL r31, r0, :new_bar JAL r31, r0, :decide CP r34, r1 diff --git a/lang/tests/son_tests_structs.txt b/lang/tests/son_tests_structs.txt index f25ab466..6080b302 100644 --- a/lang/tests/son_tests_structs.txt +++ b/lang/tests/son_tests_structs.txt @@ -2,17 +2,16 @@ main: ADDI64 r254, r254, -64d ST r31, r254, 32a, 32h LI64 r32, 4d - ADDI64 r33, r254, 16d ST r32, r254, 16a, 8h LI64 r32, 3d ST r32, r254, 24a, 8h - ADDI64 r34, r254, 0d - LD r3, r33, 0a, 16h + ADDI64 r33, r254, 0d + LD r3, r254, 16a, 16h JAL r31, r0, :odher_pass - ST r1, r34, 0a, 16h - LD r33, r254, 8a, 8h - JNE r33, r32, :0 - CP r2, r34 + ST r1, r33, 0a, 16h + LD r34, r254, 8a, 8h + JNE r34, r32, :0 + CP r2, r33 JAL r31, r0, :pass CP r32, r1 CP r1, r32 @@ -34,6 +33,6 @@ pass: LD r13, r13, 0a, 8h CP r1, r13 JALA r0, r31, 0a -code size: 313 +code size: 302 ret: 4 status: Ok(())