use crate::lcnf::*;
use std::collections::HashMap;
use super::functions::*;
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub enum BeamVal {
Reg(BeamReg),
Int(i64),
Float(f64),
Atom(String),
Nil,
Literal(u32),
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BeamProcess {
pub pid_sym: String,
pub module: String,
pub init_fn: String,
pub init_args: Vec<BeamExpr>,
pub linked: bool,
pub monitored: bool,
pub dictionary: Vec<(String, BeamExpr)>,
pub trap_exit: bool,
}
impl BeamProcess {
#[allow(dead_code)]
pub fn new(
pid_sym: impl Into<String>,
module: impl Into<String>,
init_fn: impl Into<String>,
) -> Self {
BeamProcess {
pid_sym: pid_sym.into(),
module: module.into(),
init_fn: init_fn.into(),
init_args: Vec::new(),
linked: false,
monitored: false,
dictionary: Vec::new(),
trap_exit: false,
}
}
#[allow(dead_code)]
pub fn with_arg(mut self, arg: BeamExpr) -> Self {
self.init_args.push(arg);
self
}
#[allow(dead_code)]
pub fn linked(mut self) -> Self {
self.linked = true;
self
}
#[allow(dead_code)]
pub fn trap_exit(mut self) -> Self {
self.trap_exit = true;
self
}
#[allow(dead_code)]
pub fn emit_spawn(&self) -> BeamExpr {
let args_list = self
.init_args
.iter()
.cloned()
.fold(BeamExpr::Nil, |tail, head| {
BeamExpr::Cons(Box::new(head), Box::new(tail))
});
let spawn_fn = if self.linked { "spawn_link" } else { "spawn" };
BeamExpr::Call {
module: Some("erlang".to_string()),
func: spawn_fn.to_string(),
args: vec![
BeamExpr::LitAtom(self.module.clone()),
BeamExpr::LitAtom(self.init_fn.clone()),
args_list,
],
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EtsAccess {
Private,
Protected,
Public,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtsTable {
pub name: String,
pub table_type: EtsType,
pub access: EtsAccess,
pub named_table: bool,
pub key_pos: u32,
}
impl EtsTable {
#[allow(dead_code)]
pub fn new_set(name: impl Into<String>) -> Self {
EtsTable {
name: name.into(),
table_type: EtsType::Set,
access: EtsAccess::Public,
named_table: true,
key_pos: 1,
}
}
#[allow(dead_code)]
pub fn emit_new(&self) -> BeamExpr {
let mut opts = vec![BeamExpr::LitAtom(self.table_type.to_string())];
if self.named_table {
opts.push(BeamExpr::LitAtom("named_table".to_string()));
}
opts.push(BeamExpr::LitAtom(self.access.to_string()));
if self.key_pos != 1 {
opts.push(BeamExpr::Tuple(vec![
BeamExpr::LitAtom("keypos".to_string()),
BeamExpr::LitInt(self.key_pos as i64),
]));
}
let opts_list = opts.into_iter().fold(BeamExpr::Nil, |tail, head| {
BeamExpr::Cons(Box::new(head), Box::new(tail))
});
BeamExpr::Call {
module: Some("ets".to_string()),
func: "new".to_string(),
args: vec![BeamExpr::LitAtom(self.name.clone()), opts_list],
}
}
#[allow(dead_code)]
pub fn emit_insert(&self, tuple: BeamExpr) -> BeamExpr {
BeamExpr::Call {
module: Some("ets".to_string()),
func: "insert".to_string(),
args: vec![BeamExpr::LitAtom(self.name.clone()), tuple],
}
}
#[allow(dead_code)]
pub fn emit_lookup(&self, key: BeamExpr) -> BeamExpr {
BeamExpr::Call {
module: Some("ets".to_string()),
func: "lookup".to_string(),
args: vec![BeamExpr::LitAtom(self.name.clone()), key],
}
}
#[allow(dead_code)]
pub fn emit_delete(&self, key: BeamExpr) -> BeamExpr {
BeamExpr::Call {
module: Some("ets".to_string()),
func: "delete".to_string(),
args: vec![BeamExpr::LitAtom(self.name.clone()), key],
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct TailCallInfo {
pub self_tail_calls: u32,
pub external_tails: Vec<String>,
pub is_tail_recursive: bool,
}
impl TailCallInfo {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn add_self_tail(&mut self) {
self.self_tail_calls += 1;
self.is_tail_recursive = true;
}
#[allow(dead_code)]
pub fn add_external_tail(&mut self, name: impl Into<String>) {
self.external_tails.push(name.into());
}
#[allow(dead_code)]
pub fn has_tail_calls(&self) -> bool {
self.self_tail_calls > 0 || !self.external_tails.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct BeamClause {
pub pattern: BeamPattern,
pub guard: Option<BeamExpr>,
pub body: BeamExpr,
}
pub struct BeamEmitCtx {
pub(super) label_counter: u32,
pub(super) var_counter: u32,
pub(super) indent: usize,
pub(super) output: String,
}
impl BeamEmitCtx {
pub fn new() -> Self {
BeamEmitCtx {
label_counter: 1,
var_counter: 0,
indent: 0,
output: String::new(),
}
}
pub(super) fn fresh_label(&mut self) -> u32 {
let l = self.label_counter;
self.label_counter += 1;
l
}
pub(super) fn fresh_var(&mut self) -> String {
let v = self.var_counter;
self.var_counter += 1;
format!("_V{}", v)
}
pub(super) fn indent_str(&self) -> String {
" ".repeat(self.indent)
}
pub(super) fn emit_line(&mut self, line: &str) {
let indent = self.indent_str();
self.output.push_str(&indent);
self.output.push_str(line);
self.output.push('\n');
}
pub(super) fn indented<F: FnOnce(&mut Self)>(&mut self, f: F) {
self.indent += 1;
f(self);
self.indent -= 1;
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct PatternNormalizer {
pub(super) wildcard_counter: u32,
}
impl PatternNormalizer {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn normalize(&mut self, pat: BeamPattern) -> BeamPattern {
match pat {
BeamPattern::Var(_) => {
let n = self.wildcard_counter;
self.wildcard_counter += 1;
BeamPattern::Var(format!("_N{}", n))
}
BeamPattern::Alias(_, inner) => self.normalize(*inner),
BeamPattern::Cons(h, t) => {
let hn = self.normalize(*h);
let tn = self.normalize(*t);
BeamPattern::Cons(Box::new(hn), Box::new(tn))
}
BeamPattern::Tuple(pats) => {
BeamPattern::Tuple(pats.into_iter().map(|p| self.normalize(p)).collect())
}
other => other,
}
}
#[allow(dead_code)]
pub fn equivalent(&mut self, a: BeamPattern, b: BeamPattern) -> bool {
let na = self.normalize(a);
let nb = self.normalize(b);
patterns_structurally_equal(&na, &nb)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BeamType {
Integer,
Float,
Atom,
Pid,
Port,
Reference,
Binary,
List(Box<BeamType>),
Tuple(Vec<BeamType>),
Map(Box<BeamType>, Box<BeamType>),
Fun(Vec<BeamType>, Box<BeamType>),
Any,
None,
Union(Vec<BeamType>),
Named(String),
}
#[derive(Debug, Clone)]
pub struct BeamFunction {
pub name: String,
pub arity: usize,
pub clauses: Vec<BeamClause>,
pub params: Vec<String>,
pub annotations: Vec<(String, String)>,
pub exported: bool,
pub instrs: Vec<BeamInstr>,
pub frame_size: u32,
pub return_type: Option<BeamType>,
pub param_types: Vec<BeamType>,
}
impl BeamFunction {
pub fn new(name: impl Into<String>, arity: usize) -> Self {
BeamFunction {
name: name.into(),
arity,
clauses: Vec::new(),
params: Vec::new(),
annotations: Vec::new(),
exported: false,
instrs: Vec::new(),
frame_size: 0,
return_type: None,
param_types: Vec::new(),
}
}
pub fn add_clause(&mut self, clause: BeamClause) {
self.clauses.push(clause);
}
pub fn annotate(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.annotations.push((key.into(), value.into()));
}
pub fn export(&mut self) {
self.exported = true;
}
pub fn key(&self) -> String {
format!("{}/{}", self.name, self.arity)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BeamDeadEliminator {
pub(super) reachable: std::collections::HashSet<String>,
}
impl BeamDeadEliminator {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn seed_exports(&mut self, module: &BeamModule) {
for (name, arity) in &module.exports {
self.reachable.insert(format!("{}/{}", name, arity));
}
}
#[allow(dead_code)]
pub fn eliminate(&self, module: BeamModule) -> BeamModule {
let mut result = BeamModule::new(module.name.clone());
result.attributes = module.attributes;
result.exports = module.exports;
result.on_load = module.on_load;
result.compile_info = module.compile_info;
for func in module.functions {
let key = format!("{}/{}", func.name, func.arity);
if self.reachable.contains(&key) || func.exported {
result.functions.push(func);
}
}
result
}
#[allow(dead_code)]
pub fn eliminated_count(before: &BeamModule, after: &BeamModule) -> usize {
before.functions.len().saturating_sub(after.functions.len())
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct AttributeBuilder {
pub(super) attrs: Vec<(String, String)>,
}
impl AttributeBuilder {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn vsn(mut self, version: impl Into<String>) -> Self {
self.attrs.push(("vsn".into(), version.into()));
self
}
#[allow(dead_code)]
pub fn author(mut self, name: impl Into<String>) -> Self {
self.attrs.push(("author".into(), name.into()));
self
}
#[allow(dead_code)]
pub fn compile(mut self, option: impl Into<String>) -> Self {
self.attrs.push(("compile".into(), option.into()));
self
}
#[allow(dead_code)]
pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attrs.push((key.into(), value.into()));
self
}
#[allow(dead_code)]
pub fn build(self) -> Vec<(String, String)> {
self.attrs
}
#[allow(dead_code)]
pub fn apply(self, module: &mut BeamModule) {
for (k, v) in self.build() {
module.add_attribute(k, v);
}
}
}
#[derive(Debug, Clone)]
pub enum BeamPattern {
Wildcard,
Var(String),
LitInt(i64),
LitAtom(String),
LitString(String),
Nil,
Cons(Box<BeamPattern>, Box<BeamPattern>),
Tuple(Vec<BeamPattern>),
MapPat(Vec<(BeamExpr, BeamPattern)>),
Alias(String, Box<BeamPattern>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BeamEndian {
Big,
Little,
Native,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BeamTypeCtx {
pub(super) env: HashMap<String, BeamType>,
pub(super) fun_types: HashMap<String, (Vec<BeamType>, BeamType)>,
}
impl BeamTypeCtx {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn bind(&mut self, var: impl Into<String>, ty: BeamType) {
self.env.insert(var.into(), ty);
}
#[allow(dead_code)]
pub fn lookup(&self, var: &str) -> Option<&BeamType> {
self.env.get(var)
}
#[allow(dead_code)]
pub fn register_fun(&mut self, name: impl Into<String>, params: Vec<BeamType>, ret: BeamType) {
self.fun_types.insert(name.into(), (params, ret));
}
#[allow(dead_code)]
pub fn infer(&self, expr: &BeamExpr) -> BeamType {
match expr {
BeamExpr::LitInt(_) => BeamType::Integer,
BeamExpr::LitFloat(_) => BeamType::Float,
BeamExpr::LitAtom(_) => BeamType::Atom,
BeamExpr::LitString(_) => BeamType::Named("binary".to_string()),
BeamExpr::Nil => BeamType::List(Box::new(BeamType::Any)),
BeamExpr::Var(name) => self
.env
.get(name.as_str())
.cloned()
.unwrap_or(BeamType::Any),
BeamExpr::Tuple(elems) => {
BeamType::Tuple(elems.iter().map(|e| self.infer(e)).collect())
}
BeamExpr::Cons(head, _) => BeamType::List(Box::new(self.infer(head))),
BeamExpr::Map(_) => BeamType::Map(Box::new(BeamType::Any), Box::new(BeamType::Any)),
BeamExpr::Fun { params, .. } => BeamType::Fun(
params.iter().map(|_| BeamType::Any).collect(),
Box::new(BeamType::Any),
),
_ => BeamType::Any,
}
}
#[allow(dead_code)]
pub fn merge(&self, other: &BeamTypeCtx) -> BeamTypeCtx {
let mut merged = self.clone();
for (k, v) in &other.env {
if let Some(existing) = merged.env.get(k) {
if existing != v {
merged.env.insert(k.clone(), BeamType::Any);
}
} else {
merged.env.insert(k.clone(), v.clone());
}
}
merged
}
}
#[derive(Debug, Clone)]
pub enum BeamInstr {
Label(u32),
FuncInfo {
module: String,
function: String,
arity: u32,
},
Call { arity: u32, label: u32 },
CallLast {
arity: u32,
label: u32,
deallocate: u32,
},
CallExt {
arity: u32,
destination: BeamExtFunc,
},
CallExtLast {
arity: u32,
destination: BeamExtFunc,
deallocate: u32,
},
CallFun { arity: u32 },
Move { src: BeamReg, dst: BeamReg },
PutTuple { arity: u32, dst: BeamReg },
Put(BeamVal),
GetTupleElement {
src: BeamReg,
index: u32,
dst: BeamReg,
},
SetTupleElement {
value: BeamVal,
tuple: BeamReg,
index: u32,
},
IsEq {
fail: u32,
lhs: BeamVal,
rhs: BeamVal,
},
IsEqExact {
fail: u32,
lhs: BeamVal,
rhs: BeamVal,
},
IsNe {
fail: u32,
lhs: BeamVal,
rhs: BeamVal,
},
IsLt {
fail: u32,
lhs: BeamVal,
rhs: BeamVal,
},
IsGe {
fail: u32,
lhs: BeamVal,
rhs: BeamVal,
},
IsInteger { fail: u32, arg: BeamVal },
IsFloat { fail: u32, arg: BeamVal },
IsAtom { fail: u32, arg: BeamVal },
IsNil { fail: u32, arg: BeamVal },
IsList { fail: u32, arg: BeamVal },
IsTuple { fail: u32, arg: BeamVal },
IsBinary { fail: u32, arg: BeamVal },
IsFunction { fail: u32, arg: BeamVal },
Jump(u32),
Return,
Send,
RemoveMessage,
LoopRec { fail: u32, dst: BeamReg },
Wait(u32),
WaitTimeout { fail: u32, timeout: BeamVal },
GcBif {
name: String,
fail: u32,
live: u32,
args: Vec<BeamVal>,
dst: BeamReg,
},
Bif0 { name: String, dst: BeamReg },
Allocate { stack_need: u32, live: u32 },
Deallocate(u32),
Init(BeamReg),
MakeFun2(u32),
GetList {
src: BeamReg,
head: BeamReg,
tail: BeamReg,
},
PutList {
head: BeamVal,
tail: BeamVal,
dst: BeamReg,
},
Raise { class: BeamVal, reason: BeamVal },
TryBegin { label: u32, reg: BeamReg },
TryEnd(BeamReg),
TryCase(BeamReg),
Comment(String),
}
#[derive(Debug, Clone)]
pub struct BeamExtFunc {
pub module: String,
pub function: String,
pub arity: u32,
}
#[derive(Debug, Clone)]
pub struct BeamBitSegment {
pub value: BeamExpr,
pub size: Option<BeamExpr>,
pub type_spec: String,
pub signed: bool,
pub endian: BeamEndian,
pub unit: Option<u8>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EtsType {
Set,
OrderedSet,
Bag,
DuplicateBag,
}
#[derive(Debug, Clone)]
pub enum BeamExpr {
LitInt(i64),
LitFloat(f64),
LitAtom(String),
LitString(String),
Nil,
Var(String),
Tuple(Vec<BeamExpr>),
Cons(Box<BeamExpr>, Box<BeamExpr>),
Map(Vec<(BeamExpr, BeamExpr)>),
MapUpdate(Box<BeamExpr>, Vec<(BeamExpr, BeamExpr)>),
Call {
module: Option<String>,
func: String,
args: Vec<BeamExpr>,
},
CallExt {
module: Box<BeamExpr>,
func: Box<BeamExpr>,
args: Vec<BeamExpr>,
},
Apply {
fun: Box<BeamExpr>,
args: Vec<BeamExpr>,
},
Case {
subject: Box<BeamExpr>,
clauses: Vec<BeamClause>,
},
Let {
var: String,
value: Box<BeamExpr>,
body: Box<BeamExpr>,
},
Letrec {
bindings: Vec<(String, Vec<String>, Box<BeamExpr>)>,
body: Box<BeamExpr>,
},
Fun {
params: Vec<String>,
body: Box<BeamExpr>,
arity: usize,
},
Primop { name: String, args: Vec<BeamExpr> },
Receive {
clauses: Vec<BeamClause>,
timeout: Option<Box<BeamExpr>>,
timeout_action: Option<Box<BeamExpr>>,
},
Try {
body: Box<BeamExpr>,
vars: Vec<String>,
handler: Box<BeamExpr>,
evars: Vec<String>,
catch_body: Box<BeamExpr>,
},
Seq(Box<BeamExpr>, Box<BeamExpr>),
Binary(Vec<BeamBitSegment>),
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GenServerSpec {
pub module_name: String,
pub state_doc: String,
pub handle_calls: Vec<(BeamPattern, BeamExpr)>,
pub handle_casts: Vec<(BeamPattern, BeamExpr)>,
pub handle_infos: Vec<(BeamPattern, BeamExpr)>,
pub init_state: BeamExpr,
pub terminate_body: Option<BeamExpr>,
}
impl GenServerSpec {
#[allow(dead_code)]
pub fn new(module_name: impl Into<String>, init_state: BeamExpr) -> Self {
GenServerSpec {
module_name: module_name.into(),
state_doc: "State".to_string(),
handle_calls: Vec::new(),
handle_casts: Vec::new(),
handle_infos: Vec::new(),
init_state,
terminate_body: None,
}
}
#[allow(dead_code)]
pub fn generate_module(&self) -> BeamModule {
let mut module = BeamModule::new(self.module_name.clone());
module.add_attribute("behaviour", "gen_server");
let mut start_link = BeamFunction::new("start_link", 0);
start_link.export();
module.add_function(start_link);
let mut init_fn = BeamFunction::new("init", 1);
init_fn.export();
module.add_function(init_fn);
let mut handle_call = BeamFunction::new("handle_call", 3);
handle_call.export();
module.add_function(handle_call);
let mut handle_cast = BeamFunction::new("handle_cast", 2);
handle_cast.export();
module.add_function(handle_cast);
let mut handle_info = BeamFunction::new("handle_info", 2);
handle_info.export();
module.add_function(handle_info);
let mut terminate = BeamFunction::new("terminate", 2);
terminate.export();
module.add_function(terminate);
module
}
#[allow(dead_code)]
pub fn emit_init(&self) -> String {
format!("init([]) ->\n {{ok, {}}}.\n", "State")
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BeamLinker {
pub(super) modules: Vec<BeamModule>,
pub(super) rename_map: HashMap<(String, String), String>,
pub(super) target_name: String,
}
impl BeamLinker {
#[allow(dead_code)]
pub fn new(target: impl Into<String>) -> Self {
BeamLinker {
modules: Vec::new(),
rename_map: HashMap::new(),
target_name: target.into(),
}
}
#[allow(dead_code)]
pub fn add_module(&mut self, module: BeamModule) {
self.modules.push(module);
}
#[allow(dead_code)]
pub fn rename(
&mut self,
src_module: impl Into<String>,
src_name: impl Into<String>,
new_name: impl Into<String>,
) {
self.rename_map
.insert((src_module.into(), src_name.into()), new_name.into());
}
#[allow(dead_code)]
pub fn link(self) -> BeamModule {
let mut result = BeamModule::new(self.target_name);
for module in self.modules {
for mut func in module.functions {
let key = (module.name.clone(), func.name.clone());
if let Some(new_name) = self.rename_map.get(&key) {
func.name = new_name.clone();
}
result.add_function(func);
}
for attr in module.attributes {
result.attributes.push(attr);
}
}
result
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BeamConstPool {
pub(super) literals: Vec<BeamExpr>,
pub(super) index_map: HashMap<String, u32>,
}
impl BeamConstPool {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn intern(&mut self, expr: BeamExpr, key: impl Into<String>) -> u32 {
let k = key.into();
if let Some(&idx) = self.index_map.get(&k) {
return idx;
}
let idx = self.literals.len() as u32;
self.literals.push(expr);
self.index_map.insert(k, idx);
idx
}
#[allow(dead_code)]
pub fn get(&self, idx: u32) -> Option<&BeamExpr> {
self.literals.get(idx as usize)
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.literals.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.literals.is_empty()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct XRegAllocator {
pub(super) next_x: u32,
pub(super) assignment: HashMap<String, u32>,
pub(super) max_x: u32,
}
impl XRegAllocator {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn alloc(&mut self, var: impl Into<String>) -> u32 {
let x = self.next_x;
let name = var.into();
self.assignment.insert(name, x);
if x > self.max_x {
self.max_x = x;
}
self.next_x += 1;
x
}
#[allow(dead_code)]
pub fn get(&self, var: &str) -> Option<u32> {
self.assignment.get(var).copied()
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.next_x = 0;
self.assignment.clear();
self.max_x = 0;
}
#[allow(dead_code)]
pub fn registers_used(&self) -> u32 {
if self.assignment.is_empty() {
0
} else {
self.max_x + 1
}
}
}
pub struct BeamBackend {
pub module: BeamModule,
pub(super) ctx: BeamEmitCtx,
pub(super) var_map: HashMap<u64, String>,
pub(super) next_label: u32,
}
impl BeamBackend {
pub fn new(module_name: impl Into<String>) -> Self {
BeamBackend {
module: BeamModule::new(module_name),
ctx: BeamEmitCtx::new(),
var_map: HashMap::new(),
next_label: 1,
}
}
pub(super) fn fresh_label(&mut self) -> u32 {
let l = self.next_label;
self.next_label += 1;
l
}
pub(super) fn beam_var(&mut self, id: LcnfVarId) -> String {
self.var_map
.entry(id.0)
.or_insert_with(|| format!("_X{}", id.0))
.clone()
}
pub fn emit_literal(&self, lit: &LcnfLit) -> BeamExpr {
match lit {
LcnfLit::Nat(n) => BeamExpr::LitInt(*n as i64),
LcnfLit::Str(s) => BeamExpr::LitString(s.clone()),
}
}
pub fn emit_arg(&mut self, arg: &LcnfArg) -> BeamExpr {
match arg {
LcnfArg::Var(id) => BeamExpr::Var(self.beam_var(*id)),
LcnfArg::Lit(lit) => self.emit_literal(lit),
LcnfArg::Erased => BeamExpr::LitAtom("erased".to_string()),
LcnfArg::Type(_) => BeamExpr::LitAtom("type".to_string()),
}
}
#[allow(clippy::too_many_arguments)]
pub fn emit_let_value(&mut self, val: &LcnfLetValue) -> BeamExpr {
match val {
LcnfLetValue::App(func, args) => {
let func_expr = self.emit_arg(func);
let arg_exprs: Vec<BeamExpr> = args.iter().map(|a| self.emit_arg(a)).collect();
BeamExpr::Apply {
fun: Box::new(func_expr),
args: arg_exprs,
}
}
LcnfLetValue::Proj(struct_name, idx, var) => {
let var_expr = BeamExpr::Var(self.beam_var(*var));
let beam_idx = (*idx as i64) + 2;
BeamExpr::Primop {
name: format!("element({}, {})", beam_idx, struct_name),
args: vec![var_expr],
}
}
LcnfLetValue::Ctor(name, _tag, args) => {
let mut elems = vec![BeamExpr::LitAtom(sanitize_atom(name))];
for a in args {
elems.push(self.emit_arg(a));
}
BeamExpr::Tuple(elems)
}
LcnfLetValue::Lit(lit) => self.emit_literal(lit),
LcnfLetValue::Erased => BeamExpr::LitAtom("erased".to_string()),
LcnfLetValue::FVar(id) => BeamExpr::Var(self.beam_var(*id)),
LcnfLetValue::Reset(var) => BeamExpr::Primop {
name: "reset".to_string(),
args: vec![BeamExpr::Var(self.beam_var(*var))],
},
LcnfLetValue::Reuse(slot, name, _tag, args) => {
let slot_expr = BeamExpr::Var(self.beam_var(*slot));
let mut elems = vec![BeamExpr::LitAtom(sanitize_atom(name)), slot_expr];
for a in args {
elems.push(self.emit_arg(a));
}
BeamExpr::Tuple(elems)
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn emit_expr(&mut self, expr: &LcnfExpr) -> BeamExpr {
match expr {
LcnfExpr::Let {
id, value, body, ..
} => {
let vname = self.beam_var(*id);
let val_expr = self.emit_let_value(value);
let body_expr = self.emit_expr(body);
BeamExpr::Let {
var: vname,
value: Box::new(val_expr),
body: Box::new(body_expr),
}
}
LcnfExpr::Case {
scrutinee,
alts,
default,
..
} => {
let subj_expr = BeamExpr::Var(self.beam_var(*scrutinee));
let mut clauses: Vec<BeamClause> =
alts.iter().map(|alt| self.emit_case_alt(alt)).collect();
if let Some(def_body) = default {
let def_expr = self.emit_expr(def_body);
clauses.push(BeamClause {
pattern: BeamPattern::Wildcard,
guard: None,
body: def_expr,
});
}
BeamExpr::Case {
subject: Box::new(subj_expr),
clauses,
}
}
LcnfExpr::Return(arg) => self.emit_arg(arg),
LcnfExpr::Unreachable => BeamExpr::Primop {
name: "error".to_string(),
args: vec![BeamExpr::Tuple(vec![
BeamExpr::LitAtom("error".to_string()),
BeamExpr::LitAtom("unreachable".to_string()),
])],
},
LcnfExpr::TailCall(func, args) => {
let func_expr = self.emit_arg(func);
let arg_exprs: Vec<BeamExpr> = args.iter().map(|a| self.emit_arg(a)).collect();
BeamExpr::Apply {
fun: Box::new(func_expr),
args: arg_exprs,
}
}
}
}
pub(super) fn emit_case_alt(&mut self, alt: &LcnfAlt) -> BeamClause {
let mut pats = vec![BeamPattern::LitAtom(sanitize_atom(&alt.ctor_name))];
for param in &alt.params {
pats.push(BeamPattern::Var(self.beam_var(param.id)));
}
let body = self.emit_expr(&alt.body);
BeamClause {
pattern: BeamPattern::Tuple(pats),
guard: None,
body,
}
}
pub fn emit_fun_decl(&mut self, decl: &LcnfFunDecl) -> BeamFunction {
let mut func = BeamFunction::new(sanitize_atom(&decl.name), decl.params.len());
func.params = decl.params.iter().map(|p| self.beam_var(p.id)).collect();
let patterns = func
.params
.iter()
.map(|p| BeamPattern::Var(p.clone()))
.collect();
let body = self.emit_expr(&decl.body);
func.add_clause(BeamClause {
pattern: BeamPattern::Tuple(patterns),
guard: None,
body,
});
self.lower_function(&mut func);
func
}
pub(super) fn lower_function(&mut self, func: &mut BeamFunction) {
let entry = self.fresh_label();
func.instrs.push(BeamInstr::Label(entry));
func.instrs.push(BeamInstr::FuncInfo {
module: self.module.name.clone(),
function: func.name.clone(),
arity: func.arity as u32,
});
let body_label = self.fresh_label();
func.instrs.push(BeamInstr::Label(body_label));
for (i, _param) in func.params.iter().enumerate() {
func.instrs.push(BeamInstr::Move {
src: BeamReg::X(i as u32),
dst: BeamReg::Y(i as u32),
});
}
func.instrs.push(BeamInstr::Return);
}
pub fn emit_core_erlang(&mut self) -> String {
let mut out = String::new();
out.push_str(&format!("module '{}'\n", self.module.name));
out.push_str(" [");
let exports = self.module.export_list();
for (i, exp) in exports.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&format!("'{}'", exp));
}
out.push_str("]\n");
out.push_str(" attributes []\n");
for func in &self.module.functions.clone() {
out.push_str(&self.emit_function_core_erlang(func));
}
out.push_str("end\n");
out
}
pub(super) fn emit_function_core_erlang(&self, func: &BeamFunction) -> String {
let mut out = String::new();
let params_str = func
.params
.iter()
.map(|p| format!("_{}", p))
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!(
"\n'{}'/{} =\n fun ({}) ->\n",
func.name, func.arity, params_str
));
out.push_str(" 'ok'\n");
out
}
pub fn emit_asm(&self) -> String {
let mut out = String::new();
out.push_str(&format!("{{module, '{}'}}.\n\n", self.module.name));
out.push_str(&format!(
"{{exports, [{}]}}.\n\n",
self.module
.exports
.iter()
.map(|(n, a)| format!("{{{}, {}}}", n, a))
.collect::<Vec<_>>()
.join(", ")
));
for func in &self.module.functions {
out.push_str(&format!("%% Function: {}/{}\n", func.name, func.arity));
for instr in &func.instrs {
out.push_str(&format!(" {}\n", emit_instr(instr)));
}
out.push('\n');
}
out
}
}
#[allow(dead_code)]
pub struct BeamPrinter {
pub(super) indent: usize,
pub(super) buf: String,
}
impl BeamPrinter {
#[allow(dead_code)]
pub fn new() -> Self {
BeamPrinter {
indent: 0,
buf: String::new(),
}
}
pub(super) fn pad(&self) -> String {
" ".repeat(self.indent)
}
pub(super) fn push(&mut self, s: &str) {
self.buf.push_str(s);
}
pub(super) fn newline(&mut self) {
self.buf.push('\n');
self.buf.push_str(&self.pad());
}
#[allow(dead_code)]
pub fn print_expr(&mut self, expr: &BeamExpr) {
match expr {
BeamExpr::LitInt(n) => self.push(&n.to_string()),
BeamExpr::LitFloat(v) => self.push(&format!("{:.6}", v)),
BeamExpr::LitAtom(a) => self.push(&format!("'{}'", a)),
BeamExpr::LitString(s) => self.push(&format!("\"{}\"", s)),
BeamExpr::Nil => self.push("[]"),
BeamExpr::Var(name) => self.push(name),
BeamExpr::Tuple(elems) => {
self.push("{");
for (i, e) in elems.iter().enumerate() {
if i > 0 {
self.push(", ");
}
self.print_expr(e);
}
self.push("}");
}
BeamExpr::Cons(head, tail) => {
self.push("[");
self.print_expr(head);
self.push(" | ");
self.print_expr(tail);
self.push("]");
}
BeamExpr::Let { var, value, body } => {
self.push(&format!("let {} =", var));
self.indent += 1;
self.newline();
self.print_expr(value);
self.indent -= 1;
self.newline();
self.push("in ");
self.print_expr(body);
}
BeamExpr::Apply { fun, args } => {
self.push("apply ");
self.print_expr(fun);
self.push("(");
for (i, a) in args.iter().enumerate() {
if i > 0 {
self.push(", ");
}
self.print_expr(a);
}
self.push(")");
}
BeamExpr::Call { module, func, args } => {
if let Some(m) = module {
self.push(&format!("call '{}':'{}' (", m, func));
} else {
self.push(&format!("call '{}' (", func));
}
for (i, a) in args.iter().enumerate() {
if i > 0 {
self.push(", ");
}
self.print_expr(a);
}
self.push(")");
}
BeamExpr::Primop { name, args } => {
self.push(&format!("primop '{}' (", name));
for (i, a) in args.iter().enumerate() {
if i > 0 {
self.push(", ");
}
self.print_expr(a);
}
self.push(")");
}
_ => self.push("..."),
}
}
#[allow(dead_code)]
pub fn finish(self) -> String {
self.buf
}
}
#[derive(Debug, Clone)]
pub struct BeamModule {
pub name: String,
pub attributes: Vec<(String, String)>,
pub functions: Vec<BeamFunction>,
pub exports: Vec<(String, usize)>,
pub on_load: Option<String>,
pub compile_info: HashMap<String, String>,
}
impl BeamModule {
pub fn new(name: impl Into<String>) -> Self {
BeamModule {
name: name.into(),
attributes: Vec::new(),
functions: Vec::new(),
exports: Vec::new(),
on_load: None,
compile_info: HashMap::new(),
}
}
pub fn add_function(&mut self, func: BeamFunction) {
if func.exported {
self.exports.push((func.name.clone(), func.arity));
}
self.functions.push(func);
}
pub fn add_attribute(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.attributes.push((key.into(), value.into()));
}
pub fn find_function(&self, name: &str, arity: usize) -> Option<&BeamFunction> {
self.functions
.iter()
.find(|f| f.name == name && f.arity == arity)
}
pub fn export_list(&self) -> Vec<String> {
self.exports
.iter()
.map(|(n, a)| format!("{}/{}", n, a))
.collect()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BeamReg {
X(u32),
Y(u32),
FR(u32),
}