yulang-native 0.1.1

Native backend experiments for Yulang
Documentation
use std::fmt::Write as _;

use crate::abi::{NativeAbiBlock, NativeAbiFunction, NativeAbiModule, NativeAbiStmt};
use crate::control_ir::{NativeLiteral, NativeTerminator, ValueId};

pub fn format_abi_module(module: &NativeAbiModule) -> String {
    let mut out = String::new();
    for function in &module.functions {
        format_function(&mut out, "fn", function);
    }
    for root in &module.roots {
        format_function(&mut out, "root", root);
    }
    out
}

fn format_function(out: &mut String, kind: &str, function: &NativeAbiFunction) {
    let _ = writeln!(
        out,
        "{kind} {}({}) env_slots={}:",
        function.name,
        format_values(&function.params),
        function.environment_slots
    );
    for block in &function.blocks {
        format_block(out, block);
    }
    out.push('\n');
}

fn format_block(out: &mut String, block: &NativeAbiBlock) {
    let _ = writeln!(
        out,
        "  block{}({}):",
        block.id.0,
        format_values(&block.params)
    );
    for stmt in &block.stmts {
        let _ = writeln!(out, "    {}", format_stmt(stmt));
    }
    let _ = writeln!(out, "    {}", format_terminator(&block.terminator));
}

