wasm2glulx 0.1.2

Translate WebAssembly into Glulx
Documentation
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Copyright 2024 Daniel Fox Franke.

use glulx_asm::{concise::*, LoadOperand, StoreOperand};
use walrus::{
    ir::{self, ExtendedLoad},
    ValType,
};

use crate::common::Context;

use super::{
    loadstore::{gen_copies, Credits, Debts},
    toplevel::Frame,
};

pub fn gen_memory_init(
    ctx: &mut Context,
    _frame: &mut Frame,
    init_instr: &ir::MemoryInit,
    mut credits: Credits,
    mut debts: Debts,
) {
    credits.gen(ctx);
    let data_layout = ctx.layout.data(init_instr.data);
    ctx.rom_items.push(copy(imml(data_layout.addr), push()));
    ctx.rom_items
        .push(copy(derefl(data_layout.cur_size), push()));
    ctx.rom_items
        .push(call(imml(ctx.rt.memory_init), imm(5), discard()));
    debts.gen(ctx);
}

pub fn gen_memory_grow(
    ctx: &mut Context,
    _frame: &mut Frame,
    _grow_instr: &ir::MemoryGrow,
    mut credits: Credits,
    mut debts: Debts,
) {
    let arg = credits.pop();
    let out = debts.pop();
    credits.gen(ctx);
    ctx.rom_items
        .push(callfi(imml(ctx.rt.memory_grow), arg, out));
    debts.gen(ctx);
}

pub fn gen_memory_copy(
    ctx: &mut Context,
    _frame: &mut Frame,
    _copy_instr: &ir::MemoryCopy,
    mut credits: Credits,
    mut debts: Debts,
) {
    let n = credits.pop();
    let s = credits.pop();
    let d = credits.pop();
    credits.gen(ctx);
    ctx.rom_items
        .push(callfiii(imml(ctx.rt.memory_copy), n, s, d, discard()));
    debts.gen(ctx);
}

pub fn gen_memory_fill(
    ctx: &mut Context,
    _frame: &mut Frame,
    _fill_instr: &ir::MemoryFill,
    mut credits: Credits,
    mut debts: Debts,
) {
    let n = credits.pop();
    let val = credits.pop();
    let d = credits.pop();
    credits.gen(ctx);
    ctx.rom_items
        .push(callfiii(imml(ctx.rt.memory_fill), n, val, d, discard()));
    debts.gen(ctx);
}

pub fn gen_memory_size(
    ctx: &mut Context,
    _frame: &mut Frame,
    _size_instr: &ir::MemorySize,
    mut credits: Credits,
    mut debts: Debts,
) {
    let out = debts.pop();
    credits.gen(ctx);
    ctx.rom_items
        .push(ushiftr(derefl(ctx.layout.memory().cur_size), imm(16), out));
    debts.gen(ctx);
}

pub fn gen_data_drop(
    ctx: &mut Context,
    _frame: &mut Frame,
    drop_instr: &ir::DataDrop,
    credits: Credits,
    debts: Debts,
) {
    ctx.rom_items.push(copy(
        imm(0),
        storel(ctx.layout.data(drop_instr.data).cur_size),
    ));
    gen_copies(ctx, credits, debts);
}

