use std::collections::HashMap;
use rucc_base::{Interner, Symbol};
use rucc_debug::{Bits, Constant, Encoding, Member, Param, Qualifier, Shape, Sig};
use rucc_diag::{SourceMap, Span};
use rucc_sema::{DeclId, DeclKind, Linkage, Stmt, StmtId, StorageDuration, Tast};
use rucc_target::TargetInfo;
use rucc_types::{
ArrayLen, IntKind, Qualifiers, RecordId, RecordKind, Type, TypeId, TypeKind, Types,
};
#[derive(Debug, Default)]
pub(crate) struct Meaning {
pub types: Vec<Shape>,
pub funcs: HashMap<String, Known>,
pub objects: HashMap<String, Held>,
pub locals: HashMap<u32, Named>,
pub scopes: Vec<Scope>,
}
#[derive(Debug, Clone)]
pub(crate) struct Scope {
pub parent: Option<usize>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub(crate) struct Known {
pub file: String,
pub line: u32,
pub sig: Option<Sig>,
pub params: Vec<Option<u32>>,
pub external: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct Named {
pub name: String,
pub file: String,
pub line: u32,
pub ty: Option<usize>,
pub scope: Option<usize>,
}
#[derive(Debug, Clone)]
pub(crate) struct Held {
pub file: String,
pub line: u32,
pub ty: Option<usize>,
pub external: bool,
}
pub(crate) fn collect(
tast: &Tast,
types: &Types,
target: &TargetInfo,
names: &Interner,
sources: &SourceMap,
) -> Meaning {
let mut walk =
Walk { types, target, names, out: Vec::new(), memo: HashMap::new(), tags: HashMap::new() };
let mut funcs = HashMap::new();
let mut objects = HashMap::new();
for &id in tast.top_level() {
let decl = &tast[id];
let Some(at) = sources.presumed(tast.decl_span(id).lo) else { continue };
let Some(symbol) = symbol(tast, names, id) else { continue };
let external = decl.linkage == Linkage::External;
match decl.kind {
DeclKind::Function if decl.body.is_some() => {
let described = walk.signature(tast, id);
let known = Known {
file: at.name.to_owned(),
line: at.line,
sig: described.as_ref().map(|(sig, _)| sig.clone()),
params: described.map(|(_, params)| params).unwrap_or_default(),
external,
};
funcs.insert(symbol, known);
}
DeclKind::Object if decl.duration == StorageDuration::Static => {
let held = Held {
file: at.name.to_owned(),
line: at.line,
ty: walk.told(decl.ty),
external,
};
objects.insert(symbol, held);
}
_ => {}
}
}
let nests = nesting(tast);
let mut locals = HashMap::new();
for raw in 0..u32::try_from(tast.counts().decls).unwrap_or(u32::MAX) {
let id = DeclId::new(raw);
let decl = &tast[id];
if decl.kind != DeclKind::Object || decl.duration != StorageDuration::Automatic {
continue;
}
let Some(name) = decl.name else { continue };
let Some(at) = sources.presumed(tast.decl_span(id).lo) else { continue };
let ty = walk.told(decl.ty);
let name = walk.spelled(name);
let scope = nests.which.get(&raw).copied();
locals.insert(raw, Named { name, file: at.name.to_owned(), line: at.line, ty, scope });
}
for &alias in types.aliases() {
let Some(of) = walk.told_or_void(alias.of) else { continue };
let name = walk.spelled(alias.name);
walk.out.push(Shape::Alias { name, of });
}
Meaning { types: walk.out, funcs, objects, locals, scopes: nests.out }
}
fn nesting(tast: &Tast) -> Nests<'_> {
let mut nests = Nests { tast, out: Vec::new(), which: HashMap::new() };
for &id in tast.top_level() {
let decl = &tast[id];
if decl.kind != DeclKind::Function {
continue;
}
let Some(body) = decl.body else { continue };
if let Stmt::Block(list) = tast[body] {
for &stmt in &tast[list] {
nests.walk(stmt, None);
}
}
}
nests
}
struct Nests<'a> {
tast: &'a Tast,
out: Vec<Scope>,
which: HashMap<u32, usize>,
}
impl Nests<'_> {
fn walk(&mut self, at: StmtId, inside: Option<usize>) {
match self.tast[at] {
Stmt::Block(list) => {
let scope = self.open(self.tast.stmt_span(at), inside);
for &stmt in &self.tast[list] {
self.walk(stmt, Some(scope));
}
}
Stmt::Decls(list) => {
let Some(scope) = inside else { return };
for &decl in &self.tast[list] {
self.which.insert(decl.raw(), scope);
}
}
Stmt::If { then, otherwise, .. } => {
self.walk(then, inside);
if let Some(otherwise) = otherwise {
self.walk(otherwise, inside);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::Switch { body, .. }
| Stmt::Case { body, .. }
| Stmt::Default { body }
| Stmt::Label { body, .. } => self.walk(body, inside),
Stmt::For { init, body, .. } => {
let declares = init.is_some_and(|init| matches!(self.tast[init], Stmt::Decls(_)));
let inside = match declares {
true => Some(self.open(self.tast.stmt_span(at), inside)),
false => inside,
};
if let Some(init) = init {
self.walk(init, inside);
}
self.walk(body, inside);
}
_ => {}
}
}
fn open(&mut self, span: Span, inside: Option<usize>) -> usize {
self.out.push(Scope { parent: inside, span });
self.out.len() - 1
}
}
fn symbol(tast: &Tast, names: &Interner, id: DeclId) -> Option<String> {
let decl = &tast[id];
match decl.asm_label {
Some(label) => {
Some(tast[label].elements.iter().filter_map(|&unit| char::from_u32(unit)).collect())
}
None => Some(names.resolve(decl.name?).to_owned()),
}
}
struct Walk<'a> {
types: &'a Types,
target: &'a TargetInfo,
names: &'a Interner,
out: Vec<Shape>,
memo: HashMap<Type, Option<usize>>,
tags: HashMap<RecordId, usize>,
}
impl Walk<'_> {
fn signature(&mut self, tast: &Tast, id: DeclId) -> Option<(Sig, Vec<Option<u32>>)> {
let declared = tast[id].ty;
let TypeKind::Function(which) = self.types.kind(self.types.canonical(declared)) else {
return None;
};
let signature = self.types.signature(which).clone();
let returns = self.told_or_void(signature.ret)?;
let written = tast[tast[id].params].to_vec();
let mut params = Vec::with_capacity(signature.params.len());
let mut declared = Vec::with_capacity(signature.params.len());
for (index, &ty) in signature.params.iter().enumerate() {
let ty = self.told(ty)?;
let name = written.get(index).and_then(|¶m| tast[param].name);
params.push(Param { name: name.map(|name| self.spelled(name)), ty, spot: None });
declared.push(written.get(index).map(|param| param.raw()));
}
let sig =
Sig { returns, params, variadic: signature.variadic, prototyped: signature.prototyped };
Some((sig, declared))
}
fn told_or_void(&mut self, id: TypeId) -> Option<Option<usize>> {
let ty = self.types.get(id);
if matches!(ty.kind, TypeKind::Void) && ty.quals.is_none() {
return Some(None);
}
self.told(id).map(Some)
}
fn told(&mut self, id: TypeId) -> Option<usize> {
self.shaped(self.types.get(id), id)
}
fn shaped(&mut self, ty: Type, id: TypeId) -> Option<usize> {
if let Some(&known) = self.memo.get(&ty) {
return known;
}
let answer = self.layered(ty, id);
self.memo.insert(ty, answer);
answer
}
fn layered(&mut self, ty: Type, id: TypeId) -> Option<usize> {
for (mask, which) in [
(Qualifiers::CONST, Qualifier::Const),
(Qualifiers::VOLATILE, Qualifier::Volatile),
(Qualifiers::RESTRICT, Qualifier::Restrict),
] {
if !ty.quals.has(mask) {
continue;
}
let inner = Type { kind: ty.kind, quals: ty.quals.without(mask) };
let of = match inner.kind {
TypeKind::Void if inner.quals.is_none() => None,
_ => Some(self.shaped(inner, id)?),
};
let at = self.out.len();
self.out.push(Shape::Qualified { which, of });
return Some(at);
}
match ty.kind {
TypeKind::Record(record) => self.record(id, record),
kind => {
let shape = self.bare(id, kind)?;
let at = self.out.len();
self.out.push(shape);
Some(at)
}
}
}
fn bare(&mut self, id: TypeId, kind: TypeKind) -> Option<Shape> {
match kind {
TypeKind::Void | TypeKind::BitInt { .. } | TypeKind::Vector { .. } => None,
TypeKind::Bool => {
Some(Shape::Base { name: "_Bool".to_owned(), encoding: Encoding::Boolean, size: 1 })
}
TypeKind::Int(int) => Some(Shape::Base {
name: int.as_str().to_owned(),
encoding: reading(int, self.target),
size: self.size(id)?,
}),
TypeKind::Float(float) => Some(Shape::Base {
name: float.as_str().to_owned(),
encoding: Encoding::Float,
size: self.size(id)?,
}),
TypeKind::Complex(half) => {
let TypeKind::Float(float) = self.types.kind(self.types.canonical(half)) else {
return None;
};
Some(Shape::Base {
name: format!("complex {}", float.as_str()),
encoding: Encoding::Complex,
size: self.size(id)?,
})
}
TypeKind::Pointer(to) => {
let size = self.size(id)?;
Some(Shape::Pointer { to: self.told_or_void(to)?, size })
}
TypeKind::Atomic(inner) => {
let of = self.told_or_void(inner)?;
Some(Shape::Qualified { which: Qualifier::Atomic, of })
}
TypeKind::Array { elem, len } => {
let of = self.told(elem)?;
let count = match len {
ArrayLen::Fixed(count) => Some(count),
ArrayLen::Unknown | ArrayLen::Star | ArrayLen::Variable(_) => None,
};
Some(Shape::Array { of, count })
}
TypeKind::Function(which) => {
let signature = self.types.signature(which).clone();
let returns = self.told_or_void(signature.ret)?;
let mut params = Vec::with_capacity(signature.params.len());
for &ty in &signature.params {
params.push(Param { name: None, ty: self.told(ty)?, spot: None });
}
Some(Shape::Subroutine(Sig {
returns,
params,
variadic: signature.variadic,
prototyped: signature.prototyped,
}))
}
TypeKind::Enum(which) => {
let info = self.types.enum_info(which);
let name = info.tag.map(|tag| self.spelled(tag));
let underlying = info.underlying?;
let listed = info.enumerators.clone();
let size = self.size(underlying)?;
let of = self.told(underlying)?;
let values = listed
.iter()
.map(|one| Constant { name: self.spelled(one.name), value: one.value })
.collect();
Some(Shape::Enumeration { name, of, size, values })
}
TypeKind::Typedef { name, underlying, .. } => {
let of = self.told_or_void(underlying)?;
Some(Shape::Alias { name: self.spelled(name), of })
}
TypeKind::Record(_) => None,
}
}
fn record(&mut self, id: TypeId, record: RecordId) -> Option<usize> {
if let Some(&at) = self.tags.get(&record) {
return Some(at);
}
let info = self.types.record_info(record);
let union = info.kind == RecordKind::Union;
let name = info.tag.map(|tag| self.spelled(tag));
let placed = info.layout.is_some();
let size = self.size(id);
let at = self.out.len();
self.out.push(Shape::Record { union, name, size, members: None });
self.tags.insert(record, at);
if !placed {
return Some(at);
}
let fields = self.types.record_info(record).fields.clone();
let mut members = Vec::with_capacity(fields.len());
for field in &fields {
let Some(ty) = self.told(field.ty) else { continue };
let bits = match field.bits {
Some(width) => match u64::try_from(field.bit_offset()) {
Ok(start) => Some(Bits { at: start, width: u64::from(width) }),
Err(_) => continue,
},
None => None,
};
let name = field.name.map(|name| self.spelled(name));
members.push(Member { name, ty, at: field.offset, bits });
}
if let Shape::Record { members: held, .. } = &mut self.out[at] {
*held = Some(members);
}
Some(at)
}
fn size(&self, id: TypeId) -> Option<u64> {
rucc_types::layout(self.types, id, self.target).ok().map(|laid_out| laid_out.size)
}
fn spelled(&self, name: Symbol) -> String {
self.names.resolve(name).to_owned()
}
}
fn reading(int: IntKind, target: &TargetInfo) -> Encoding {
match int {
IntKind::Char if target.char_is_signed => Encoding::SignedChar,
IntKind::Char | IntKind::UChar => Encoding::UnsignedChar,
IntKind::SChar => Encoding::SignedChar,
_ if int.is_signed(target.char_is_signed) => Encoding::Signed,
_ => Encoding::Unsigned,
}
}