fn format_stmt(stmt: &NativeAbiStmt) -> String {
    match stmt {
        NativeAbiStmt::Literal { dest, literal } => {
            format!("{} = {}", format_value(*dest), format_literal(literal))
        }
        NativeAbiStmt::Primitive { dest, op, args } => format!(
            "{} = primitive {:?}({})",
            format_value(*dest),
            op,
            format_values(args)
        ),
        NativeAbiStmt::DirectCall { dest, target, args } => format!(
            "{} = call {target}({})",
            format_value(*dest),
            format_values(args)
        ),
        NativeAbiStmt::Tuple { dest, items } => {
            format!("{} = tuple({})", format_value(*dest), format_values(items))
        }
        NativeAbiStmt::Record { dest, base, fields } => format!(
            "{} = record{}({})",
            format_value(*dest),
            base.map(|base| format!(" ..{}", format_value(base)))
                .unwrap_or_default(),
            fields
                .iter()
                .map(|field| format!("{}: {}", field.name.0, format_value(field.value)))
                .collect::<Vec<_>>()
                .join(", ")
        ),
        NativeAbiStmt::RecordWithoutFields { dest, base, fields } => format!(
            "{} = record_without_fields {} - {{{}}}",
            format_value(*dest),
            format_value(*base),
            fields
                .iter()
                .map(|field| field.0.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        ),
        NativeAbiStmt::Variant { dest, tag, value } => format!(
            "{} = variant :{}({})",
            format_value(*dest),
            tag.0,
            value
                .map(format_value)
                .unwrap_or_else(|| "none".to_string())
        ),
        NativeAbiStmt::Select { dest, base, field } => format!(
            "{} = select {}.{}",
            format_value(*dest),
            format_value(*base),
            field.0
        ),
        NativeAbiStmt::TupleGet { dest, tuple, index } => format!(
            "{} = tuple_get {}[{index}]",
            format_value(*dest),
            format_value(*tuple)
        ),
        NativeAbiStmt::VariantTagEq { dest, variant, tag } => format!(
            "{} = variant_tag_eq {} :{}",
            format_value(*dest),
            format_value(*variant),
            tag.0
        ),
        NativeAbiStmt::VariantPayload { dest, variant } => format!(
            "{} = variant_payload {}",
            format_value(*dest),
            format_value(*variant)
        ),
        NativeAbiStmt::ValueEq { dest, left, right } => format!(
            "{} = value_eq {} {}",
            format_value(*dest),
            format_value(*left),
            format_value(*right)
        ),
        NativeAbiStmt::BoolAnd { dest, left, right } => format!(
            "{} = bool_and {} {}",
            format_value(*dest),
            format_value(*left),
            format_value(*right)
        ),
        NativeAbiStmt::LoadEnv { dest, slot } => {
            format!("{} = load_env[{slot}]", format_value(*dest))
        }
        NativeAbiStmt::AllocateClosure {
            dest,
            target,
            environment,
        } => format!(
            "{} = closure {target} env({})",
            format_value(*dest),
            format_values(environment)
        ),
        NativeAbiStmt::IndirectClosureCall { dest, callee, args } => format!(
            "{} = call_closure {}({})",
            format_value(*dest),
            format_value(*callee),
            format_values(args)
        ),
    }
}

fn format_terminator(terminator: &NativeTerminator) -> String {
    match terminator {
        NativeTerminator::Return(value) => format!("return {}", format_value(*value)),
        NativeTerminator::Jump { target, args } => {
            format!("jump block{}({})", target.0, format_values(args))
        }
        NativeTerminator::Branch {
            cond,
            then_block,
            else_block,
        } => format!(
            "branch {} block{} block{}",
            format_value(*cond),
            then_block.0,
            else_block.0
        ),
    }
}

fn format_literal(literal: &NativeLiteral) -> String {
    match literal {
        NativeLiteral::Int(value) => value.clone(),
        NativeLiteral::Float(value) => value.clone(),
        NativeLiteral::String(value) => format!("{value:?}"),
        NativeLiteral::Bool(value) => value.to_string(),
        NativeLiteral::Unit => "()".to_string(),
    }
}

fn format_values(values: &[ValueId]) -> String {
    values
        .iter()
        .map(|value| format_value(*value))
        .collect::<Vec<_>>()
        .join(", ")
}

fn format_value(value: ValueId) -> String {
    format!("%{}", value.0)
}

#[cfg(test)]
mod tests {
    use yulang_typed_ir as typed_ir;

    use crate::abi::{NativeAbiBlock, NativeAbiFunction, NativeAbiModule, NativeAbiStmt};
    use crate::control_ir::{BlockId, NativeLiteral, NativeTerminator, ValueId};

    use super::*;

    #[test]
    fn formats_primitive_abi_module() {
        let module = NativeAbiModule {
            functions: vec![NativeAbiFunction {
                name: "add".to_string(),
                params: vec![ValueId(0), ValueId(1)],
                environment_slots: 0,
                blocks: vec![NativeAbiBlock {
                    id: BlockId(0),
                    params: Vec::new(),
                    stmts: vec![NativeAbiStmt::Primitive {
                        dest: ValueId(2),
                        op: typed_ir::PrimitiveOp::IntAdd,
                        args: vec![ValueId(0), ValueId(1)],
                    }],
                    terminator: NativeTerminator::Return(ValueId(2)),
                }],
            }],
            roots: vec![NativeAbiFunction {
                name: "root".to_string(),
                params: Vec::new(),
                environment_slots: 0,
                blocks: vec![NativeAbiBlock {
                    id: BlockId(0),
                    params: Vec::new(),
                    stmts: vec![NativeAbiStmt::Literal {
                        dest: ValueId(0),
                        literal: NativeLiteral::Int("41".to_string()),
                    }],
                    terminator: NativeTerminator::Return(ValueId(0)),
                }],
            }],
        };

        assert_eq!(
            format_abi_module(&module),
            concat!(
                "fn add(%0, %1) env_slots=0:\n",
                "  block0():\n",
                "    %2 = primitive IntAdd(%0, %1)\n",
                "    return %2\n",
                "\n",
                "root root() env_slots=0:\n",
                "  block0():\n",
                "    %0 = 41\n",
                "    return %0\n",
                "\n",
            )
        );
    }

    #[test]
    fn formats_closure_abi_forms() {
        let module = NativeAbiModule {
            functions: Vec::new(),
            roots: vec![NativeAbiFunction {
                name: "root".to_string(),
                params: Vec::new(),
                environment_slots: 1,
                blocks: vec![NativeAbiBlock {
                    id: BlockId(0),
                    params: Vec::new(),
                    stmts: vec![
                        NativeAbiStmt::LoadEnv {
                            dest: ValueId(0),
                            slot: 0,
                        },
                        NativeAbiStmt::AllocateClosure {
                            dest: ValueId(1),
                            target: "root#lambda0".to_string(),
                            environment: vec![ValueId(0)],
                        },
                        NativeAbiStmt::IndirectClosureCall {
                            dest: ValueId(2),
                            callee: ValueId(1),
                            args: vec![ValueId(0)],
                        },
                    ],
                    terminator: NativeTerminator::Return(ValueId(2)),
                }],
            }],
        };

        assert_eq!(
            format_abi_module(&module),
            concat!(
                "root root() env_slots=1:\n",
                "  block0():\n",
                "    %0 = load_env[0]\n",
                "    %1 = closure root#lambda0 env(%0)\n",
                "    %2 = call_closure %1(%0)\n",
                "    return %2\n",
                "\n",
            )
        );
    }
}