use std::collections::HashMap;
use std::fmt;
use std::ops::{Index, IndexMut};
use rucc_base::float::Format;
use rucc_base::{Idx, IdxRange, Symbol};
use rucc_target::{TargetInfo, Triple};
use crate::func::Func;
use crate::inst::{Imm, Meta, MetaNode};
use crate::ty::Type;
pub type FuncId = Idx<Func>;
pub type GlobalId = Idx<Global>;
pub type AliasId = Idx<Alias>;
pub type DataList = IdxRange<Datum>;
#[derive(Debug)]
pub struct Byte;
pub type ByteRange = IdxRange<Byte>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Linkage {
#[default]
External,
Internal,
Weak,
LinkOnce,
Common,
}
impl Linkage {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::External => "external",
Self::Internal => "internal",
Self::Weak => "weak",
Self::LinkOnce => "linkonce",
Self::Common => "common",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|linkage| linkage.name() == name)
}
pub fn all() -> impl Iterator<Item = Self> {
[Self::External, Self::Internal, Self::Weak, Self::LinkOnce, Self::Common].into_iter()
}
#[must_use]
pub const fn is_local(self) -> bool {
matches!(self, Self::Internal)
}
#[must_use]
pub const fn may_be_replaced(self) -> bool {
matches!(self, Self::Weak | Self::LinkOnce | Self::Common)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Visibility {
#[default]
Default,
Hidden,
Protected,
}
impl Visibility {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Default => "default",
Self::Hidden => "hidden",
Self::Protected => "protected",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|visibility| visibility.name() == name)
}
pub fn all() -> impl Iterator<Item = Self> {
[Self::Default, Self::Hidden, Self::Protected].into_iter()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum TlsModel {
#[default]
GlobalDynamic,
LocalDynamic,
InitialExec,
LocalExec,
}
impl TlsModel {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::GlobalDynamic => "global_dynamic",
Self::LocalDynamic => "local_dynamic",
Self::InitialExec => "initial_exec",
Self::LocalExec => "local_exec",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|model| model.name() == name)
}
pub fn all() -> impl Iterator<Item = Self> {
[Self::GlobalDynamic, Self::LocalDynamic, Self::InitialExec, Self::LocalExec].into_iter()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Datum {
Zero(u64),
Bytes(ByteRange),
Scalar {
ty: Type,
value: Idx<Imm>,
},
Addr(Idx<Reloc>),
}
impl Datum {
#[must_use]
pub fn size(self, module: &Module) -> u64 {
match self {
Self::Zero(bytes) => bytes,
Self::Bytes(range) => range.len() as u64,
Self::Scalar { ty, .. } => u64::from(ty.bits().div_ceil(8)) * u64::from(ty.lanes()),
Self::Addr(reloc) => u64::from(module[reloc].size),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Reloc {
pub symbol: Symbol,
pub addend: i64,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct Global {
pub name: Symbol,
pub size: u64,
pub align: u32,
pub linkage: Linkage,
pub visibility: Visibility,
pub tls: Option<TlsModel>,
pub constant: bool,
pub section: Option<Symbol>,
pub init: Option<DataList>,
}
impl Global {
#[must_use]
pub fn new(name: Symbol, size: u64, align: u32) -> Self {
Self {
name,
size,
align,
linkage: Linkage::External,
visibility: Visibility::Default,
tls: None,
constant: false,
section: None,
init: None,
}
}
#[must_use]
pub fn is_declaration(&self) -> bool {
self.init.is_none()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum AliasKind {
#[default]
Alias,
IFunc,
}
impl AliasKind {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Alias => "alias",
Self::IFunc => "ifunc",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
match name {
"alias" => Some(Self::Alias),
"ifunc" => Some(Self::IFunc),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Alias {
pub name: Symbol,
pub target: Symbol,
pub kind: AliasKind,
pub linkage: Linkage,
pub visibility: Visibility,
}
impl Alias {
#[must_use]
pub fn new(name: Symbol, target: Symbol) -> Self {
Self {
name,
target,
kind: AliasKind::Alias,
linkage: Linkage::External,
visibility: Visibility::Default,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolRef {
Func(FuncId),
Global(GlobalId),
Alias(AliasId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DataLayout {
pub little_endian: bool,
pub pointer_bits: u32,
pub pointer_align: u32,
pub i64_align: u32,
pub f80_align: Option<u32>,
pub stack_align: u32,
}
impl DataLayout {
#[must_use]
pub fn for_target(target: &TargetInfo) -> Self {
Self {
little_endian: target.little_endian,
pointer_bits: target.pointer_width,
pointer_align: target.pointer_width,
i64_align: 64,
f80_align: match target.long_double_format {
Format::X87Extended => Some(128),
_ => None,
},
stack_align: 128,
}
}
#[must_use]
pub fn parse(text: &str) -> Option<Self> {
let mut little_endian = None;
let mut pointer = None;
let mut i64_align = None;
let mut f80_align = None;
let mut stack_align = None;
for field in text.split('-') {
let seen = match field {
"e" => little_endian.replace(true).is_some(),
"E" => little_endian.replace(false).is_some(),
_ if field.starts_with("p:") => {
let (bits, align) = field[2..].split_once(':')?;
pointer.replace((number(bits)?, number(align)?)).is_some()
}
_ if field.starts_with("i64:") => i64_align.replace(number(&field[4..])?).is_some(),
_ if field.starts_with("f80:") => f80_align.replace(number(&field[4..])?).is_some(),
_ if field.starts_with('S') => stack_align.replace(number(&field[1..])?).is_some(),
_ => return None,
};
if seen {
return None;
}
}
let (pointer_bits, pointer_align) = pointer?;
Some(Self {
little_endian: little_endian?,
pointer_bits,
pointer_align,
i64_align: i64_align?,
f80_align,
stack_align: stack_align?,
})
}
}
impl fmt::Display for DataLayout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", if self.little_endian { "e" } else { "E" })?;
write!(f, "-p:{}:{}", self.pointer_bits, self.pointer_align)?;
write!(f, "-i64:{}", self.i64_align)?;
if let Some(align) = self.f80_align {
write!(f, "-f80:{align}")?;
}
write!(f, "-S{}", self.stack_align)
}
}
fn number(text: &str) -> Option<u32> {
if text.is_empty() || (text.len() > 1 && text.starts_with('0')) {
return None;
}
if !text.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
text.parse().ok()
}
#[derive(Debug)]
pub struct Module {
pub name: Symbol,
pub triple: Triple,
pub datalayout: DataLayout,
funcs: Vec<Func>,
globals: Vec<Global>,
aliases: Vec<Alias>,
metadata: Vec<MetaNode>,
data: Vec<Datum>,
bytes: Vec<u8>,
imms: Vec<Imm>,
relocs: Vec<Reloc>,
symbols: HashMap<Symbol, SymbolRef>,
}
impl Module {
#[must_use]
pub fn new(name: Symbol, target: &TargetInfo) -> Self {
Self {
name,
triple: target.triple,
datalayout: DataLayout::for_target(target),
funcs: Vec::new(),
globals: Vec::new(),
aliases: Vec::new(),
metadata: Vec::new(),
data: Vec::new(),
bytes: Vec::new(),
imms: Vec::new(),
relocs: Vec::new(),
symbols: HashMap::new(),
}
}
pub fn add_func(&mut self, func: Func) -> FuncId {
let id = Idx::from_usize(self.funcs.len());
self.claim(func.name, SymbolRef::Func(id));
self.funcs.push(func);
id
}
pub fn add_global(&mut self, global: Global) -> GlobalId {
let id = Idx::from_usize(self.globals.len());
self.claim(global.name, SymbolRef::Global(id));
self.globals.push(global);
id
}
pub fn add_alias(&mut self, alias: Alias) -> AliasId {
let id = Idx::from_usize(self.aliases.len());
self.claim(alias.name, SymbolRef::Alias(id));
self.aliases.push(alias);
id
}
#[must_use]
pub fn lookup(&self, name: Symbol) -> Option<SymbolRef> {
self.symbols.get(&name).copied()
}
pub fn funcs(&self) -> impl Iterator<Item = FuncId> + use<> {
(0..self.funcs.len()).map(Idx::from_usize)
}
pub fn globals(&self) -> impl Iterator<Item = GlobalId> + use<> {
(0..self.globals.len()).map(Idx::from_usize)
}
pub fn aliases(&self) -> impl Iterator<Item = AliasId> + use<> {
(0..self.aliases.len()).map(Idx::from_usize)
}
fn claim(&mut self, name: Symbol, what: SymbolRef) {
assert!(
self.symbols.insert(name, what).is_none(),
"a module cannot have two symbols with the same name"
);
}
pub fn add_meta(&mut self, node: MetaNode) -> Meta {
self.metadata.push(node);
Idx::from_usize(self.metadata.len() - 1)
}
pub fn metadata(&self) -> impl Iterator<Item = Meta> + use<> {
(0..self.metadata.len()).map(Idx::from_usize)
}
pub fn push_data(&mut self, data: &[Datum]) -> DataList {
let start = self.data.len();
self.data.extend_from_slice(data);
DataList::new(Idx::from_usize(start), Idx::from_usize(self.data.len()))
}
pub fn push_bytes(&mut self, bytes: &[u8]) -> ByteRange {
let start = self.bytes.len();
self.bytes.extend_from_slice(bytes);
ByteRange::new(Idx::from_usize(start), Idx::from_usize(self.bytes.len()))
}
pub fn add_imm(&mut self, imm: Imm) -> Idx<Imm> {
self.imms.push(imm);
Idx::from_usize(self.imms.len() - 1)
}
pub fn add_reloc(&mut self, reloc: Reloc) -> Idx<Reloc> {
self.relocs.push(reloc);
Idx::from_usize(self.relocs.len() - 1)
}
#[must_use]
pub fn counts(&self) -> ModuleCounts {
ModuleCounts {
funcs: self.funcs.len(),
globals: self.globals.len(),
aliases: self.aliases.len(),
metadata: self.metadata.len(),
data_bytes: self.bytes.len(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ModuleCounts {
pub funcs: usize,
pub globals: usize,
pub aliases: usize,
pub metadata: usize,
pub data_bytes: usize,
}
impl Index<FuncId> for Module {
type Output = Func;
fn index(&self, id: FuncId) -> &Func {
&self.funcs[id.index()]
}
}
impl IndexMut<FuncId> for Module {
fn index_mut(&mut self, id: FuncId) -> &mut Func {
&mut self.funcs[id.index()]
}
}
impl Index<GlobalId> for Module {
type Output = Global;
fn index(&self, id: GlobalId) -> &Global {
&self.globals[id.index()]
}
}
impl IndexMut<GlobalId> for Module {
fn index_mut(&mut self, id: GlobalId) -> &mut Global {
&mut self.globals[id.index()]
}
}
impl Index<AliasId> for Module {
type Output = Alias;
fn index(&self, id: AliasId) -> &Alias {
&self.aliases[id.index()]
}
}
impl Index<Meta> for Module {
type Output = MetaNode;
fn index(&self, meta: Meta) -> &MetaNode {
&self.metadata[meta.index()]
}
}
impl Index<Idx<Imm>> for Module {
type Output = Imm;
fn index(&self, imm: Idx<Imm>) -> &Imm {
&self.imms[imm.index()]
}
}
impl Index<Idx<Reloc>> for Module {
type Output = Reloc;
fn index(&self, reloc: Idx<Reloc>) -> &Reloc {
&self.relocs[reloc.index()]
}
}
impl Index<DataList> for Module {
type Output = [Datum];
fn index(&self, list: DataList) -> &[Datum] {
&self.data[list.as_usize_range()]
}
}
impl Index<ByteRange> for Module {
type Output = [u8];
fn index(&self, range: ByteRange) -> &[u8] {
&self.bytes[range.as_usize_range()]
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_target::{Arch, Env, Os};
use super::*;
use crate::inst::Signature;
fn target(arch: Arch, os: Os, env: Env) -> TargetInfo {
TargetInfo::new(Triple::new(arch, os, env))
}
fn linux() -> TargetInfo {
target(Arch::X86_64, Os::Linux, Env::Gnu)
}
#[test]
fn a_datum_is_sixteen_bytes() {
assert_eq!(size_of::<Datum>(), 16);
}
#[test]
fn the_layout_of_x86_64_linux_is_the_one_in_the_spec() {
let layout = DataLayout::for_target(&linux());
assert_eq!(layout.to_string(), "e-p:64:64-i64:64-f80:128-S128");
}
#[test]
fn only_x86_has_the_eighty_bit_format() {
assert_eq!(DataLayout::for_target(&linux()).f80_align, Some(128));
let arm = DataLayout::for_target(&target(Arch::Aarch64, Os::Linux, Env::Gnu));
assert_eq!(arm.f80_align, None);
assert_eq!(arm.to_string(), "e-p:64:64-i64:64-S128");
}
#[test]
fn a_layout_round_trips() {
for triple in [
Triple::new(Arch::X86_64, Os::Linux, Env::Gnu),
Triple::new(Arch::X86_64, Os::Darwin, Env::None),
Triple::new(Arch::Aarch64, Os::Darwin, Env::None),
Triple::new(Arch::Riscv64, Os::Linux, Env::Musl),
] {
let layout = DataLayout::for_target(&TargetInfo::new(triple));
let text = layout.to_string();
assert_eq!(DataLayout::parse(&text), Some(layout), "{text}");
}
}
#[test]
fn a_layout_may_be_written_in_any_order() {
let text = "S128-i64:64-f80:128-p:64:64-e";
assert_eq!(DataLayout::parse(text), Some(DataLayout::for_target(&linux())));
}
#[test]
fn a_layout_needs_every_field_it_prints() {
for text in ["", "e", "e-p:64:64-S128", "e-i64:64-S128", "e-p:64:64-i64:64"] {
assert_eq!(DataLayout::parse(text), None, "{text}");
}
}
#[test]
fn a_layout_refuses_a_second_spelling() {
for text in ["e-p:64:064-i64:64-S128", "e-e-p:64:64-i64:64-S128", "e-p:64:64-i64:64-S128-x"]
{
assert_eq!(DataLayout::parse(text), None, "{text}");
}
}
#[test]
fn a_module_finds_what_it_holds() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("test.c"), &linux());
let counter = names.intern("counter");
let sum = names.intern("sum");
let total = names.intern("total");
let global = module.add_global(Global::new(counter, 4, 4));
let func = module.add_func(Func::new(sum, Signature::new()));
let alias = module.add_alias(Alias::new(total, counter));
assert_eq!(module.lookup(counter), Some(SymbolRef::Global(global)));
assert_eq!(module.lookup(sum), Some(SymbolRef::Func(func)));
assert_eq!(module.lookup(total), Some(SymbolRef::Alias(alias)));
assert_eq!(module.lookup(names.intern("nothing")), None);
assert_eq!(module[alias].target, counter);
assert!(module[global].is_declaration());
assert!(module[func].is_declaration());
}
#[test]
#[should_panic(expected = "two symbols with the same name")]
fn a_name_means_one_thing() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("test.c"), &linux());
let name = names.intern("x");
module.add_global(Global::new(name, 4, 4));
module.add_func(Func::new(name, Signature::new()));
}
#[test]
fn an_initializer_adds_up_to_the_size() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("test.c"), &linux());
let text = names.intern("hi.str");
let seven = module.add_imm(Imm::int(7, Type::int(32)));
let bytes = module.push_bytes(b"hi\0");
let addr = module.add_reloc(Reloc { symbol: text, addend: 0, size: 8 });
let init = module.push_data(&[
Datum::Scalar { ty: Type::int(32), value: seven },
Datum::Zero(4),
Datum::Addr(addr),
Datum::Zero(8),
]);
let mut global = Global::new(names.intern("entry"), 24, 8);
global.init = Some(init);
global.constant = true;
let id = module.add_global(global);
assert!(!module[id].is_declaration());
let size: u64 = module[init].iter().map(|datum| datum.size(&module)).sum();
assert_eq!(size, module[id].size);
assert_eq!(&module[bytes], b"hi\0");
assert_eq!(module[seven].unsigned(), 7);
assert_eq!(module.counts().data_bytes, 3);
}
#[test]
fn a_scalar_datum_is_as_wide_as_its_type() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("test.c"), &linux());
let value = module.add_imm(Imm::int(0, Type::int(32)));
assert_eq!(Datum::Scalar { ty: Type::int(32), value }.size(&module), 4);
assert_eq!(Datum::Scalar { ty: Type::I1, value }.size(&module), 1);
assert_eq!(Datum::Scalar { ty: Type::int(24), value }.size(&module), 3);
assert_eq!(Datum::Scalar { ty: Type::vector(Type::int(8), 16), value }.size(&module), 16);
}
#[test]
fn the_names_round_trip() {
for linkage in Linkage::all() {
assert_eq!(Linkage::from_name(linkage.name()), Some(linkage));
}
for visibility in Visibility::all() {
assert_eq!(Visibility::from_name(visibility.name()), Some(visibility));
}
for model in TlsModel::all() {
assert_eq!(TlsModel::from_name(model.name()), Some(model));
}
for kind in [AliasKind::Alias, AliasKind::IFunc] {
assert_eq!(AliasKind::from_name(kind.name()), Some(kind));
}
assert_eq!(Linkage::from_name("static"), None);
assert_eq!(Visibility::from_name("internal"), None);
}
#[test]
fn only_internal_linkage_is_local() {
for linkage in Linkage::all() {
assert_eq!(linkage.is_local(), linkage == Linkage::Internal);
assert_eq!(
linkage.may_be_replaced(),
!matches!(linkage, Linkage::External | Linkage::Internal)
);
}
}
#[test]
fn metadata_is_shared_by_the_whole_module() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("test.c"), &linux());
let char_node = module.add_meta(MetaNode {
name: names.intern("omnipotent char"),
parent: None,
offset: 0,
});
let int_node = module.add_meta(MetaNode {
name: names.intern("int"),
parent: Some(char_node),
offset: 0,
});
assert_eq!(module[int_node].parent, Some(char_node));
assert_eq!(module.metadata().count(), 2);
}
}