pub fn gen_load(
    ctx: &mut Context,
    frame: &mut Frame,
    load_instr: &ir::Load,
    mut credits: Credits,
    mut debts: Debts,
) {
    let offset = load_instr.arg.offset;

    match load_instr.kind {
        ir::LoadKind::F32 | ir::LoadKind::I32 { atomic: _ } => {
            let addr = credits.pop();
            let out = debts.pop();
            credits.gen(ctx);
            ctx.rom_items
                .push(callfii(imml(ctx.rt.memload32), uimm(offset), addr, out));
            debts.gen(ctx);
        }
        ir::LoadKind::F64 | ir::LoadKind::I64 { atomic: _ } => {
            let addr = credits.pop();
            credits.gen(ctx);
            ctx.rom_items
                .push(callfii(imml(ctx.rt.memload64), uimm(offset), addr, push()));
            gen_copies(ctx, Credits::from_returns(ctx, &[ValType::I64]), debts);
        }
        ir::LoadKind::V128 => {
            credits.gen(ctx);
            ctx.errors
                .push(crate::CompilationError::UnsupportedInstruction {
                    function: frame.function_name.map(|s| s.to_owned()),
                    instr: "v128.load",
                });
            debts.gen(ctx);
        }
        ir::LoadKind::I32_8 { kind } => {
            let addr = credits.pop();
            let out = debts.pop();
            credits.gen(ctx);
            match kind {
                ExtendedLoad::SignExtend => {
                    ctx.rom_items
                        .push(callfii(imml(ctx.rt.memload8), uimm(offset), addr, push()));
                    ctx.rom_items.push(sexb(pop(), out));
                }
                ExtendedLoad::ZeroExtend | ExtendedLoad::ZeroExtendAtomic => {
                    ctx.rom_items
                        .push(callfii(imml(ctx.rt.memload8), uimm(offset), addr, out));
                }
            }
            debts.gen(ctx);
        }
        ir::LoadKind::I32_16 { kind } => {
            let addr = credits.pop();
            let out = debts.pop();
            credits.gen(ctx);
            match kind {
                ExtendedLoad::SignExtend => {
                    ctx.rom_items
                        .push(callfii(imml(ctx.rt.memload16), uimm(offset), addr, push()));
                    ctx.rom_items.push(sexs(pop(), out));
                }
                ExtendedLoad::ZeroExtend | ExtendedLoad::ZeroExtendAtomic => {
                    ctx.rom_items
                        .push(callfii(imml(ctx.rt.memload16), uimm(offset), addr, out));
                }
            }
            debts.gen(ctx);
        }
        ir::LoadKind::I64_8 { kind } => {
            let addr = credits.pop();
            let out_hi = debts.pop();
            credits.gen(ctx);
            ctx.rom_items
                .push(callfii(imml(ctx.rt.memload8), uimm(offset), addr, push()));

            match kind {
                ExtendedLoad::SignExtend => {
                    ctx.rom_items.push(sexb(pop(), push()));
                    ctx.rom_items.push(stkpeek(imm(0), push()));
                    ctx.rom_items.push(sshiftr(pop(), imm(31), out_hi));
                }
                ExtendedLoad::ZeroExtend | ExtendedLoad::ZeroExtendAtomic => {
                    if !matches!(out_hi, StoreOperand::Discard) {
                        ctx.rom_items.push(copy(imm(0), out_hi));
                    }
                }
            }
            debts.gen(ctx);
        }
        ir::LoadKind::I64_16 { kind } => {
            let addr = credits.pop();
            let out_hi = debts.pop();
            credits.gen(ctx);
            ctx.rom_items
                .push(callfii(imml(ctx.rt.memload16), uimm(offset), addr, push()));

            match kind {
                ExtendedLoad::SignExtend => {
                    ctx.rom_items.push(sexs(pop(), push()));
                    ctx.rom_items.push(stkpeek(imm(0), push()));
                    ctx.rom_items.push(sshiftr(pop(), imm(31), out_hi));
                }
                ExtendedLoad::ZeroExtend | ExtendedLoad::ZeroExtendAtomic => {
                    if !matches!(out_hi, StoreOperand::Discard) {
                        ctx.rom_items.push(copy(imm(0), out_hi));
                    }
                }
            }
            debts.gen(ctx);
        }
        ir::LoadKind::I64_32 { kind } => {
            let addr = credits.pop();
            let out_hi = debts.pop();
            credits.gen(ctx);
            ctx.rom_items
                .push(callfii(imml(ctx.rt.memload32), uimm(offset), addr, push()));

            match kind {
                ExtendedLoad::SignExtend => {
                    ctx.rom_items.push(stkpeek(imm(0), push()));
                    ctx.rom_items.push(sshiftr(pop(), imm(31), out_hi));
                }
                ExtendedLoad::ZeroExtend | ExtendedLoad::ZeroExtendAtomic => {
                    if !matches!(out_hi, StoreOperand::Discard) {
                        ctx.rom_items.push(copy(imm(0), out_hi));
                    }
                }
            }
            debts.gen(ctx);
        }
    }
}

