use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap, HashSet};
use rucc_base::{Interner, Symbol};
use rucc_diag::{Diagnostic, Span};
use rucc_ir::{
DataList, Datum, Func, Global, Imm, Linkage as IrLinkage, Module, Reloc, TlsModel, Type,
};
use rucc_sema::{
Base, Const, DeclId, DeclKind, Definition, Eval, ExprId, ExprKind, InitEntry, InitList,
Linkage, StorageDuration, StrId, Tast,
};
use rucc_target::TargetInfo;
use rucc_types::{TypeId, TypeKind, Types};
use crate::abi::{self, Plan};
use crate::body;
use crate::repr;
#[derive(Debug)]
pub struct Context<'a> {
pub tast: &'a Tast,
pub types: &'a Types,
pub target: &'a TargetInfo,
pub names: &'a mut Interner,
}
#[derive(Debug)]
pub struct Lowered {
pub module: Module,
pub diagnostics: Vec<Diagnostic>,
}
#[must_use]
pub fn lower(name: &str, cx: Context<'_>) -> Lowered {
let Context { tast, types, target, names } = cx;
let module = Module::new(names.intern(name), target);
let mut unit = Unit {
tast,
types,
target,
names,
module,
diagnostics: Vec::new(),
strings: HashMap::new(),
statics: HashMap::new(),
done: HashSet::new(),
};
unit.run();
Lowered { module: unit.module, diagnostics: unit.diagnostics }
}
pub(crate) struct Unit<'a> {
pub(crate) tast: &'a Tast,
pub(crate) types: &'a Types,
pub(crate) target: &'a TargetInfo,
pub(crate) names: &'a mut Interner,
pub(crate) module: Module,
pub(crate) diagnostics: Vec<Diagnostic>,
strings: HashMap<StrId, Symbol>,
statics: HashMap<DeclId, Symbol>,
done: HashSet<DeclId>,
}
impl std::fmt::Debug for Unit<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Unit")
.field("module", &self.module.counts())
.field("diagnostics", &self.diagnostics.len())
.finish()
}
}
impl Unit<'_> {
fn run(&mut self) {
for index in 0..self.tast.top_level().len() {
let decl = self.tast.top_level()[index];
if !self.done.insert(decl) {
continue;
}
match self.tast[decl].kind {
DeclKind::Function => self.function(decl),
DeclKind::Object => self.object(decl),
}
}
}
fn object(&mut self, decl: DeclId) {
let tast = self.tast;
let node = &tast[decl];
let (ty, state, init) = (node.ty, node.state, node.init);
let (linkage, duration, alignment) = (node.linkage, node.duration, node.alignment);
let span = tast.decl_span(decl);
if duration == StorageDuration::Automatic {
return;
}
let symbol = self.symbol_of(decl);
let size = repr::size_of(self.types, self.target, ty);
let align = alignment.unwrap_or_else(|| repr::align_of(self.types, self.target, ty));
let mut global = Global::new(symbol, size, align);
global.linkage = match linkage {
Linkage::External => IrLinkage::External,
Linkage::Internal | Linkage::None => IrLinkage::Internal,
};
global.tls = (duration == StorageDuration::Thread).then_some(TlsModel::GlobalDynamic);
global.constant = repr::is_read_only(self.types, ty);
global.init = match state {
Definition::Declared => None,
Definition::Tentative => Some(self.zeros(size)),
Definition::Defined => Some(self.image(init, size, span)),
};
self.module.add_global(global);
}
fn function(&mut self, decl: DeclId) {
let tast = self.tast;
let node = &tast[decl];
let (ty, linkage, body) = (node.ty, node.linkage, node.body);
let span = tast.decl_span(decl);
let Some(name) = node.name else { return };
let Some(plan) = self.plan(ty, &[], span) else { return };
let mut func = Func::new(name, plan.signature.clone());
func.linkage = match linkage {
Linkage::Internal | Linkage::None => IrLinkage::Internal,
Linkage::External => IrLinkage::External,
};
if body.is_some() {
body::lower(self, decl, &mut func, &plan);
}
self.module.add_func(func);
}
pub(crate) fn plan(&mut self, ty: TypeId, actual: &[TypeId], span: Span) -> Option<Plan> {
let canonical = self.types.canonical(ty);
let canonical = match self.types.kind(canonical) {
TypeKind::Pointer(pointee) => self.types.canonical(pointee),
_ => canonical,
};
let TypeKind::Function(id) = self.types.kind(canonical) else {
self.unsupported("a call through something that is not a function", span);
return None;
};
let signature = self.types.signature(id);
let ret = signature.ret;
let variadic = signature.variadic || !signature.prototyped;
let params = signature.params.clone();
match abi::plan(self.types, self.target, ret, ¶ms, actual, variadic) {
Ok(plan) => Some(plan),
Err(what) => {
self.unsupported(what, span);
None
}
}
}
pub(crate) fn image(&mut self, init: Option<InitList>, size: u64, span: Span) -> DataList {
let Some(init) = init else { return self.zeros(size) };
let entries = self.in_image_order(&self.tast[init]);
let mut packed = self.packed(&entries, size);
let mut data: Vec<Datum> = Vec::with_capacity(entries.len());
let mut at = 0;
for entry in entries {
let Some(datum) = self.entry(entry, &mut packed, size) else { continue };
match entry.offset.cmp(&at) {
Ordering::Greater => data.push(Datum::Zero(entry.offset - at)),
Ordering::Less => {
self.unsupported("an initializer that writes over an earlier one", span);
continue;
}
Ordering::Equal => {}
}
at = entry.offset + datum.size(&self.module);
data.push(datum);
}
if at < size {
data.push(Datum::Zero(size - at));
}
self.module.push_data(&data)
}
fn in_image_order(&self, entries: &[InitEntry]) -> Vec<InitEntry> {
let mut sorted = entries.to_vec();
sorted.sort_by_key(|entry| entry.offset);
let mut kept: Vec<InitEntry> = Vec::with_capacity(sorted.len());
for entry in sorted {
if !entry.is_bit_field() {
let over = |last: &InitEntry| last.offset == entry.offset && !last.is_bit_field();
while kept.last().is_some_and(over) {
kept.pop();
}
}
kept.push(entry);
}
kept
}
fn entry(
&mut self,
entry: InitEntry,
packed: &mut BTreeMap<u64, u8>,
size: u64,
) -> Option<Datum> {
if entry.is_bit_field() {
let bytes = take_run(packed, entry.offset)?;
return Some(Datum::Bytes(self.module.push_bytes(&bytes)));
}
let room = size.saturating_sub(entry.offset);
self.datum(entry.value, room)
}
fn packed(&mut self, entries: &[InitEntry], size: u64) -> BTreeMap<u64, u8> {
let mut bytes = BTreeMap::new();
for entry in entries.iter().filter(|entry| entry.is_bit_field()) {
let Some(folded) = self.fold(entry.value) else { continue };
let Const::Int(number) = folded else {
let span = self.tast.expr_span(entry.value);
let what = "a bit-field initialized by something that is not an integer";
self.unsupported(what, span);
continue;
};
let width = entry.bit_width;
let ones = if width >= 128 { u128::MAX } else { (1u128 << width) - 1 };
let mut mask = ones << entry.bit_offset;
let mut placed = ((number as u128) & ones) << entry.bit_offset;
let mut at = entry.offset;
while mask != 0 && at < size {
let (bits, keep) = ((placed & 0xff) as u8, !((mask & 0xff) as u8));
if bits != 0 || bytes.contains_key(&at) {
let byte = bytes.entry(at).or_insert(0);
*byte = (*byte & keep) | bits;
}
mask >>= 8;
placed >>= 8;
at += 1;
}
}
bytes
}
fn datum(&mut self, value: ExprId, room: u64) -> Option<Datum> {
let tast = self.tast;
let ty = tast[value].ty;
let span = tast.expr_span(value);
if let TypeKind::Array { .. } = self.types.kind(self.types.canonical(ty)) {
let ExprKind::Str(id) = tast[value].kind else {
self.unsupported("this initializer", span);
return None;
};
let bytes = tast[id].bytes(self.target);
let take = bytes.len().min(usize::try_from(room).unwrap_or(usize::MAX));
return Some(Datum::Bytes(self.module.push_bytes(&bytes[..take])));
}
let size = repr::size_of(self.types, self.target, ty);
match self.fold(value)? {
Const::Int(number) => {
let ty = repr::value_type(self.types, self.target, ty)?;
let ty = if ty.is_ptr() { Type::int(self.target.pointer_width) } else { ty };
let imm = self.module.add_imm(Imm::int(number, ty));
Some(Datum::Scalar { ty, value: imm })
}
Const::Float(number) => {
let ty = repr::value_type(self.types, self.target, ty)?;
let imm = self.module.add_imm(Imm::from_bits(number.to_bits()));
Some(Datum::Scalar { ty, value: imm })
}
Const::Address(address) => {
let symbol = match address.base {
Base::Decl(decl) => self.symbol_of(decl),
Base::Str(id) => self.string(id),
};
let addend = i64::try_from(address.offset).unwrap_or(0);
let size = u32::try_from(size).unwrap_or(0);
Some(Datum::Addr(self.module.add_reloc(Reloc { symbol, addend, size })))
}
}
}
fn zeros(&mut self, size: u64) -> DataList {
if size == 0 {
return DataList::EMPTY;
}
self.module.push_data(&[Datum::Zero(size)])
}
pub(crate) fn string(&mut self, id: StrId) -> Symbol {
if let Some(&symbol) = self.strings.get(&id) {
return symbol;
}
let literal = &self.tast[id];
let bytes = literal.bytes(self.target);
let align = literal.encoding.element_width(self.target) / 8;
let symbol = self.names.intern(&format!(".Lstr.{}", self.strings.len()));
let mut global = Global::new(symbol, bytes.len() as u64, align.max(1));
global.linkage = IrLinkage::Internal;
global.constant = true;
let range = self.module.push_bytes(&bytes);
global.init = Some(self.module.push_data(&[Datum::Bytes(range)]));
self.module.add_global(global);
self.strings.insert(id, symbol);
symbol
}
pub(crate) fn symbol_of(&mut self, decl: DeclId) -> Symbol {
let tast = self.tast;
let node = &tast[decl];
if node.linkage != Linkage::None {
return node.name.unwrap_or_else(|| self.names.intern(".Lanon"));
}
if let Some(&symbol) = self.statics.get(&decl) {
return symbol;
}
let base = match node.name {
Some(name) => self.names.resolve(name).to_string(),
None => ".Lanon".to_string(),
};
let symbol = self.names.intern(&format!("{base}.{}", self.statics.len()));
self.statics.insert(decl, symbol);
symbol
}
pub(crate) fn local_static(&mut self, decl: DeclId) {
if !self.done.insert(decl) {
return;
}
match self.tast[decl].kind {
DeclKind::Function => self.function(decl),
DeclKind::Object => self.object(decl),
}
}
fn fold(&mut self, expr: ExprId) -> Option<Const> {
let mut eval = Eval::new(self.tast, self.types, self.target, self.names);
let folded = eval.constant(expr);
let reported = eval.finish();
self.diagnostics.extend(reported);
match folded {
Ok(value) => Some(value),
Err(stop) => {
if !stop.poisoned {
let span = self.tast.expr_span(stop.at);
self.unsupported("an initializer this compiler cannot fold", span);
}
None
}
}
}
pub(crate) fn unsupported(&mut self, what: &str, span: Span) {
self.diagnostics.push(
Diagnostic::error(format!("{what} is not supported yet"), span).with_code("E0519"),
);
}
}
fn take_run(bytes: &mut BTreeMap<u64, u8>, start: u64) -> Option<Vec<u8>> {
let mut run = vec![bytes.remove(&start)?];
let mut at = start + 1;
while let Some(byte) = bytes.remove(&at) {
run.push(byte);
at += 1;
}
Some(run)
}