use super::*;
pub(super) fn emit_str_alloc(ctx: &mut WasmGenCtx, len_i32: u32) -> (u32, u32) {
let padded = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(len_i32));
ctx.emit(Instruction::I32Const(7));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(-8i32)); ctx.emit(Instruction::I32And);
ctx.emit(Instruction::LocalSet(padded));
let payload = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::LocalGet(padded));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(payload));
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc_dynamic(ctx, payload, ptr, "string");
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::LocalGet(len_i32));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
let content_base = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(content_base));
(ptr, content_base)
}
pub(super) fn emit_str_count(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&str:count expects 1 arg".into());
}
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(0))); Ok(())
}
pub(super) fn emit_str_empty(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("str-empty? expects 1 arg".into());
}
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::Select);
Ok(())
}
pub(super) fn emit_str_concat(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&str:concat expects 2 args".into());
}
let ptr_a = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_b = emit_ptr_to_i32(ctx, &args[1])?;
let len_a = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(len_a));
let len_b = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(len_b));
let len_c = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(len_a));
ctx.emit(Instruction::LocalGet(len_b));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(len_c));
let (ptr_c, dst_c) = emit_str_alloc(ctx, len_c);
let src_a = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src_a));
ctx.emit(Instruction::LocalGet(dst_c));
ctx.emit(Instruction::LocalGet(src_a));
ctx.emit(Instruction::LocalGet(len_a));
ctx.emit(Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 });
let dst_b = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(dst_c));
ctx.emit(Instruction::LocalGet(len_a));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(dst_b));
let src_b = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src_b));
ctx.emit(Instruction::LocalGet(dst_b));
ctx.emit(Instruction::LocalGet(src_b));
ctx.emit(Instruction::LocalGet(len_b));
ctx.emit(Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 });
ctx.emit(Instruction::LocalGet(ptr_c));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_str_nth(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&str:nth expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let idx = ctx.alloc_local_typed(ValType::I32);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(idx));
let byte_len = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64Load(mem_arg_i32(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(byte_len));
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::LocalGet(byte_len));
ctx.emit(Instruction::I32LtU);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(wasm_encoder::ValType::F64)));
let byte_val = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Load8U(mem_arg_byte(0)));
ctx.emit(Instruction::LocalSet(byte_val));
let one = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(one));
let (char_ptr, char_base) = emit_str_alloc(ctx, one);
ctx.emit(Instruction::LocalGet(char_base));
ctx.emit(Instruction::LocalGet(byte_val));
ctx.emit(Instruction::I32Store8(mem_arg_byte(0)));
ctx.emit(Instruction::LocalGet(char_ptr));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0)); ctx.emit(Instruction::End);
Ok(())
}
pub(super) fn emit_str_nth_from_locals(ctx: &mut WasmGenCtx, ptr_f64: u32, idx_f64: u32) {
let ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(ptr));
let idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(idx_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(idx));
let byte_len = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64Load(mem_arg_i32(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(byte_len));
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::LocalGet(byte_len));
ctx.emit(Instruction::I32LtU);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(wasm_encoder::ValType::F64)));
let byte_val = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Load8U(mem_arg_byte(0)));
ctx.emit(Instruction::LocalSet(byte_val));
let one = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(one));
let (char_ptr, char_base) = emit_str_alloc(ctx, one);
ctx.emit(Instruction::LocalGet(char_base));
ctx.emit(Instruction::LocalGet(byte_val));
ctx.emit(Instruction::I32Store8(mem_arg_byte(0)));
ctx.emit(Instruction::LocalGet(char_ptr));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0)); ctx.emit(Instruction::End);
}
pub(super) fn emit_str_first(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&str:first expects 1 arg".into());
}
let zero = crate::calcit::Calcit::Number(0.0);
let new_args = [args[0].clone(), zero];
emit_str_nth(ctx, &new_args)
}
pub(super) fn emit_str_rest(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&str:rest expects 1 arg".into());
}
let ptr_a = emit_ptr_to_i32(ctx, &args[0])?;
let old_len = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(old_len));
let new_len = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(old_len));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(new_len));
let (ptr_b, dst_b) = emit_str_alloc(ctx, new_len);
let src = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::I32Const(9));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src));
ctx.emit(Instruction::LocalGet(dst_b));
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::LocalGet(new_len));
ctx.emit(Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 });
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_str_slice(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 3 && args.len() != 2 {
return Err("&str:slice expects 2 or 3 args (str, start[, end])".into());
}
let ptr_a = emit_ptr_to_i32(ctx, &args[0])?;
let start = ctx.alloc_local_typed(ValType::I32);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(start));
let end = ctx.alloc_local_typed(ValType::I32);
if args.len() == 3 {
emit_expr(ctx, &args[2])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(end));
} else {
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(end));
}
let new_len = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(end));
ctx.emit(Instruction::LocalGet(start));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(new_len));
let (ptr_b, dst_b) = emit_str_alloc(ctx, new_len);
let src = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(start));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src));
ctx.emit(Instruction::LocalGet(dst_b));
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::LocalGet(new_len));
ctx.emit(Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 });
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_str_compare(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&str:compare expects 2 args".into());
}
let ptr_a = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_b = emit_ptr_to_i32(ctx, &args[1])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_compare")
.ok_or_else(|| "runtime helper __rt_str_compare not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_str_contains(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&str:contains? expects 2 args (str, idx)".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32GtU);
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_str_find_index(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&str:find-index expects 2 args (haystack, needle)".into());
}
let ptr_h = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_n = emit_ptr_to_i32(ctx, &args[1])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_find_index")
.ok_or_else(|| "runtime helper __rt_str_find_index not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_h));
ctx.emit(Instruction::LocalGet(ptr_n));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_str_includes(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&str:includes? expects 2 args (haystack, needle)".into());
}
let ptr_h = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_n = emit_ptr_to_i32(ctx, &args[1])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_find_index")
.ok_or_else(|| "runtime helper __rt_str_find_index not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_h));
ctx.emit(Instruction::LocalGet(ptr_n));
ctx.emit(Instruction::Call(fn_idx));
ctx.emit(Instruction::F64Const(Ieee64::from(0.0f64)));
ctx.emit(Instruction::F64Ge);
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_str_starts_with(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("starts-with? expects 2 args (string, prefix)".into());
}
let ptr_s = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_p = emit_ptr_to_i32(ctx, &args[1])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_starts_with")
.ok_or_else(|| "runtime helper __rt_str_starts_with not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_s));
ctx.emit(Instruction::LocalGet(ptr_p));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_str_ends_with(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("ends-with? expects 2 args (string, suffix)".into());
}
let ptr_s = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_suf = emit_ptr_to_i32(ctx, &args[1])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_ends_with")
.ok_or_else(|| "runtime helper __rt_str_ends_with not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_s));
ctx.emit(Instruction::LocalGet(ptr_suf));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_turn_string(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("turn-string expects 1 arg".into());
}
let f64_to_str_idx = *ctx
.runtime_fn_index
.get("__rt_f64_to_str")
.ok_or_else(|| "runtime helper __rt_f64_to_str not found".to_string())?;
let string_type_tag = *ctx.tag_index.get("string").ok_or("string tag missing")? as i32;
let v = ctx.alloc_local(); let result = ctx.alloc_local(); let v_i32 = ctx.alloc_local_typed(ValType::I32); let raw_base = ctx.alloc_local_typed(ValType::I32);
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(v));
let heap_min = (HEAP_BASE as f64) + 8.0;
ctx.emit(Instruction::LocalGet(v));
ctx.emit(f64_const(heap_min));
ctx.emit(Instruction::F64Ge);
ctx.emit(Instruction::LocalGet(v));
ctx.emit(Instruction::F64Floor);
ctx.emit(Instruction::LocalGet(v));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::I32And);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
ctx.emit(Instruction::LocalGet(v));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(v_i32));
ctx.emit(Instruction::LocalGet(v_i32));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(raw_base));
ctx.emit(Instruction::LocalGet(raw_base));
ctx.emit(Instruction::I32Load(mem_arg_i32(0)));
ctx.emit(Instruction::I32Const(HEAP_MAGIC));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::LocalGet(raw_base));
ctx.emit(Instruction::I32Load(mem_arg_i32(4)));
ctx.emit(Instruction::I32Const(string_type_tag));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::I32And);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(v));
ctx.emit(Instruction::LocalSet(result));
ctx.emit(Instruction::End); }
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(result));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
ctx.emit(Instruction::LocalGet(v));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let raw = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::LocalTee(raw));
ctx.emit(Instruction::I32Const(HEAP_MAGIC));
ctx.emit(Instruction::I32Store(mem_arg_i32(0)));
ctx.emit(Instruction::LocalGet(raw));
ctx.emit(Instruction::I32Const(string_type_tag));
ctx.emit(Instruction::I32Store(mem_arg_i32(4)));
ctx.emit(Instruction::LocalGet(raw));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Const(wasm_encoder::Ieee64::from(0.0f64)));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(raw));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::GlobalSet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::LocalGet(raw));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::LocalSet(result));
}
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(v));
ctx.emit(Instruction::Call(f64_to_str_idx));
ctx.emit(Instruction::LocalSet(result));
ctx.emit(Instruction::End);
}
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(result));
Ok(())
}
pub(super) fn emit_format_to_lisp(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
for arg in args {
emit_expr(ctx, arg)?;
ctx.emit(Instruction::Drop);
}
ctx.emit(f64_const(0.0));
Ok(())
}
pub(super) fn emit_str_pad_left(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 3 {
return Err("&str:pad-left expects 3 args (str, size, pattern)".into());
}
let ptr_s = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_p = emit_ptr_to_i32(ctx, &args[2])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_pad_left")
.ok_or_else(|| "runtime helper __rt_str_pad_left not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_s));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalGet(ptr_p));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_str_pad_right(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 3 {
return Err("&str:pad-right expects 3 args (str, size, pattern)".into());
}
let ptr_s = emit_ptr_to_i32(ctx, &args[0])?;
let ptr_p = emit_ptr_to_i32(ctx, &args[2])?;
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_str_pad_right")
.ok_or_else(|| "runtime helper __rt_str_pad_right not found".to_string())?;
ctx.emit(Instruction::LocalGet(ptr_s));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalGet(ptr_p));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
fn concat_two_i32_ptrs(ctx: &mut WasmGenCtx, ptr_a: u32, ptr_b: u32) {
let len_a = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(len_a));
let len_b = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(len_b));
let len_c = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(len_a));
ctx.emit(Instruction::LocalGet(len_b));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(len_c));
let (ptr_c, dst_c) = emit_str_alloc(ctx, len_c);
let src_a = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_a));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src_a));
ctx.emit(Instruction::LocalGet(dst_c));
ctx.emit(Instruction::LocalGet(src_a));
ctx.emit(Instruction::LocalGet(len_a));
ctx.emit(Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 });
let dst_b_off = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(dst_c));
ctx.emit(Instruction::LocalGet(len_a));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(dst_b_off));
let src_b = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_b));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src_b));
ctx.emit(Instruction::LocalGet(dst_b_off));
ctx.emit(Instruction::LocalGet(src_b));
ctx.emit(Instruction::LocalGet(len_b));
ctx.emit(Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 });
ctx.emit(Instruction::LocalGet(ptr_c));
ctx.emit(Instruction::F64ConvertI32U);
}
pub(super) fn emit_turn_string_from_local(ctx: &mut WasmGenCtx, v_local: u32) -> u32 {
use crate::calcit::{CalcitLocal, CalcitSymbolInfo, DYNAMIC_TYPE};
use std::sync::Arc;
let sym: Arc<str> = Arc::from("__ts_arg__");
let dummy_info = Arc::new(CalcitSymbolInfo {
at_ns: Arc::from("wasm"),
at_def: Arc::from("__ts"),
});
let prev = ctx.locals.insert(sym.as_ref().to_owned(), v_local);
let expr = crate::calcit::Calcit::Local(CalcitLocal {
idx: 0,
sym: sym.clone(),
info: dummy_info,
location: None,
type_info: DYNAMIC_TYPE.clone(),
});
let _ = emit_turn_string(ctx, std::slice::from_ref(&expr));
match prev {
Some(v) => {
ctx.locals.insert(sym.as_ref().to_owned(), v);
}
None => {
ctx.locals.remove(sym.as_ref());
}
}
let str_i32 = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(str_i32));
str_i32
}
pub(super) fn emit_str_variadic(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.is_empty() {
let zero_local = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(zero_local));
let (ptr, _) = emit_str_alloc(ctx, zero_local);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64ConvertI32U);
return Ok(());
}
emit_turn_string(ctx, std::slice::from_ref(&args[0]))?;
let acc_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(acc_ptr));
for arg in &args[1..] {
emit_turn_string(ctx, std::slice::from_ref(arg))?;
let next_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(next_ptr));
concat_two_i32_ptrs(ctx, acc_ptr, next_ptr);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(acc_ptr));
}
ctx.emit(Instruction::LocalGet(acc_ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_str_spaced(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.is_empty() {
let zero_local = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(zero_local));
let (ptr, _) = emit_str_alloc(ctx, zero_local);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64ConvertI32U);
return Ok(());
}
let one_local = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(one_local));
let (space_ptr, space_content) = emit_str_alloc(ctx, one_local);
ctx.emit(Instruction::LocalGet(space_content));
ctx.emit(Instruction::I32Const(0x20));
ctx.emit(Instruction::I32Store8(super::mem_arg_byte(0)));
emit_turn_string(ctx, std::slice::from_ref(&args[0]))?;
let acc_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(acc_ptr));
for arg in &args[1..] {
concat_two_i32_ptrs(ctx, acc_ptr, space_ptr);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(acc_ptr));
emit_turn_string(ctx, std::slice::from_ref(arg))?;
let next_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(next_ptr));
concat_two_i32_ptrs(ctx, acc_ptr, next_ptr);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(acc_ptr));
}
ctx.emit(Instruction::LocalGet(acc_ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn build_str_new_fn(str_tag_id: i32) -> CompiledFn {
let instructions = vec![
Instruction::LocalGet(1),
Instruction::I32Const(7),
Instruction::I32Add,
Instruction::I32Const(-8i32),
Instruction::I32And,
Instruction::LocalSet(2), Instruction::I32Const(8),
Instruction::LocalGet(2),
Instruction::I32Add,
Instruction::LocalSet(3), Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(HEAP_MAGIC),
Instruction::I32Store(mem_arg_i32(0)),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(str_tag_id),
Instruction::I32Store(mem_arg_i32(4)),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalTee(4), Instruction::LocalGet(3), Instruction::I32Add,
Instruction::GlobalSet(HEAP_PTR_GLOBAL),
Instruction::LocalGet(4),
Instruction::LocalGet(1), Instruction::F64ConvertI32U,
Instruction::F64Store(mem_arg_f64(0)),
Instruction::LocalGet(4),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalGet(0), Instruction::LocalGet(1), Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 },
Instruction::LocalGet(4),
Instruction::F64ConvertI32U,
];
CompiledFn {
export_name: Some("__str_new".to_string()),
params: vec![ValType::I32, ValType::I32],
results: vec![ValType::F64],
locals: vec![ValType::I32, ValType::I32, ValType::I32], instructions,
}
}
pub(super) fn build_str_pad_left_fn(str_tag_id: i32) -> CompiledFn {
let instructions = vec![
Instruction::LocalGet(0),
Instruction::F64Load(mem_arg_f64(0)),
Instruction::I32TruncF64U,
Instruction::LocalSet(3),
Instruction::Block(wasm_encoder::BlockType::Result(ValType::F64)),
Instruction::LocalGet(3),
Instruction::LocalGet(1),
Instruction::I32GeU,
Instruction::If(wasm_encoder::BlockType::Empty),
Instruction::LocalGet(0),
Instruction::F64ConvertI32U,
Instruction::Br(1), Instruction::End,
Instruction::LocalGet(1),
Instruction::LocalGet(3),
Instruction::I32Sub,
Instruction::LocalSet(4),
Instruction::LocalGet(2),
Instruction::F64Load(mem_arg_f64(0)),
Instruction::I32TruncF64U,
Instruction::LocalSet(5),
Instruction::LocalGet(5),
Instruction::I32Eqz,
Instruction::If(wasm_encoder::BlockType::Empty),
Instruction::LocalGet(0),
Instruction::F64ConvertI32U,
Instruction::Br(1),
Instruction::End,
Instruction::LocalGet(1),
Instruction::I32Const(7),
Instruction::I32Add,
Instruction::I32Const(-8i32),
Instruction::I32And,
Instruction::LocalSet(6),
Instruction::I32Const(8),
Instruction::LocalGet(6),
Instruction::I32Add,
Instruction::LocalSet(7),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(HEAP_MAGIC),
Instruction::I32Store(mem_arg_i32(0)),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(str_tag_id),
Instruction::I32Store(mem_arg_i32(4)),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalSet(8), Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(8),
Instruction::LocalGet(7),
Instruction::I32Add,
Instruction::I32Add,
Instruction::GlobalSet(HEAP_PTR_GLOBAL),
Instruction::LocalGet(8),
Instruction::LocalGet(1),
Instruction::F64ConvertI32U,
Instruction::F64Store(mem_arg_f64(0)),
Instruction::LocalGet(8),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalSet(9),
Instruction::I32Const(0),
Instruction::LocalSet(10),
Instruction::I32Const(0),
Instruction::LocalSet(11),
Instruction::Block(wasm_encoder::BlockType::Empty), Instruction::Loop(wasm_encoder::BlockType::Empty), Instruction::LocalGet(10),
Instruction::LocalGet(4),
Instruction::I32GeU,
Instruction::BrIf(1),
Instruction::LocalGet(11),
Instruction::LocalGet(5),
Instruction::I32GeU,
Instruction::If(wasm_encoder::BlockType::Empty),
Instruction::I32Const(0),
Instruction::LocalSet(11),
Instruction::End,
Instruction::LocalGet(2),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalGet(11),
Instruction::I32Add,
Instruction::I32Load8U(mem_arg_byte(0)),
Instruction::LocalSet(12),
Instruction::LocalGet(9),
Instruction::LocalGet(10),
Instruction::I32Add,
Instruction::LocalGet(12),
Instruction::I32Store8(mem_arg_byte(0)),
Instruction::LocalGet(10),
Instruction::I32Const(1),
Instruction::I32Add,
Instruction::LocalSet(10),
Instruction::LocalGet(11),
Instruction::I32Const(1),
Instruction::I32Add,
Instruction::LocalSet(11),
Instruction::Br(0), Instruction::End, Instruction::End, Instruction::LocalGet(9),
Instruction::LocalGet(4),
Instruction::I32Add, Instruction::LocalGet(0),
Instruction::I32Const(8),
Instruction::I32Add, Instruction::LocalGet(3), Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 },
Instruction::LocalGet(8),
Instruction::F64ConvertI32U,
Instruction::End, ];
CompiledFn {
export_name: None,
params: vec![ValType::I32, ValType::I32, ValType::I32],
results: vec![ValType::F64],
locals: vec![
ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ],
instructions,
}
}
pub(super) fn build_str_pad_right_fn(str_tag_id: i32) -> CompiledFn {
let instructions = vec![
Instruction::LocalGet(0),
Instruction::F64Load(mem_arg_f64(0)),
Instruction::I32TruncF64U,
Instruction::LocalSet(3),
Instruction::Block(wasm_encoder::BlockType::Result(ValType::F64)),
Instruction::LocalGet(3),
Instruction::LocalGet(1),
Instruction::I32GeU,
Instruction::If(wasm_encoder::BlockType::Empty),
Instruction::LocalGet(0),
Instruction::F64ConvertI32U,
Instruction::Br(1),
Instruction::End,
Instruction::LocalGet(1),
Instruction::LocalGet(3),
Instruction::I32Sub,
Instruction::LocalSet(4),
Instruction::LocalGet(2),
Instruction::F64Load(mem_arg_f64(0)),
Instruction::I32TruncF64U,
Instruction::LocalSet(5),
Instruction::LocalGet(5),
Instruction::I32Eqz,
Instruction::If(wasm_encoder::BlockType::Empty),
Instruction::LocalGet(0),
Instruction::F64ConvertI32U,
Instruction::Br(1),
Instruction::End,
Instruction::LocalGet(1),
Instruction::I32Const(7),
Instruction::I32Add,
Instruction::I32Const(-8i32),
Instruction::I32And,
Instruction::LocalSet(6),
Instruction::I32Const(8),
Instruction::LocalGet(6),
Instruction::I32Add,
Instruction::LocalSet(7),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(HEAP_MAGIC),
Instruction::I32Store(mem_arg_i32(0)),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(str_tag_id),
Instruction::I32Store(mem_arg_i32(4)),
Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalSet(8), Instruction::GlobalGet(HEAP_PTR_GLOBAL),
Instruction::I32Const(8),
Instruction::LocalGet(7),
Instruction::I32Add,
Instruction::I32Add,
Instruction::GlobalSet(HEAP_PTR_GLOBAL),
Instruction::LocalGet(8),
Instruction::LocalGet(1),
Instruction::F64ConvertI32U,
Instruction::F64Store(mem_arg_f64(0)),
Instruction::LocalGet(8),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalSet(9),
Instruction::LocalGet(9), Instruction::LocalGet(0),
Instruction::I32Const(8),
Instruction::I32Add, Instruction::LocalGet(3), Instruction::MemoryCopy { dst_mem: 0, src_mem: 0 },
Instruction::I32Const(0),
Instruction::LocalSet(10),
Instruction::I32Const(0),
Instruction::LocalSet(11),
Instruction::Block(wasm_encoder::BlockType::Empty),
Instruction::Loop(wasm_encoder::BlockType::Empty),
Instruction::LocalGet(10),
Instruction::LocalGet(4),
Instruction::I32GeU,
Instruction::BrIf(1),
Instruction::LocalGet(11),
Instruction::LocalGet(5),
Instruction::I32GeU,
Instruction::If(wasm_encoder::BlockType::Empty),
Instruction::I32Const(0),
Instruction::LocalSet(11),
Instruction::End,
Instruction::LocalGet(2),
Instruction::I32Const(8),
Instruction::I32Add,
Instruction::LocalGet(11),
Instruction::I32Add,
Instruction::I32Load8U(mem_arg_byte(0)),
Instruction::LocalSet(12),
Instruction::LocalGet(9),
Instruction::LocalGet(3), Instruction::I32Add,
Instruction::LocalGet(10), Instruction::I32Add,
Instruction::LocalGet(12),
Instruction::I32Store8(mem_arg_byte(0)),
Instruction::LocalGet(10),
Instruction::I32Const(1),
Instruction::I32Add,
Instruction::LocalSet(10),
Instruction::LocalGet(11),
Instruction::I32Const(1),
Instruction::I32Add,
Instruction::LocalSet(11),
Instruction::Br(0),
Instruction::End,
Instruction::End,
Instruction::LocalGet(8),
Instruction::F64ConvertI32U,
Instruction::End, ];
CompiledFn {
export_name: None,
params: vec![ValType::I32, ValType::I32, ValType::I32],
results: vec![ValType::F64],
locals: vec![
ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ],
instructions,
}
}
pub(super) fn emit_join_str_from_locals(ctx: &mut WasmGenCtx, xs_f64: u32, sep_f64: u32) -> Result<(), String> {
let xs_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(xs_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(xs_ptr));
let count = emit_load_count_i32(ctx, xs_ptr);
let result = ctx.alloc_local();
let zero = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(zero));
let (empty_ptr, _) = emit_str_alloc(ctx, zero);
ctx.emit(Instruction::LocalGet(empty_ptr));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::LocalSet(result));
let sep_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(sep_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(sep_ptr));
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::I32GtU);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
let result_ptr_for_sep = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(result));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(result_ptr_for_sep));
concat_two_i32_ptrs(ctx, result_ptr_for_sep, sep_ptr);
ctx.emit(Instruction::LocalSet(result));
ctx.emit(Instruction::End);
let elem = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(xs_ptr));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(elem));
let elem_arr = [crate::calcit::Calcit::Nil]; let _ = elem_arr;
let elem_str_ptr = emit_turn_string_from_local(ctx, elem);
let result_ptr2 = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(result));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(result_ptr2));
concat_two_i32_ptrs(ctx, result_ptr2, elem_str_ptr);
ctx.emit(Instruction::LocalSet(result));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(result));
Ok(())
}
pub(super) fn emit_join_str(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err(format!("join-str expects 2 args, got {}", args.len()));
}
let xs = ctx.alloc_local();
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(xs));
let sep = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(sep));
emit_join_str_from_locals(ctx, xs, sep)
}
pub(super) fn emit_str_split(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err(format!("split expects 2 args, got {}", args.len()));
}
let xs_f64 = ctx.alloc_local();
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(xs_f64));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::Drop);
emit_str_split_chars_from_local(ctx, xs_f64)
}
pub(super) fn emit_str_split_chars_from_local(ctx: &mut WasmGenCtx, xs_f64: u32) -> Result<(), String> {
let str_ptr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(xs_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(str_ptr));
let byte_len = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(str_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(byte_len));
let list_size = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalGet(byte_len));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(list_size));
let list_ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc_dynamic(ctx, list_size, list_ptr, "list");
ctx.emit(Instruction::LocalGet(list_ptr));
ctx.emit(Instruction::LocalGet(byte_len));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
let content_base = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(str_ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(content_base));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(byte_len));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let one = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(one));
let (char_ptr, char_base) = emit_str_alloc(ctx, one);
let byte_val = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(content_base));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Load8U(mem_arg_byte(0)));
ctx.emit(Instruction::LocalSet(byte_val));
ctx.emit(Instruction::LocalGet(char_base));
ctx.emit(Instruction::LocalGet(byte_val));
ctx.emit(Instruction::I32Store8(mem_arg_byte(0)));
let offset_addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalGet(list_ptr));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(offset_addr));
ctx.emit(Instruction::LocalGet(offset_addr));
ctx.emit(Instruction::LocalGet(char_ptr));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0)); ctx.emit(Instruction::End); ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(list_ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}