pub fn gen_store(
    ctx: &mut Context,
    frame: &mut Frame,
    store_instr: &ir::Store,
    mut credits: Credits,
    mut debts: Debts,
) {
    let offset = store_instr.arg.offset;
    match store_instr.kind {
        ir::StoreKind::F32 | ir::StoreKind::I32 { atomic: _ } => {
            let val = credits.pop();
            let addr = credits.pop();
            credits.gen(ctx);
            ctx.rom_items.push(callfiii(
                imml(ctx.rt.memstore32),
                uimm(offset),
                val,
                addr,
                discard(),
            ));
            debts.gen(ctx);
        }
        ir::StoreKind::F64 | ir::StoreKind::I64 { atomic: _ } => {
            credits.gen(ctx);
            ctx.rom_items.push(copy(uimm(offset), push()));
            ctx.rom_items
                .push(call(imml(ctx.rt.memstore64), imm(4), discard()));
            debts.gen(ctx);
        }
        ir::StoreKind::V128 => {
            credits.gen(ctx);
            ctx.errors
                .push(crate::CompilationError::UnsupportedInstruction {
                    function: frame.function_name.map(|s| s.to_owned()),
                    instr: "v128.store",
                });
            debts.gen(ctx);
        }
        ir::StoreKind::I32_8 { atomic: _ } => {
            let val = credits.pop();
            let addr = credits.pop();
            credits.gen(ctx);
            ctx.rom_items.push(callfiii(
                imml(ctx.rt.memstore8),
                uimm(offset),
                val,
                addr,
                discard(),
            ));
            debts.gen(ctx);
        }
        ir::StoreKind::I32_16 { atomic: _ } => {
            let val = credits.pop();
            let addr = credits.pop();
            credits.gen(ctx);
            ctx.rom_items.push(callfiii(
                imml(ctx.rt.memstore16),
                uimm(offset),
                val,
                addr,
                discard(),
            ));
            debts.gen(ctx);
        }
        ir::StoreKind::I64_8 { atomic: _ } => {
            let val_hi = credits.pop();
            let val_lo = credits.pop();
            let addr = credits.pop();
            credits.gen(ctx);

            if matches!(val_hi, LoadOperand::Pop) {
                ctx.rom_items.push(copy(pop(), discard()));
            }
            ctx.rom_items.push(callfiii(
                imml(ctx.rt.memstore8),
                uimm(offset),
                val_lo,
                addr,
                discard(),
            ));
            debts.gen(ctx);
        }
        ir::StoreKind::I64_16 { atomic: _ } => {
            let val_hi = credits.pop();
            let val_lo = credits.pop();
            let addr = credits.pop();
            credits.gen(ctx);

            if matches!(val_hi, LoadOperand::Pop) {
                ctx.rom_items.push(copy(pop(), discard()));
            }
            ctx.rom_items.push(callfiii(
                imml(ctx.rt.memstore16),
                uimm(offset),
                val_lo,
                addr,
                discard(),
            ));
            debts.gen(ctx);
        }
        ir::StoreKind::I64_32 { atomic: _ } => {
            let val_hi = credits.pop();
            let val_lo = credits.pop();
            let addr = credits.pop();
            credits.gen(ctx);

            if matches!(val_hi, LoadOperand::Pop) {
                ctx.rom_items.push(copy(pop(), discard()));
            }
            ctx.rom_items.push(callfiii(
                imml(ctx.rt.memstore32),
                uimm(offset),
                val_lo,
                addr,
                discard(),
            ));
            debts.gen(ctx);
        }
    }
}