use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use core::fmt;
use crate::encode::section_sizes;
use crate::ids::{
ConstId, FuncId, GlobalId, ImportId, NameRef, Reg, StrId, TableId, Target, TypeId, TypeRef,
UpvalIdx,
};
use crate::inst::Inst;
use crate::module::{
Callee, Const, Export, ExportItem, Function, Global, Handler, Hook, HookBinding, Import,
JumpTable, LineRow, LocalVar, Module, Strings,
};
use crate::policy::IntTy;
use crate::types::{FuncType, TypeDef, ValType};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum BuildError {
UnboundLabel {
func: FuncId,
label: u32,
},
LabelRebound {
func: FuncId,
label: u32,
},
ForeignLabel {
func: FuncId,
},
TargetOutOfRange {
func: FuncId,
target: u32,
},
InvalidRange {
func: FuncId,
},
TooMany {
func: FuncId,
what: &'static str,
},
UnknownRegister {
func: FuncId,
reg: Reg,
},
PromoteNotDynamic {
func: FuncId,
pc: u32,
},
ConflictingMoves {
func: FuncId,
dst: Reg,
},
ForeignFunction(FuncId),
UndefinedFunction(FuncId),
UndefinedType(TypeId),
ConstForwardRef(ConstId),
ModuleFull(&'static str),
TooLarge {
section: u32,
},
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildError::UnboundLabel { func, label } => {
write!(f, "label {label} of {func} is used but never bound")
}
BuildError::LabelRebound { func, label } => {
write!(f, "label {label} of {func} is bound twice")
}
BuildError::ForeignLabel { func } => {
write!(f, "{func} was given a label from another function")
}
BuildError::TargetOutOfRange { func, target } => {
write!(f, "branch target @{target} is past the end of {func}")
}
BuildError::InvalidRange { func } => {
write!(
f,
"a try region or local range of {func} ends before it starts"
)
}
BuildError::TooMany { func, what } => write!(f, "{func} has too many {what}"),
BuildError::UnknownRegister { func, reg } => {
write!(f, "{func} names undeclared register {reg}")
}
BuildError::PromoteNotDynamic { func, pc } => write!(
f,
"instruction {pc} of {func} uses overflow = promote but its destination is not dyn"
),
BuildError::ConflictingMoves { func, dst } => {
write!(f, "a parallel move in {func} writes {dst} twice")
}
BuildError::ForeignFunction(func) => {
write!(
f,
"{func} does not belong to this module builder or was added twice"
)
}
BuildError::UndefinedFunction(func) => {
write!(f, "{func} was declared but never added")
}
BuildError::UndefinedType(ty) => write!(f, "{ty} was reserved but never defined"),
BuildError::ConstForwardRef(id) => write!(
f,
"an aggregate constant refers to {id}, which is not an earlier constant"
),
BuildError::ModuleFull(what) => write!(f, "the module's {what} table is full"),
BuildError::TooLarge { section } => {
write!(f, "section {section} would exceed 4 GiB")
}
}
}
}
impl core::error::Error for BuildError {}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Label {
func: FuncId,
index: u32,
}
#[derive(Clone, Debug)]
pub struct FunctionBuilder {
id: FuncId,
name: StrId,
sig: TypeId,
regs: Vec<ValType>,
params: u16,
captures: Vec<ValType>,
names: Vec<StrId>,
name_index: BTreeMap<StrId, NameRef>,
type_refs: Vec<TypeId>,
type_index: BTreeMap<TypeId, TypeRef>,
code: Vec<Inst>,
labels: Vec<Option<u32>>,
fixups: Vec<(u32, Label)>,
tables: Vec<(Vec<Label>, Label)>,
handlers: Vec<(Label, Label, Label, Reg)>,
locals: Vec<(Reg, StrId, Label, Label)>,
lines: Vec<LineRow>,
location: Option<(StrId, u32, u32)>,
temps: BTreeMap<ValType, Reg>,
error: Option<BuildError>,
}
const UNRESOLVED: Target = Target(u32::MAX);
impl FunctionBuilder {
fn new(id: FuncId, name: StrId, sig: TypeId, params: &[ValType]) -> Self {
let mut f = FunctionBuilder {
id,
name,
sig,
regs: Vec::with_capacity(params.len()),
params: 0,
captures: Vec::new(),
names: Vec::new(),
name_index: BTreeMap::new(),
type_refs: Vec::new(),
type_index: BTreeMap::new(),
code: Vec::new(),
labels: Vec::new(),
fixups: Vec::new(),
tables: Vec::new(),
handlers: Vec::new(),
locals: Vec::new(),
lines: Vec::new(),
location: None,
temps: BTreeMap::new(),
error: None,
};
if params.len() > usize::from(u8::MAX) {
f.fail(BuildError::TooMany {
func: id,
what: "parameters",
});
}
for &ty in params {
let _param = f.reg(ty);
}
f.params = u16::try_from(params.len()).unwrap_or(u16::MAX);
f
}
fn fail(&mut self, error: BuildError) {
if self.error.is_none() {
self.error = Some(error);
}
}
fn too_many(&mut self, what: &'static str) {
self.fail(BuildError::TooMany {
func: self.id,
what,
});
}
#[must_use]
pub fn id(&self) -> FuncId {
self.id
}
#[must_use]
pub fn param(&self, index: u16) -> Reg {
Reg(index)
}
#[must_use]
pub fn param_count(&self) -> u16 {
self.params
}
pub fn reg(&mut self, ty: ValType) -> Reg {
match u16::try_from(self.regs.len()) {
Ok(index) => {
self.regs.push(ty);
Reg(index)
}
Err(_) => {
self.too_many("registers");
Reg(u16::MAX)
}
}
}
pub fn regs(&mut self, types: &[ValType]) -> Reg {
let first = Reg(u16::try_from(self.regs.len()).unwrap_or(u16::MAX));
for &ty in types {
let _reg = self.reg(ty);
}
first
}
pub fn capture(&mut self, ty: ValType) -> UpvalIdx {
match u16::try_from(self.captures.len()) {
Ok(index) => {
self.captures.push(ty);
UpvalIdx(index)
}
Err(_) => {
self.too_many("captures");
UpvalIdx(u16::MAX)
}
}
}
pub fn name_ref(&mut self, name: StrId) -> NameRef {
if let Some(&r) = self.name_index.get(&name) {
return r;
}
match u16::try_from(self.names.len()) {
Ok(index) => {
self.names.push(name);
let r = NameRef(index);
let _previous = self.name_index.insert(name, r);
r
}
Err(_) => {
self.too_many("name refs");
NameRef(u16::MAX)
}
}
}
pub fn type_ref(&mut self, ty: TypeId) -> TypeRef {
if let Some(&r) = self.type_index.get(&ty) {
return r;
}
match u16::try_from(self.type_refs.len()) {
Ok(index) => {
self.type_refs.push(ty);
let r = TypeRef(index);
let _previous = self.type_index.insert(ty, r);
r
}
Err(_) => {
self.too_many("type refs");
TypeRef(u16::MAX)
}
}
}
pub fn set_location(&mut self, file: StrId, line: u32, column: u32) {
self.location = Some((file, line, column));
}
#[must_use]
pub fn pc(&self) -> u32 {
u32::try_from(self.code.len()).unwrap_or(u32::MAX)
}
pub fn emit(&mut self, inst: Inst) -> u32 {
let pc = self.pc();
if pc == u32::MAX {
self.too_many("instructions");
return pc;
}
if let Some((file, line, column)) = self.location {
let same = self
.lines
.last()
.is_some_and(|r| (r.file, r.line, r.column) == (file, line, column));
if !same {
self.lines.push(LineRow {
pc,
file,
line,
column,
});
}
}
self.code.push(inst);
pc
}
pub fn mov(&mut self, dst: Reg, src: Reg) -> u32 {
self.emit(Inst::Mov { dst, src })
}
pub fn ret(&mut self, src: Reg) -> u32 {
self.emit(Inst::Ret { src })
}
pub fn ret_void(&mut self) -> u32 {
self.emit(Inst::RetVoid {})
}
pub fn label(&mut self) -> Label {
let index = match u32::try_from(self.labels.len()) {
Ok(index) => index,
Err(_) => {
self.too_many("labels");
u32::MAX
}
};
self.labels.push(None);
Label {
func: self.id,
index,
}
}
fn owns(&mut self, label: Label) -> bool {
let ok = label.func == self.id && (label.index as usize) < self.labels.len();
if !ok {
self.fail(BuildError::ForeignLabel { func: self.id });
}
ok
}
pub fn bind(&mut self, label: Label) {
if !self.owns(label) {
return;
}
let pc = self.pc();
match self.labels.get_mut(label.index as usize) {
Some(slot @ None) => *slot = Some(pc),
_ => self.fail(BuildError::LabelRebound {
func: self.id,
label: label.index,
}),
}
}
fn branch(&mut self, inst: Inst, label: Label) -> u32 {
let owned = self.owns(label);
let pc = self.emit(inst);
if owned {
self.fixups.push((pc, label));
}
pc
}
pub fn jmp(&mut self, label: Label) -> u32 {
self.branch(Inst::Jmp { target: UNRESOLVED }, label)
}
pub fn jmp_if(&mut self, cond: Reg, label: Label) -> u32 {
self.branch(
Inst::JmpIf {
cond,
target: UNRESOLVED,
},
label,
)
}
pub fn jmp_if_not(&mut self, cond: Reg, label: Label) -> u32 {
self.branch(
Inst::JmpIfNot {
cond,
target: UNRESOLVED,
},
label,
)
}
pub fn switch(&mut self, ty: IntTy, src: Reg, targets: &[Label], default: Label) -> TableId {
let all_owned = targets.iter().all(|&l| self.owns(l)) & self.owns(default);
let table = match u32::try_from(self.tables.len()) {
Ok(index) => TableId(index),
Err(_) => {
self.too_many("jump tables");
TableId(u32::MAX)
}
};
if all_owned {
self.tables.push((targets.to_vec(), default));
}
let _pc = self.emit(Inst::Switch { src, table, ty });
table
}
pub fn try_region(&mut self, start: Label, end: Label, handler: Label, catch: Reg) {
if self.owns(start) & self.owns(end) & self.owns(handler) {
self.handlers.push((start, end, handler, catch));
}
}
pub fn local(&mut self, reg: Reg, name: StrId, start: Label, end: Label) {
if self.owns(start) & self.owns(end) {
self.locals.push((reg, name, start, end));
}
}
fn temp(&mut self, ty: ValType, named: &BTreeSet<Reg>) -> Reg {
if let Some(&r) = self.temps.get(&ty) {
if !named.contains(&r) {
return r;
}
}
loop {
let r = self.reg(ty);
if self.error.is_some() || !named.contains(&r) {
let _previous = self.temps.insert(ty, r);
return r;
}
}
}
pub fn parallel_move(&mut self, moves: &[(Reg, Reg)]) {
let mut pending: Vec<(Reg, Reg)> = moves.iter().copied().filter(|(d, s)| d != s).collect();
let named: BTreeSet<Reg> = moves.iter().flat_map(|&(d, s)| [d, s]).collect();
let n = pending.len();
let mut writer: BTreeMap<Reg, usize> = BTreeMap::new();
for (i, &(dst, _)) in pending.iter().enumerate() {
if writer.insert(dst, i).is_some() {
self.fail(BuildError::ConflictingMoves { func: self.id, dst });
return;
}
}
let mut readers: BTreeMap<Reg, Vec<usize>> = BTreeMap::new();
for (i, &(_, src)) in pending.iter().enumerate() {
readers.entry(src).or_default().push(i);
}
let mut live_readers: BTreeMap<Reg, usize> =
readers.iter().map(|(&r, v)| (r, v.len())).collect();
let mut done = alloc::vec![false; n];
let mut ready: Vec<usize> = (0..n)
.filter(|&i| {
pending
.get(i)
.is_some_and(|&(d, _)| !live_readers.contains_key(&d))
})
.collect();
let mut remaining = n;
let mut scan = 0usize;
while remaining > 0 {
while let Some(i) = ready.pop() {
let Some(&(dst, src)) = pending.get(i) else {
continue;
};
let _pc = self.emit(Inst::Mov { dst, src });
if let Some(flag) = done.get_mut(i) {
*flag = true;
}
remaining -= 1;
if let Some(count) = live_readers.get_mut(&src) {
*count -= 1;
if *count == 0 {
let _gone = live_readers.remove(&src);
if let Some(&w) = writer.get(&src) {
if !done.get(w).copied().unwrap_or(true) {
ready.push(w);
}
}
}
}
}
if remaining == 0 {
break;
}
while done.get(scan).copied().unwrap_or(false) {
scan += 1;
}
let Some(&(saved, _)) = pending.get(scan) else {
return;
};
let Some(&ty) = self.regs.get(saved.index()) else {
self.fail(BuildError::UnknownRegister {
func: self.id,
reg: saved,
});
return;
};
let tmp = self.temp(ty, &named);
let _pc = self.emit(Inst::Mov {
dst: tmp,
src: saved,
});
if let Some(list) = readers.get(&saved) {
for &j in list {
if !done.get(j).copied().unwrap_or(true) {
if let Some(m) = pending.get_mut(j) {
m.1 = tmp;
}
}
}
}
let _gone = live_readers.remove(&saved);
ready.push(scan);
}
}
fn finish(mut self) -> Result<(FuncId, Function), BuildError> {
if let Some(error) = self.error {
return Err(error);
}
let func = self.id;
let len = self.pc();
let labels = core::mem::take(&mut self.labels);
let resolve =
|label: Label| -> Result<u32, BuildError> {
labels.get(label.index as usize).copied().flatten().ok_or(
BuildError::UnboundLabel {
func,
label: label.index,
},
)
};
let in_code = |pc: u32| -> Result<Target, BuildError> {
if pc < len {
Ok(Target(pc))
} else {
Err(BuildError::TargetOutOfRange { func, target: pc })
}
};
for &(pc, label) in &self.fixups {
let target = in_code(resolve(label)?)?;
if let Some(inst) = self.code.get_mut(pc as usize) {
*inst = match *inst {
Inst::Jmp { .. } => Inst::Jmp { target },
Inst::JmpIf { cond, .. } => Inst::JmpIf { cond, target },
Inst::JmpIfNot { cond, .. } => Inst::JmpIfNot { cond, target },
other => other,
};
}
}
for (pc, inst) in self.code.iter().enumerate() {
let (target, promote) = inst.build_checks();
if let Some(target) = target {
let _target = in_code(target.0)?;
}
if promote {
let declared = inst.dst().and_then(|d| self.regs.get(d.index()));
if declared.is_some_and(|&ty| ty != ValType::Dyn) {
let pc = u32::try_from(pc).unwrap_or(u32::MAX);
return Err(BuildError::PromoteNotDynamic { func, pc });
}
}
}
let mut tables = Vec::with_capacity(self.tables.len());
for (targets, default) in &self.tables {
let mut resolved = Vec::with_capacity(targets.len());
for &l in targets {
resolved.push(in_code(resolve(l)?)?);
}
tables.push(JumpTable {
targets: resolved,
default: in_code(resolve(*default)?)?,
});
}
let mut handlers = Vec::with_capacity(self.handlers.len());
for &(start, end, handler, catch) in &self.handlers {
let (start, end) = (resolve(start)?, resolve(end)?);
if start > end {
return Err(BuildError::InvalidRange { func });
}
let target = in_code(resolve(handler)?)?;
handlers.push(Handler {
start,
end,
target,
catch,
});
}
let mut locals = Vec::with_capacity(self.locals.len());
for &(reg, name, start, end) in &self.locals {
let (start, end) = (resolve(start)?, resolve(end)?);
if start > end {
return Err(BuildError::InvalidRange { func });
}
locals.push(LocalVar {
reg,
name,
start,
end,
});
}
Ok((
func,
Function {
name: self.name,
sig: self.sig,
regs: self.regs,
captures: self.captures,
names: self.names,
type_refs: self.type_refs,
tables,
handlers,
code: self.code,
lines: self.lines,
locals,
},
))
}
}
#[derive(Clone, Debug, Default)]
pub struct ModuleBuilder {
strings: Strings,
string_index: BTreeMap<Box<str>, StrId>,
types: Vec<Option<TypeDef>>,
type_index: BTreeMap<TypeDef, TypeId>,
consts: Vec<Const>,
const_index: BTreeMap<Const, ConstId>,
imports: Vec<Import>,
globals: Vec<Global>,
functions: Vec<Option<Function>>,
declared: Vec<(StrId, TypeId)>,
exports: Vec<Export>,
hooks: BTreeMap<Hook, Callee>,
name: Option<StrId>,
start: Option<FuncId>,
error: Option<BuildError>,
}
fn next_id(len: usize) -> Option<u32> {
u32::try_from(len).ok().filter(|&id| id != u32::MAX)
}
impl ModuleBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn fail(&mut self, error: BuildError) {
if self.error.is_none() {
self.error = Some(error);
}
}
pub fn string(&mut self, s: &str) -> StrId {
if let Some(&id) = self.string_index.get(s) {
return id;
}
let Some(id) = next_id(self.strings.len()) else {
self.fail(BuildError::ModuleFull("string"));
return StrId(u32::MAX);
};
if !self.strings.push(s) {
self.fail(BuildError::ModuleFull("string"));
return StrId(u32::MAX);
}
let _previous = self.string_index.insert(Box::from(s), StrId(id));
StrId(id)
}
pub fn add_type(&mut self, def: TypeDef) -> TypeId {
if def.is_structural() {
if let Some(&id) = self.type_index.get(&def) {
return id;
}
}
let Some(id) = next_id(self.types.len()) else {
self.fail(BuildError::ModuleFull("type"));
return TypeId(u32::MAX);
};
if def.is_structural() {
let _previous = self.type_index.insert(def.clone(), TypeId(id));
}
self.types.push(Some(def));
TypeId(id)
}
pub fn reserve_type(&mut self) -> TypeId {
let Some(id) = next_id(self.types.len()) else {
self.fail(BuildError::ModuleFull("type"));
return TypeId(u32::MAX);
};
self.types.push(None);
TypeId(id)
}
pub fn define_type(&mut self, id: TypeId, def: TypeDef) {
match self.types.get_mut(id.index()) {
Some(slot @ None) => *slot = Some(def),
_ => self.fail(BuildError::UndefinedType(id)),
}
}
pub fn func_type(&mut self, params: &[ValType], results: &[ValType]) -> TypeId {
self.add_type(TypeDef::Func(FuncType {
params: params.to_vec(),
results: results.to_vec(),
}))
}
pub fn constant(&mut self, c: Const) -> ConstId {
if let Some(&id) = self.const_index.get(&c) {
return id;
}
let len = self.consts.len();
if let Some(bad) = c.children().find(|child| child.index() >= len) {
self.fail(BuildError::ConstForwardRef(bad));
return ConstId(u32::MAX);
}
let Some(id) = next_id(len) else {
self.fail(BuildError::ModuleFull("constant"));
return ConstId(u32::MAX);
};
let _previous = self.const_index.insert(c.clone(), ConstId(id));
self.consts.push(c);
ConstId(id)
}
pub fn import(&mut self, module: &str, name: &str, sig: TypeId) -> ImportId {
let (module, name) = (self.string(module), self.string(name));
let Some(id) = next_id(self.imports.len()) else {
self.fail(BuildError::ModuleFull("import"));
return ImportId(u32::MAX);
};
self.imports.push(Import { module, name, sig });
ImportId(id)
}
pub fn global(
&mut self,
name: &str,
ty: ValType,
mutable: bool,
init: Option<ConstId>,
) -> GlobalId {
let name = self.string(name);
let Some(id) = next_id(self.globals.len()) else {
self.fail(BuildError::ModuleFull("global"));
return GlobalId(u32::MAX);
};
self.globals.push(Global {
name,
ty,
mutable,
init,
});
GlobalId(id)
}
pub fn function(
&mut self,
name: &str,
params: &[ValType],
results: &[ValType],
) -> FunctionBuilder {
let name = self.string(name);
let sig = self.func_type(params, results);
let id = match next_id(self.functions.len()) {
Some(id) => {
self.functions.push(None);
self.declared.push((name, sig));
FuncId(id)
}
None => {
self.fail(BuildError::ModuleFull("function"));
FuncId(u32::MAX)
}
};
FunctionBuilder::new(id, name, sig, params)
}
pub fn add_function(&mut self, f: FunctionBuilder) -> Result<FuncId, BuildError> {
let id = f.id;
let declared = self.declared.get(id.index()) == Some(&(f.name, f.sig));
let empty = matches!(self.functions.get(id.index()), Some(None));
if !declared || !empty {
return Err(BuildError::ForeignFunction(id));
}
let (id, function) = f.finish()?;
if let Some(slot) = self.functions.get_mut(id.index()) {
*slot = Some(function);
}
Ok(id)
}
pub fn export(&mut self, name: &str, item: ExportItem) {
let name = self.string(name);
if next_id(self.exports.len()).is_none() {
self.fail(BuildError::ModuleFull("export"));
return;
}
self.exports.push(Export { name, item });
}
pub fn hook(&mut self, hook: Hook, callee: Callee) {
let _previous = self.hooks.insert(hook, callee);
}
pub fn set_name(&mut self, name: &str) {
self.name = Some(self.string(name));
}
pub fn set_start(&mut self, func: FuncId) {
self.start = Some(func);
}
pub fn finish(self) -> Result<Module, BuildError> {
if let Some(error) = self.error {
return Err(error);
}
let mut functions = Vec::with_capacity(self.functions.len());
for (i, f) in self.functions.into_iter().enumerate() {
let id = FuncId(u32::try_from(i).unwrap_or(u32::MAX));
functions.push(f.ok_or(BuildError::UndefinedFunction(id))?);
}
let mut types = Vec::with_capacity(self.types.len());
for (i, t) in self.types.into_iter().enumerate() {
let id = TypeId(u32::try_from(i).unwrap_or(u32::MAX));
types.push(t.ok_or(BuildError::UndefinedType(id))?);
}
let module = Module {
strings: self.strings,
types,
consts: self.consts,
imports: self.imports,
globals: self.globals,
functions,
exports: self.exports,
hooks: self
.hooks
.into_iter()
.map(|(hook, callee)| HookBinding { hook, callee })
.collect(),
name: self.name,
start: self.start,
};
for (section, size) in (1u32..).zip(section_sizes(&module)) {
if size > u64::from(u32::MAX) {
return Err(BuildError::TooLarge { section });
}
}
Ok(module)
}
}