alduin 0.0.1

WIP: A toy compiler backend
Documentation
use crate::compiler::graph::{BaseOp, Graph, Literal, NodeId, Type};

use super::X64Reg;

const GPR_ARGS: &'static [X64Reg] = &[
    X64Reg::RDI,
    X64Reg::RSI,
    X64Reg::RDX,
    X64Reg::RDX,
    X64Reg::R8,
    X64Reg::R9,
];

const FPR_ARGS: &'static [X64Reg] = &[
    X64Reg::XMM0,
    X64Reg::XMM1,
    X64Reg::XMM2,
    X64Reg::XMM3,
    X64Reg::XMM4,
    X64Reg::XMM5,
];

/// Coalesce registers if a node requries two values in the same register.
/// e.g. the return value and first input of the add instruction.
pub fn coalesce_live_intervals(g: &Graph, n: NodeId, mut coalesce: impl FnMut(NodeId, NodeId)) {
    let op = g[n].op::<BaseOp>();
    match op {
        BaseOp::Add
        | BaseOp::Sub
        | BaseOp::Mul
        | BaseOp::And
        | BaseOp::Or
        | BaseOp::Xor
        | BaseOp::DivS
        | BaseOp::DivU
        | BaseOp::FDiv => coalesce(n, g[n].inputs[0]),
        BaseOp::Move => coalesce(n, g[n].inputs[0]),
        BaseOp::ITruncU | BaseOp::SExt | BaseOp::ZExt => coalesce(n, g[n].inputs[0]),
        _ => {}
    }
}

/// Register pre-coloring
pub fn pre_assign_registers(g: &mut Graph, n: NodeId) {
    let op = g[n].op::<BaseOp>();
    match op {
        BaseOp::DivS | BaseOp::DivU => {
            // Set first input to RAX
            let mov = g.new_move_input_node(n, 0);
            g[mov].fixed_reg = Some(X64Reg::RAX.into());
            // Set return value to RAX
            g[n].fixed_reg = Some(X64Reg::RAX.into());
            g.new_move_output_node(n);
        }
        BaseOp::RemS | BaseOp::RemU => {
            // Set first input to RAX
            let mov = g.new_move_input_node(n, 0);
            g[mov].fixed_reg = Some(X64Reg::RAX.into());
            // Set return value to RDX
            g[n].fixed_reg = Some(X64Reg::RDX.into());
            g.new_move_output_node(n);
        }
        BaseOp::Shl | BaseOp::ShrS | BaseOp::ShrU | BaseOp::Rotl | BaseOp::Rotr => {
            // Set second input to RCX
            g.new_move_input_node(n, 0);
            let mov = g.new_move_input_node(n, 1);
            g[mov].fixed_reg = Some(X64Reg::RCX.into());
        }
        BaseOp::Return if g[n].inputs.len() > 0 => {
            let mov = g.new_move_input_node(n, 0);
            if g.signature.1.is_floating_point() {
                g[mov].fixed_reg = Some(X64Reg::XMM0.into());
            } else {
                g[mov].fixed_reg = Some(X64Reg::RAX.into());
            }
        }
        BaseOp::Call | BaseOp::CallIndirect => {
            let args_start_index = match op {
                // The first argument is the function pointer
                // The second argument is the context word
                BaseOp::CallIndirect if g[n].has_call_indirect_ctx => 2,
                // The first argument is the function pointer
                BaseOp::CallIndirect if !g[n].has_call_indirect_ctx => 1,
                _ => 0,
            };
            if op == BaseOp::CallIndirect && g[n].has_call_indirect_ctx {
                let mov = g.new_move_input_node(n, 1);
                g[mov].fixed_reg = Some(X64Reg::R15 as _);
            }
            // Set argument registers
            macro_rules! iter_args_with_type_filter {
                ($filter: expr, |$a:ident, $i:ident| $body: tt) => {{
                    let filter = $filter;
                    let mut j = 0;
                    let num_args = g[n].inputs[args_start_index..].len();
                    for i in 0..num_args {
                        let arg = g[n].inputs[args_start_index + i];
                        if !filter(g[arg].ty) {
                            continue;
                        }
                        {
                            let ($a, $i) = (arg, j);
                            $body;
                        }
                        j += 1;
                    }
                }};
            }
            iter_args_with_type_filter!(|ty: Type| !ty.is_floating_point(), |a, i| {
                if i < GPR_ARGS.len() {
                    let input_index = g[n].inputs.iter().position(|i| *i == a).unwrap();
                    let _mov = g.new_move_input_node(n, input_index);
                }
            });
            iter_args_with_type_filter!(|ty: Type| ty.is_floating_point(), |a, i| {
                if i < FPR_ARGS.len() {
                    let input_index = g[n].inputs.iter().position(|i| *i == a).unwrap();
                    let _mov = g.new_move_input_node(n, input_index);
                }
            });
            // Set fixed registers
            iter_args_with_type_filter!(|ty: Type| !ty.is_floating_point(), |a, i| {
                if i < GPR_ARGS.len() {
                    g[a].fixed_reg = Some(GPR_ARGS[i] as _);
                }
            });
            iter_args_with_type_filter!(|ty: Type| ty.is_floating_point(), |a, i| {
                if i < FPR_ARGS.len() {
                    g[a].fixed_reg = Some(FPR_ARGS[i] as _);
                }
            });
            if g[n].ty.is_floating_point() {
                g[n].fixed_reg = Some(X64Reg::XMM0.into());
            } else {
                g[n].fixed_reg = Some(X64Reg::RAX.into());
            }
            if g[n].ty != Type::Void {
                g.new_move_output_node(n);
            }
        }
        BaseOp::Param => {
            // let gpr_params = g.signature.0.iter().filter(|x| !x.is_floating_point()).cloned().collect::<Vec<_>>();
            let param_index = match g[n].literal.as_ref().unwrap() {
                Literal::ParamIndex(v) => *v,
                _ => unreachable!(),
            };
            let fixed_reg = if g[n].ty.is_floating_point() {
                let mut index = 0;
                for i in 0..param_index {
                    if g.signature.0[i].is_floating_point() {
                        index += 1;
                    }
                }
                FPR_ARGS.get(index)
            } else {
                let mut index = 0;
                for i in 0..param_index {
                    if !g.signature.0[i].is_floating_point() {
                        index += 1;
                    }
                }
                GPR_ARGS.get(index)
            };
            g[n].fixed_reg = fixed_reg.map(|r| Into::<usize>::into(*r));
            if g[n].fixed_reg.is_none() {
                let (mut gprs, mut fprs) = (0, 0);
                let mut stack_arg_index = 0;
                for i in 0..param_index {
                    if g.signature.0[i].is_floating_point() && fprs < FPR_ARGS.len() {
                        fprs += 1;
                    } else if !g.signature.0[i].is_floating_point() && gprs < GPR_ARGS.len() {
                        gprs += 1;
                    } else {
                        stack_arg_index += 1;
                    }
                }
                g[n].stack_arg_index = Some(stack_arg_index);
            }
            g.new_move_output_node(n);
        }
        _ => {}
    }
}