use alloc::collections::BTreeSet;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use ir_lang::{FuncId, Init, Linkage, Module, SymbolRef};
use crate::isel::{self, IselError};
use crate::mir;
use crate::regalloc::{self, RegAllocError};
use crate::target::abi::Abi;
use crate::x86_64::{self, EmitError, EmittedFunction, Emitter, Program, Reloc, RelocKind};
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Options {
pub abi: &'static Abi,
pub frame_pointer: bool,
pub trap_symbol: &'static str,
pub gc_poll_symbol: &'static str,
pub resume_symbol: &'static str,
pub pow_f64_symbol: &'static str,
pub pow_f32_symbol: &'static str,
pub jump_table_min_cases: u32,
pub jump_table_max_spread: u32,
pub regalloc: regalloc::Options,
}
impl Options {
#[must_use]
pub const fn sysv() -> Options {
Options::with_abi(&x86_64::SYSV)
}
#[must_use]
pub const fn win64() -> Options {
Options::with_abi(&x86_64::WIN64)
}
#[must_use]
pub const fn host() -> Options {
if cfg!(windows) {
Options::win64()
} else {
Options::sysv()
}
}
#[must_use]
pub const fn with_abi(abi: &'static Abi) -> Options {
Options {
abi,
frame_pointer: true,
trap_symbol: "__lexersketch_trap",
gc_poll_symbol: "__lexersketch_gc_poll",
resume_symbol: "__lexersketch_resume",
pow_f64_symbol: "__lexersketch_pow_f64",
pow_f32_symbol: "__lexersketch_pow_f32",
jump_table_min_cases: 4,
jump_table_max_spread: 3,
regalloc: regalloc::Options::new(),
}
}
#[must_use]
pub const fn with_frame_pointer(mut self, keep: bool) -> Options {
self.frame_pointer = keep;
self
}
#[must_use]
pub const fn with_runtime(
mut self,
trap: &'static str,
gc_poll: &'static str,
resume: &'static str,
) -> Options {
self.trap_symbol = trap;
self.gc_poll_symbol = gc_poll;
self.resume_symbol = resume;
self
}
#[must_use]
pub const fn with_pow_runtime(
mut self,
f64_pow: &'static str,
f32_pow: &'static str,
) -> Options {
self.pow_f64_symbol = f64_pow;
self.pow_f32_symbol = f32_pow;
self
}
#[must_use]
pub const fn with_jump_tables(mut self, min_cases: u32, max_spread: u32) -> Options {
self.jump_table_min_cases = min_cases;
self.jump_table_max_spread = max_spread;
self
}
#[must_use]
pub const fn with_regalloc(mut self, regalloc: regalloc::Options) -> Options {
self.regalloc = regalloc;
self
}
}
impl Default for Options {
fn default() -> Options {
Options::host()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompileError {
Invalid(ir_lang::ValidationError),
Select {
function: String,
error: IselError,
},
Allocate {
function: String,
error: RegAllocError,
},
Emit {
function: Option<String>,
error: EmitError,
},
}
impl fmt::Display for CompileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompileError::Invalid(e) => write!(f, "invalid module: {e}"),
CompileError::Select { function, error } => write!(f, "`{function}`: {error}"),
CompileError::Allocate { function, error } => write!(f, "`{function}`: {error}"),
CompileError::Emit {
function: Some(n),
error,
} => write!(f, "`{n}`: {error}"),
CompileError::Emit {
function: None,
error,
} => fmt::Display::fmt(error, f),
}
}
}
impl core::error::Error for CompileError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
CompileError::Invalid(e) => Some(e),
CompileError::Select { error, .. } => Some(error),
CompileError::Allocate { error, .. } => Some(error),
CompileError::Emit { error, .. } => Some(error),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DataObject {
name: String,
size: u64,
align: u32,
mutable: bool,
exported: bool,
bytes: Option<Vec<u8>>,
relocs: Vec<Reloc>,
}
impl DataObject {
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn size(&self) -> u64 {
self.size
}
#[inline]
#[must_use]
pub fn align(&self) -> u32 {
self.align
}
#[inline]
#[must_use]
pub fn mutable(&self) -> bool {
self.mutable
}
#[inline]
#[must_use]
pub fn exported(&self) -> bool {
self.exported
}
#[inline]
#[must_use]
pub fn bytes(&self) -> Option<&[u8]> {
self.bytes.as_deref()
}
#[inline]
#[must_use]
pub fn relocations(&self) -> &[Reloc] {
&self.relocs
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Compiled {
program: Program,
data: Vec<DataObject>,
imports: Vec<String>,
exports: Vec<String>,
}
impl Compiled {
#[inline]
#[must_use]
pub fn code(&self) -> &[u8] {
self.program.bytes()
}
#[inline]
#[must_use]
pub fn program(&self) -> &Program {
&self.program
}
#[inline]
#[must_use]
pub fn functions(&self) -> &[EmittedFunction] {
self.program.functions()
}
#[must_use]
pub fn function(&self, name: &str) -> Option<&EmittedFunction> {
let offset = self.program.symbol(name)?;
let functions = self.program.functions();
let i = functions.partition_point(|f| f.offset() < offset);
functions.get(i).filter(|f| f.name() == name)
}
#[inline]
#[must_use]
pub fn relocations(&self) -> &[Reloc] {
self.program.relocations()
}
#[inline]
#[must_use]
pub fn data(&self) -> &[DataObject] {
&self.data
}
#[inline]
#[must_use]
pub fn imports(&self) -> &[String] {
&self.imports
}
#[inline]
#[must_use]
pub fn exports(&self) -> &[String] {
&self.exports
}
}
pub fn compile(module: &Module, options: &Options) -> Result<Compiled, CompileError> {
module.validate().map_err(CompileError::Invalid)?;
let mut defined: BTreeSet<String> = BTreeSet::new();
let mut exports: Vec<String> = Vec::new();
for func in module.functions() {
if let Some(d) = module.func_decl(func) {
if d.linkage() != Linkage::Import {
let _ = defined.insert(d.name().to_string());
if d.linkage() == Linkage::Export {
exports.push(d.name().to_string());
}
}
}
}
for g in module.globals() {
if let (Some(name), Some(data)) = (module.global_name(g), module.global(g)) {
if data.linkage != Linkage::Import {
let _ = defined.insert(name.to_string());
if data.linkage == Linkage::Export {
exports.push(name.to_string());
}
}
}
}
exports.sort();
let mut emitter = Emitter::new();
let emit_err = |function: Option<&str>, error: EmitError| CompileError::Emit {
function: function.map(str::to_string),
error,
};
let mut externals: BTreeSet<String> = BTreeSet::new();
for func in module.functions() {
if let Some(d) = module.func_decl(func) {
if d.linkage() == Linkage::Import {
let _ = externals.insert(d.name().to_string());
}
}
}
for g in module.globals() {
if let Some(name) = module.global_name(g) {
let _ = externals.insert(name.to_string());
}
}
for rt in [
options.trap_symbol,
options.gc_poll_symbol,
options.resume_symbol,
options.pow_f64_symbol,
options.pow_f32_symbol,
] {
if !defined.contains(rt) {
let _ = externals.insert(rt.to_string());
}
}
for name in &externals {
emitter
.declare_external(name)
.map_err(|e| emit_err(Some(name), e))?;
}
for func in module.functions() {
let Some(decl) = module.func_decl(func) else {
continue;
};
if decl.linkage() == Linkage::Import {
continue;
}
let name = decl.name();
let mir = compile_validated(module, func, options)?;
let _ = emitter
.function(&mir)
.map_err(|e| emit_err(Some(name), e))?;
}
let program = emitter.finish().map_err(|e| emit_err(None, e))?;
let mut data = Vec::new();
for g in module.globals() {
let (Some(name), Some(gd)) = (module.global_name(g), module.global(g)) else {
continue;
};
if gd.linkage == Linkage::Import {
continue;
}
let mut relocs = Vec::with_capacity(gd.relocs.len());
for r in &gd.relocs {
let target = match r.target {
SymbolRef::Func(f) => module.func_decl(f).map(|d| d.name().to_string()),
SymbolRef::Global(t) => module.global_name(t).map(str::to_string),
_ => None,
};
if let Some(t) = target {
relocs.push(Reloc::new(
r.offset as usize,
RelocKind::Abs64,
&t,
r.addend,
));
}
}
data.push(DataObject {
name: name.to_string(),
size: gd.size,
align: gd.align,
mutable: gd.mutable,
exported: gd.linkage == Linkage::Export,
bytes: match &gd.init {
Init::Bytes(b) => Some(b.clone()),
_ => None,
},
relocs,
});
}
let mut imports: BTreeSet<String> = BTreeSet::new();
for r in program.relocations() {
if !defined.contains(&r.symbol) {
let _ = imports.insert(r.symbol.clone());
}
}
for d in &data {
for r in d.relocations() {
if !defined.contains(&r.symbol) {
let _ = imports.insert(r.symbol.clone());
}
}
}
Ok(Compiled {
program,
data,
imports: imports.into_iter().collect(),
exports,
})
}
pub fn compile_function(
module: &Module,
func: FuncId,
options: &Options,
) -> Result<mir::Function, CompileError> {
module.validate().map_err(CompileError::Invalid)?;
compile_validated(module, func, options)
}
fn compile_validated(
module: &Module,
func: FuncId,
options: &Options,
) -> Result<mir::Function, CompileError> {
let name = module
.func_decl(func)
.map_or_else(|| func.to_string(), |d| d.name().to_string());
let mut mir =
isel::select_validated(module, func, options).map_err(|error| CompileError::Select {
function: name.clone(),
error,
})?;
let _ = regalloc::allocate_with(&mut mir, &options.regalloc).map_err(|error| {
CompileError::Allocate {
function: name.clone(),
error,
}
})?;
Ok(mir)
}