use std::collections::HashMap;
use wasm_encoder::Instruction;
use crate::ast::{BinOp, Expr, Literal, Spanned, Stmt, TailCallData};
use crate::ir::{
CallPlan, LeafOp, SemanticConstructor, WrapperKind, classify_call_plan,
classify_constructor_name, classify_leaf_op,
};
use crate::types::Type;
use super::super::types::{WasmType, aver_type_to_wasm};
use super::super::value;
use super::ExprEmitter;
impl<'a> ExprEmitter<'a> {
pub(super) fn emit_block(&mut self, stmts: &[Stmt]) {
if stmts.is_empty() {
self.instructions.push(Instruction::I32Const(0));
return;
}
for (i, stmt) in stmts.iter().enumerate() {
let is_last = i == stmts.len() - 1;
match stmt {
Stmt::Binding(name, _type_ann, expr) => {
let at = self.aver_type_of(expr).clone();
let wt = aver_type_to_wasm(&at);
self.emit_expr(expr);
let idx = self.alloc_local(wt);
self.locals.insert(name.clone(), idx);
self.local_aver_types.insert(idx, at);
self.instructions.push(Instruction::LocalSet(idx));
if is_last {
self.instructions.push(Instruction::I32Const(0));
}
}
Stmt::Expr(expr) => {
self.emit_expr(expr);
if !is_last {
self.instructions.push(Instruction::Drop);
}
}
}
}
}
pub(super) fn emit_expr(&mut self, expr: &Spanned<Expr>) {
match &expr.node {
Expr::Literal(lit) => self.emit_literal(lit),
Expr::Ident(name) | Expr::Resolved { name, .. } => {
if let Some(&idx) = self.locals.get(name) {
self.instructions.push(Instruction::LocalGet(idx));
} else {
self.codegen_error(format!(
"unresolved local identifier `{}` in WASM codegen",
name
));
self.emit_default_value(self.wasm_type_of(expr));
}
}
Expr::BinOp(op, lhs, rhs) => self.emit_binop(op, lhs, rhs),
Expr::FnCall(callee, args) => self.emit_fn_call(expr, callee, args),
Expr::Match { subject, arms } => self.emit_match(expr, subject, arms),
Expr::Constructor(name, inner) => self.emit_constructor(expr, name, inner),
Expr::ErrorProp(inner) => self.emit_error_prop(inner),
Expr::InterpolatedStr(_) => unreachable!(
"InterpolatedStr should have been lowered by ir::interp_lower; \
WASM codegen runs only on lowered IR (see ir::pipeline contract)"
),
Expr::List(items) => self.emit_list(items),
Expr::Tuple(items) => self.emit_tuple(items),
Expr::RecordCreate { type_name, fields } => {
self.emit_record_create(type_name, fields);
}
Expr::Attr(_, _) => {
let leaf = {
let ctx = self.ir_ctx();
classify_leaf_op(&expr.node, &ctx)
};
match leaf {
Some(LeafOp::NoneValue) => {
self.instructions
.push(Instruction::I32Const(value::NONE_SENTINEL));
}
Some(LeafOp::VariantConstructor {
qualified_type_name,
variant_name,
}) => {
self.emit_variant_constructor(&qualified_type_name, &variant_name, &[]);
}
Some(LeafOp::StaticRef(name)) => {
self.codegen_error(format!(
"static path `{}` in value position not supported in WASM",
name
));
self.emit_default_value(WasmType::I32);
}
Some(LeafOp::FieldAccess {
object, field_name, ..
}) => {
let field_wasm_type = self.wasm_type_of(expr);
self.emit_field_access(object, field_name, field_wasm_type);
}
Some(other) => {
unreachable!(
"classify_leaf_op returned unexpected variant for Expr::Attr: {:?}",
other
);
}
None => {
unreachable!("classify_leaf_op returned None for Expr::Attr");
}
}
}
Expr::TailCall(tc) => self.emit_tailcall(tc),
Expr::MapLiteral(entries) => {
self.emit_map_literal(entries);
}
Expr::IndependentProduct(items, unwrap) => {
self.emit_independent_product(items, *unwrap);
}
Expr::RecordUpdate {
type_name,
base,
updates,
} => {
self.emit_record_update(type_name, base, updates);
}
}
}
fn emit_binop(&mut self, op: &BinOp, lhs: &Spanned<Expr>, rhs: &Spanned<Expr>) {
let lhs_type = self.wasm_type_of(lhs);
let rhs_type = self.wasm_type_of(rhs);
let operand_type = if lhs_type == WasmType::F64 || rhs_type == WasmType::F64 {
WasmType::F64
} else {
lhs_type
};
if matches!(op, BinOp::Add) && operand_type == WasmType::I32 {
let lhs_aver = self.aver_type_of(lhs);
if matches!(lhs_aver, Type::Str) {
self.emit_expr(lhs);
self.emit_expr(rhs);
self.instructions
.push(Instruction::Call(self.rt.str_concat));
return;
}
}
if matches!(op, BinOp::Eq | BinOp::Neq) && operand_type != WasmType::F64 {
let lhs_aver = self.aver_type_of(lhs);
if matches!(lhs_aver, Type::Str) {
self.emit_expr(lhs);
if lhs_type != WasmType::I32 {
self.instructions.push(Instruction::I32WrapI64);
}
self.emit_expr(rhs);
if rhs_type != WasmType::I32 {
self.instructions.push(Instruction::I32WrapI64);
}
self.instructions.push(Instruction::Call(self.rt.str_eq));
if matches!(op, BinOp::Neq) {
self.instructions.push(Instruction::I32Eqz);
}
return;
}
if !matches!(lhs_aver, Type::Int | Type::Bool | Type::Float) {
let a_local = self.alloc_local(WasmType::I32);
let b_local = self.alloc_local(WasmType::I32);
self.emit_expr(lhs);
if lhs_type != WasmType::I32 {
self.instructions.push(Instruction::I32WrapI64);
}
self.instructions.push(Instruction::LocalSet(a_local));
self.emit_expr(rhs);
if rhs_type != WasmType::I32 {
self.instructions.push(Instruction::I32WrapI64);
}
self.instructions.push(Instruction::LocalSet(b_local));
self.instructions.push(Instruction::LocalGet(a_local));
self.instructions.push(Instruction::LocalGet(b_local));
self.instructions.push(Instruction::I32Eq);
self.instructions.push(Instruction::LocalGet(a_local));
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::LocalGet(b_local));
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::I64Eq);
self.instructions.push(Instruction::I32Or);
if matches!(op, BinOp::Neq) {
self.instructions.push(Instruction::I32Eqz);
}
return;
}
}
self.emit_expr(lhs);
if operand_type == WasmType::F64 && lhs_type == WasmType::I64 {
self.instructions.push(Instruction::F64ConvertI64S);
}
self.emit_expr(rhs);
if operand_type == WasmType::F64 && rhs_type == WasmType::I64 {
self.instructions.push(Instruction::F64ConvertI64S);
}
let instr = match (op, operand_type) {
(BinOp::Add, WasmType::I64) => Some(Instruction::I64Add),
(BinOp::Add, WasmType::F64) => Some(Instruction::F64Add),
(BinOp::Sub, WasmType::I64) => Some(Instruction::I64Sub),
(BinOp::Sub, WasmType::F64) => Some(Instruction::F64Sub),
(BinOp::Mul, WasmType::I64) => Some(Instruction::I64Mul),
(BinOp::Mul, WasmType::F64) => Some(Instruction::F64Mul),
(BinOp::Div, WasmType::I64) => Some(Instruction::I64DivS),
(BinOp::Div, WasmType::F64) => Some(Instruction::F64Div),
(BinOp::Eq, WasmType::I64) => Some(Instruction::I64Eq),
(BinOp::Eq, WasmType::F64) => Some(Instruction::F64Eq),
(BinOp::Eq, WasmType::I32) => Some(Instruction::I32Eq),
(BinOp::Neq, WasmType::I64) => Some(Instruction::I64Ne),
(BinOp::Neq, WasmType::F64) => Some(Instruction::F64Ne),
(BinOp::Neq, WasmType::I32) => Some(Instruction::I32Ne),
(BinOp::Lt, WasmType::I64) => Some(Instruction::I64LtS),
(BinOp::Lt, WasmType::F64) => Some(Instruction::F64Lt),
(BinOp::Lt, WasmType::I32) => Some(Instruction::I32LtS),
(BinOp::Gt, WasmType::I64) => Some(Instruction::I64GtS),
(BinOp::Gt, WasmType::F64) => Some(Instruction::F64Gt),
(BinOp::Gt, WasmType::I32) => Some(Instruction::I32GtS),
(BinOp::Lte, WasmType::I64) => Some(Instruction::I64LeS),
(BinOp::Lte, WasmType::F64) => Some(Instruction::F64Le),
(BinOp::Lte, WasmType::I32) => Some(Instruction::I32LeS),
(BinOp::Gte, WasmType::I64) => Some(Instruction::I64GeS),
(BinOp::Gte, WasmType::F64) => Some(Instruction::F64Ge),
(BinOp::Gte, WasmType::I32) => Some(Instruction::I32GeS),
_ => None,
};
if let Some(instr) = instr {
self.instructions.push(instr);
} else {
let result_type = match op {
BinOp::Eq | BinOp::Neq | BinOp::Lt | BinOp::Gt | BinOp::Lte | BinOp::Gte => {
WasmType::I32
}
_ => operand_type,
};
self.codegen_error(format!(
"unsupported binary operation `{:?}` for WASM operand type `{:?}`",
op, operand_type
));
self.instructions.push(Instruction::Drop);
self.instructions.push(Instruction::Drop);
self.emit_default_value(result_type);
}
}
fn emit_fn_call(
&mut self,
outer: &Spanned<Expr>,
callee: &Spanned<Expr>,
args: &[Spanned<Expr>],
) {
let plan = classify_call_plan(&callee.node, &self.ir_ctx());
match plan {
CallPlan::Function(ref name) => {
let ret_type = self.wasm_type_of(outer);
let resolved_name = self.resolve_user_fn_name(name);
for arg in args {
self.emit_expr(arg);
}
if let Some(&fn_idx) = self.fn_indices.get(resolved_name.as_str()) {
self.instructions.push(Instruction::Call(fn_idx));
} else {
self.codegen_error(format!(
"missing function index for call to `{}`",
resolved_name
));
for _ in args {
self.instructions.push(Instruction::Drop);
}
self.emit_default_value(ret_type);
}
}
CallPlan::Wrapper(kind) => {
if args.len() == 1 {
self.emit_expr(&args[0]);
self.emit_wrap(kind, &args[0]);
} else {
self.codegen_error("wrapper call with invalid arity");
self.emit_default_value(WasmType::I32);
}
}
CallPlan::NoneValue => {
for arg in args {
self.emit_expr(arg);
self.instructions.push(Instruction::Drop);
}
self.instructions
.push(Instruction::I32Const(value::NONE_SENTINEL));
}
CallPlan::TypeConstructor {
ref qualified_type_name,
ref variant_name,
} => {
self.emit_variant_constructor(qualified_type_name, variant_name, args);
}
CallPlan::Builtin(ref name) => {
if name == "Option.withDefault"
&& args.len() == 2
&& self.try_emit_vec_set_owned_keep(&args[0], &args[1])
{
return;
}
if name == "Option.withDefault"
&& args.len() == 2
&& self.try_emit_vec_get_or_default(&args[0], &args[1])
{
return;
}
if name == "__to_str" && args.len() == 1 {
self.emit_value_to_str(&args[0]);
return;
}
for arg in args {
self.emit_expr(arg);
}
self.emit_builtin_call(name, args);
}
CallPlan::Dynamic => {
let ret_type = self.wasm_type_of(outer);
self.codegen_error("dynamic function calls are not supported in the WASM backend");
for arg in args {
self.emit_expr(arg);
}
for _ in args {
self.instructions.push(Instruction::Drop);
}
self.emit_default_value(ret_type);
}
}
}
fn emit_wrap(&mut self, kind: WrapperKind, arg: &Spanned<Expr>) {
let inner_type = self.wasm_type_of(arg);
let inner_is_ptr = self.expr_is_heap_ptr_spanned(arg);
let wrap_tag = match kind {
WrapperKind::ResultOk => value::WRAP_OK,
WrapperKind::ResultErr => value::WRAP_ERR,
WrapperKind::OptionSome => value::WRAP_SOME,
};
let tmp = self.alloc_local(inner_type);
self.instructions.push(Instruction::LocalSet(tmp));
self.instructions
.push(Instruction::I32Const(wrap_tag as i32));
self.instructions.push(Instruction::LocalGet(tmp));
match inner_type {
WasmType::I64 => {
self.instructions
.push(Instruction::I32Const(if inner_is_ptr { 1 } else { 0 }));
self.instructions.push(Instruction::Call(self.rt.wrap));
}
WasmType::F64 => self.instructions.push(Instruction::Call(self.rt.wrap_f64)),
WasmType::I32 => {
self.instructions
.push(Instruction::I32Const(if inner_is_ptr { 1 } else { 0 }));
self.instructions.push(Instruction::Call(self.rt.wrap_i32));
}
}
}
pub(super) fn emit_variant_constructor(
&mut self,
type_name: &str,
variant_name: &str,
args: &[Spanned<Expr>],
) {
let info = self
.variant_registry
.get(&(type_name.to_string(), variant_name.to_string()));
let tag = info.map(|i| i.tag).unwrap_or(0);
let field_count = args.len();
let size = 8 + field_count * 8;
let ptr_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(size as i32));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(ptr_local));
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_VARIANT,
tag as u64,
self.ptr_mask_for_exprs(args.iter()) as u64,
field_count as u64,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
for (i, arg) in args.iter().enumerate() {
let field_type = self.wasm_type_of(arg);
self.instructions.push(Instruction::LocalGet(ptr_local));
self.emit_expr(arg);
match field_type {
WasmType::I64 => {}
WasmType::F64 => {
self.instructions.push(Instruction::I64ReinterpretF64);
}
WasmType::I32 => {
self.instructions.push(Instruction::I64ExtendI32S);
}
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions.push(Instruction::LocalGet(ptr_local));
}
fn emit_error_prop(&mut self, inner: &Spanned<Expr>) {
self.emit_expr(inner);
let val_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(val_local));
let inner_aver_type = self.aver_type_of(inner);
let ok_wasm_type = match inner_aver_type {
Type::Result(ok, _) => aver_type_to_wasm(ok),
_ => WasmType::I64,
};
let result_bt = wasm_encoder::BlockType::Result(ok_wasm_type.to_val_type());
self.instructions.push(Instruction::LocalGet(val_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32GtS);
self.emit_if(result_bt);
self.instructions.push(Instruction::LocalGet(val_local));
self.instructions.push(Instruction::Call(self.rt.obj_tag));
self.instructions
.push(Instruction::I32Const(value::WRAP_ERR as i32));
self.instructions.push(Instruction::I32Eq);
self.emit_if(result_bt);
self.instructions.push(Instruction::LocalGet(val_local));
self.emit_boundary_return_from_stack(self.fn_return_type, self.fn_return_is_heap);
self.emit_else();
self.instructions.push(Instruction::LocalGet(val_local));
match ok_wasm_type {
WasmType::I64 => self.instructions.push(Instruction::Call(self.rt.unwrap)),
WasmType::F64 => self
.instructions
.push(Instruction::Call(self.rt.unwrap_f64)),
WasmType::I32 => self
.instructions
.push(Instruction::Call(self.rt.unwrap_i32)),
}
self.emit_end();
self.emit_else();
match ok_wasm_type {
WasmType::I32 => self.instructions.push(Instruction::I32Const(0)),
WasmType::I64 => self.instructions.push(Instruction::I64Const(0)),
WasmType::F64 => self.instructions.push(Instruction::F64Const(0.0)),
}
self.emit_end();
}
fn emit_constructor(
&mut self,
_outer: &Spanned<Expr>,
name: &str,
inner: &Option<Box<Spanned<Expr>>>,
) {
let ctor = classify_constructor_name(name, &self.ir_ctx());
match ctor {
SemanticConstructor::Wrapper(kind) => {
let wrap_tag = match kind {
WrapperKind::ResultOk => value::WRAP_OK,
WrapperKind::ResultErr => value::WRAP_ERR,
WrapperKind::OptionSome => value::WRAP_SOME,
};
if let Some(expr) = inner {
let inner_type = self.wasm_type_of(expr);
self.instructions
.push(Instruction::I32Const(wrap_tag as i32));
self.emit_expr(expr);
match inner_type {
WasmType::I64 => {
self.instructions.push(Instruction::I32Const(
if self.expr_is_heap_ptr_spanned(expr) {
1
} else {
0
},
));
self.instructions.push(Instruction::Call(self.rt.wrap));
}
WasmType::F64 => {
self.instructions.push(Instruction::Call(self.rt.wrap_f64));
}
WasmType::I32 => {
self.instructions.push(Instruction::I32Const(
if self.expr_is_heap_ptr_spanned(expr) {
1
} else {
0
},
));
self.instructions.push(Instruction::Call(self.rt.wrap_i32));
}
}
} else {
self.instructions.push(Instruction::I32Const(0));
}
}
SemanticConstructor::NoneValue => {
self.instructions
.push(Instruction::I32Const(value::NONE_SENTINEL));
}
SemanticConstructor::TypeConstructor {
qualified_type_name,
variant_name,
} => {
let mut args_vec = Vec::new();
if let Some(expr) = inner {
args_vec.push(expr.as_ref().clone());
}
self.emit_variant_constructor(&qualified_type_name, &variant_name, &args_vec);
}
SemanticConstructor::Unknown(_) => {
if let Some(expr) = inner {
self.emit_expr(expr);
} else {
self.instructions.push(Instruction::I32Const(0));
}
}
}
}
fn emit_list(&mut self, items: &[Spanned<Expr>]) {
if items.is_empty() {
self.instructions
.push(Instruction::I32Const(value::EMPTY_LIST));
return;
}
let elem_type = self.wasm_type_of(&items[0]);
let elem_is_ptr = self.expr_is_heap_ptr_spanned(&items[0]);
self.instructions
.push(Instruction::I32Const(value::EMPTY_LIST));
for item in items.iter().rev() {
let tail_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(tail_local));
self.emit_expr(item);
self.instructions.push(Instruction::LocalGet(tail_local));
match elem_type {
WasmType::F64 => {
self.instructions
.push(Instruction::Call(self.rt.list_cons_f64));
}
WasmType::I32 => {
let tmp = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(tmp));
self.instructions.push(Instruction::I64ExtendI32S);
self.instructions.push(Instruction::LocalGet(tmp));
self.instructions
.push(Instruction::I32Const(if elem_is_ptr { 1 } else { 0 }));
self.instructions.push(Instruction::Call(self.rt.list_cons));
}
_ => {
self.instructions
.push(Instruction::I32Const(if elem_is_ptr { 1 } else { 0 }));
self.instructions.push(Instruction::Call(self.rt.list_cons));
}
}
}
}
fn emit_tuple(&mut self, items: &[Spanned<Expr>]) {
if items.is_empty() {
self.instructions.push(Instruction::I32Const(0));
return;
}
let count = items.len();
let size = 8 + count * 8;
let ptr_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(size as i32));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(ptr_local));
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_TUPLE,
0,
self.ptr_mask_for_exprs(items.iter()) as u64,
count as u64,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
for (i, item) in items.iter().enumerate() {
let item_type = self.wasm_type_of(item);
self.instructions.push(Instruction::LocalGet(ptr_local));
self.emit_expr(item);
match item_type {
WasmType::I64 => {}
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions.push(Instruction::LocalGet(ptr_local));
}
fn emit_tuple_from_locals(&mut self, items: &[(u32, WasmType)]) {
if items.is_empty() {
self.instructions.push(Instruction::I32Const(0));
return;
}
let ptr_mask = items
.iter()
.enumerate()
.fold(0u16, |mask, (idx, (local, _))| {
if idx < 16
&& self
.local_aver_types
.get(local)
.is_some_and(|ty| self.is_heap_type(ty))
{
mask | (1u16 << idx)
} else {
mask
}
});
let count = items.len();
let size = 8 + count * 8;
let ptr_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(size as i32));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(ptr_local));
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_TUPLE,
0,
ptr_mask as u64,
count as u64,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
for (i, (local, item_type)) in items.iter().enumerate() {
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions.push(Instruction::LocalGet(*local));
match item_type {
WasmType::I64 => {}
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions.push(Instruction::LocalGet(ptr_local));
}
pub(super) fn emit_record_from_locals(&mut self, fields: &[(u32, WasmType)]) {
if fields.is_empty() {
self.instructions.push(Instruction::I32Const(0));
return;
}
let ptr_mask = fields
.iter()
.enumerate()
.fold(0u16, |mask, (idx, (local, _))| {
if idx < 16
&& self
.local_aver_types
.get(local)
.is_some_and(|ty| self.is_heap_type(ty))
{
mask | (1u16 << idx)
} else {
mask
}
});
let count = fields.len();
let size = 8 + count * 8;
let ptr_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(size as i32));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(ptr_local));
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_RECORD,
0,
ptr_mask as u64,
count as u64,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
for (i, (local, field_type)) in fields.iter().enumerate() {
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions.push(Instruction::LocalGet(*local));
match field_type {
WasmType::I64 => {}
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions.push(Instruction::LocalGet(ptr_local));
}
fn emit_independent_product(&mut self, items: &[Spanned<Expr>], unwrap: bool) {
if !unwrap {
self.emit_tuple(items);
return;
}
let mut tuple_locals = Vec::with_capacity(items.len());
for item in items {
self.emit_expr(item);
let result_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(result_local));
self.instructions.push(Instruction::LocalGet(result_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32GtS);
self.emit_if(wasm_encoder::BlockType::Empty);
self.instructions.push(Instruction::LocalGet(result_local));
self.instructions.push(Instruction::Call(self.rt.obj_tag));
self.instructions
.push(Instruction::I32Const(value::WRAP_ERR as i32));
self.instructions.push(Instruction::I32Eq);
self.emit_if(wasm_encoder::BlockType::Empty);
self.instructions.push(Instruction::LocalGet(result_local));
self.emit_boundary_return_from_stack(self.fn_return_type, self.fn_return_is_heap);
self.emit_end();
self.emit_else();
self.instructions.push(Instruction::LocalGet(result_local));
self.emit_boundary_return_from_stack(self.fn_return_type, self.fn_return_is_heap);
self.emit_end();
let ok_type = match self.aver_type_of(item) {
Type::Result(ok, _) => (**ok).clone(),
other => other.clone(),
};
let ok_wasm_type = aver_type_to_wasm(&ok_type);
let ok_local = self.alloc_local(ok_wasm_type);
self.instructions.push(Instruction::LocalGet(result_local));
match ok_wasm_type {
WasmType::I64 => self.instructions.push(Instruction::Call(self.rt.unwrap)),
WasmType::F64 => self
.instructions
.push(Instruction::Call(self.rt.unwrap_f64)),
WasmType::I32 => self
.instructions
.push(Instruction::Call(self.rt.unwrap_i32)),
}
self.instructions.push(Instruction::LocalSet(ok_local));
tuple_locals.push((ok_local, ok_wasm_type));
}
self.emit_tuple_from_locals(&tuple_locals);
}
fn emit_record_create(&mut self, type_name: &str, fields: &[(String, Spanned<Expr>)]) {
if matches!(self.rt.adapter, super::super::WasmAdapter::Fetch)
&& type_name == "HttpResponse"
&& let Some(&import_idx) = self.host_import_indices.get("response_text")
{
let status = fields.iter().find(|(n, _)| n == "status").map(|(_, e)| e);
let body = fields.iter().find(|(n, _)| n == "body").map(|(_, e)| e);
let headers = fields.iter().find(|(n, _)| n == "headers").map(|(_, e)| e);
if let (Some(status_expr), Some(body_expr)) = (status, body) {
let body_local = self.alloc_local(WasmType::I32);
if let (Some(headers_expr), Some(&set_header_idx)) =
(headers, self.host_import_indices.get("response_set_header"))
{
self.emit_fetch_apply_headers(headers_expr, set_header_idx);
}
self.emit_expr(status_expr);
self.instructions.push(Instruction::I32WrapI64);
self.emit_expr(body_expr);
self.instructions.push(Instruction::LocalSet(body_local));
self.instructions.push(Instruction::LocalGet(body_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Add); self.instructions.push(Instruction::LocalGet(body_local));
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64); self.instructions.push(Instruction::Call(import_idx));
return;
}
}
let count = fields.len();
let size = 8 + count * 8;
let ptr_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(size as i32));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(ptr_local));
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_RECORD,
0,
self.ptr_mask_for_exprs(fields.iter().map(|(_, expr)| expr)) as u64,
count as u64,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
for (i, (_name, expr)) in fields.iter().enumerate() {
let field_type = self.wasm_type_of(expr);
self.instructions.push(Instruction::LocalGet(ptr_local));
self.emit_expr(expr);
match field_type {
WasmType::I64 => {}
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions.push(Instruction::LocalGet(ptr_local));
}
pub(super) fn emit_http_send(
&mut self,
method: &'static str,
args: &[Spanned<Expr>],
has_body: bool,
) {
use wasm_encoder::{BlockType, MemArg, ValType};
let send_idx = match self.host_import_indices.get("http_send").copied() {
Some(idx) => idx,
None => {
self.codegen_error("missing host import `http_send`");
for _ in args {
self.instructions.push(Instruction::Drop);
}
self.instructions.push(Instruction::I32Const(0));
return;
}
};
let url_local;
let body_local;
let ct_local;
let headers_local;
if has_body {
headers_local = self.alloc_local(WasmType::I32);
ct_local = self.alloc_local(WasmType::I32);
body_local = self.alloc_local(WasmType::I32);
url_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(headers_local));
self.instructions.push(Instruction::LocalSet(ct_local));
self.instructions.push(Instruction::LocalSet(body_local));
self.instructions.push(Instruction::LocalSet(url_local));
} else {
url_local = self.alloc_local(WasmType::I32);
body_local = 0;
ct_local = 0;
headers_local = 0;
self.instructions.push(Instruction::LocalSet(url_local));
}
if let Some(&clear_idx) = self.host_import_indices.get("http_clear_request_headers") {
self.instructions.push(Instruction::Call(clear_idx));
}
if has_body && let Some(&add_idx) = self.host_import_indices.get("http_add_request_header")
{
self.emit_http_walk_headers(headers_local, add_idx);
}
let header_load = MemArg {
offset: 0,
align: 3,
memory_index: 0,
};
let push_str_pair = |this: &mut Self, str_local: u32| {
this.instructions.push(Instruction::LocalGet(str_local));
this.instructions.push(Instruction::I32Const(8));
this.instructions.push(Instruction::I32Add); this.instructions.push(Instruction::LocalGet(str_local));
this.instructions.push(Instruction::I64Load(header_load));
this.instructions.push(Instruction::I64Const(0xFFFFFFFF));
this.instructions.push(Instruction::I64And);
this.instructions.push(Instruction::I32WrapI64); };
if let Some(&(offset, len)) = self.string_literals.get(method) {
self.instructions
.push(Instruction::I32Const(offset as i32 + 8));
self.instructions.push(Instruction::I32Const(len as i32));
} else {
self.codegen_error(format!(
"internal: HTTP method literal `{}` not interned",
method
));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32Const(0));
}
push_str_pair(self, url_local);
if has_body {
push_str_pair(self, body_local);
push_str_pair(self, ct_local);
} else {
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32Const(0));
}
self.instructions.push(Instruction::Call(send_idx));
let err_local = self.alloc_local(WasmType::I32);
let resp_headers_local = self.alloc_local(WasmType::I32);
let resp_body_local = self.alloc_local(WasmType::I32);
let status_local = self.alloc_local(WasmType::I64);
self.instructions.push(Instruction::LocalSet(err_local));
self.instructions
.push(Instruction::LocalSet(resp_headers_local));
self.instructions
.push(Instruction::LocalSet(resp_body_local));
self.instructions.push(Instruction::LocalSet(status_local));
self.instructions.push(Instruction::LocalGet(err_local));
self.emit_if(BlockType::Result(ValType::I32));
self.instructions
.push(Instruction::I32Const(super::super::value::WRAP_ERR as i32));
self.instructions.push(Instruction::LocalGet(err_local));
self.instructions.push(Instruction::I64ExtendI32U);
self.instructions.push(Instruction::I32Const(1)); self.instructions.push(Instruction::Call(self.rt.wrap));
self.emit_else();
let response_size: i32 = 8 + 3 * 8;
let resp_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(response_size));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(resp_local));
self.instructions.push(Instruction::LocalGet(resp_local));
self.instructions
.push(Instruction::I64Const(super::super::value::make_header(
super::super::value::OBJ_RECORD,
0,
0b110,
3,
) as i64));
self.instructions.push(Instruction::I64Store(header_load));
self.instructions.push(Instruction::LocalGet(resp_local));
self.instructions.push(Instruction::LocalGet(status_local));
self.instructions.push(Instruction::I64Store(MemArg {
offset: 8,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::LocalGet(resp_local));
self.instructions
.push(Instruction::LocalGet(resp_body_local));
self.instructions.push(Instruction::I64ExtendI32U);
self.instructions.push(Instruction::I64Store(MemArg {
offset: 16,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::LocalGet(resp_local));
self.instructions
.push(Instruction::LocalGet(resp_headers_local));
self.instructions.push(Instruction::I64ExtendI32U);
self.instructions.push(Instruction::I64Store(MemArg {
offset: 24,
align: 3,
memory_index: 0,
}));
self.instructions
.push(Instruction::I32Const(super::super::value::WRAP_OK as i32));
self.instructions.push(Instruction::LocalGet(resp_local));
self.instructions.push(Instruction::I64ExtendI32U);
self.instructions.push(Instruction::I32Const(1));
self.instructions.push(Instruction::Call(self.rt.wrap));
self.emit_end();
}
fn emit_http_walk_headers(&mut self, headers_local: u32, add_idx: u32) {
use wasm_encoder::{BlockType, MemArg};
let entries_local = self.alloc_local(WasmType::I32);
let entry_local = self.alloc_local(WasmType::I32);
let key_local = self.alloc_local(WasmType::I32);
let values_local = self.alloc_local(WasmType::I32);
let value_local = self.alloc_local(WasmType::I32);
let header_load = MemArg {
offset: 0,
align: 3,
memory_index: 0,
};
self.instructions.push(Instruction::LocalGet(headers_local));
self.instructions
.push(Instruction::Call(self.rt.map_entries));
self.instructions.push(Instruction::LocalSet(entries_local));
self.instructions.push(Instruction::Block(BlockType::Empty));
self.instructions.push(Instruction::Loop(BlockType::Empty));
self.instructions.push(Instruction::LocalGet(entries_local));
self.instructions.push(Instruction::I32Eqz);
self.instructions.push(Instruction::BrIf(1));
self.instructions.push(Instruction::LocalGet(entries_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(entry_local));
self.instructions.push(Instruction::LocalGet(entry_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(key_local));
self.instructions.push(Instruction::LocalGet(entry_local));
self.instructions.push(Instruction::I32Const(1));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(values_local));
self.instructions.push(Instruction::Block(BlockType::Empty));
self.instructions.push(Instruction::Loop(BlockType::Empty));
self.instructions.push(Instruction::LocalGet(values_local));
self.instructions.push(Instruction::I32Eqz);
self.instructions.push(Instruction::BrIf(1));
self.instructions.push(Instruction::LocalGet(values_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(value_local));
self.instructions.push(Instruction::LocalGet(key_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Add);
self.instructions.push(Instruction::LocalGet(key_local));
self.instructions.push(Instruction::I64Load(header_load));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64);
self.instructions.push(Instruction::LocalGet(value_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Add);
self.instructions.push(Instruction::LocalGet(value_local));
self.instructions.push(Instruction::I64Load(header_load));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64);
self.instructions.push(Instruction::Call(add_idx));
self.instructions.push(Instruction::LocalGet(values_local));
self.instructions.push(Instruction::I32Const(1));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(values_local));
self.instructions.push(Instruction::Br(0));
self.instructions.push(Instruction::End);
self.instructions.push(Instruction::End);
self.instructions.push(Instruction::LocalGet(entries_local));
self.instructions.push(Instruction::I32Const(1));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(entries_local));
self.instructions.push(Instruction::Br(0));
self.instructions.push(Instruction::End);
self.instructions.push(Instruction::End);
}
fn emit_fetch_apply_headers(&mut self, headers_expr: &Spanned<Expr>, set_header_idx: u32) {
use wasm_encoder::{BlockType, MemArg};
let entries_local = self.alloc_local(WasmType::I32);
let entry_local = self.alloc_local(WasmType::I32);
let key_local = self.alloc_local(WasmType::I32);
let values_local = self.alloc_local(WasmType::I32);
let value_local = self.alloc_local(WasmType::I32);
let header_load = MemArg {
offset: 0,
align: 3,
memory_index: 0,
};
self.emit_expr(headers_expr);
self.instructions
.push(Instruction::Call(self.rt.map_entries));
self.instructions.push(Instruction::LocalSet(entries_local));
self.instructions.push(Instruction::Block(BlockType::Empty));
self.instructions.push(Instruction::Loop(BlockType::Empty));
self.instructions.push(Instruction::LocalGet(entries_local));
self.instructions.push(Instruction::I32Eqz);
self.instructions.push(Instruction::BrIf(1));
self.instructions.push(Instruction::LocalGet(entries_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(entry_local));
self.instructions.push(Instruction::LocalGet(entry_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(key_local));
self.instructions.push(Instruction::LocalGet(entry_local));
self.instructions.push(Instruction::I32Const(1));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(values_local));
self.instructions.push(Instruction::Block(BlockType::Empty));
self.instructions.push(Instruction::Loop(BlockType::Empty));
self.instructions.push(Instruction::LocalGet(values_local));
self.instructions.push(Instruction::I32Eqz);
self.instructions.push(Instruction::BrIf(1));
self.instructions.push(Instruction::LocalGet(values_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(value_local));
self.instructions.push(Instruction::LocalGet(key_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Add); self.instructions.push(Instruction::LocalGet(key_local));
self.instructions.push(Instruction::I64Load(header_load));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64); self.instructions.push(Instruction::LocalGet(value_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Add); self.instructions.push(Instruction::LocalGet(value_local));
self.instructions.push(Instruction::I64Load(header_load));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64); self.instructions.push(Instruction::Call(set_header_idx));
self.instructions.push(Instruction::LocalGet(values_local));
self.instructions.push(Instruction::I32Const(1));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(values_local));
self.instructions.push(Instruction::Br(0));
self.instructions.push(Instruction::End); self.instructions.push(Instruction::End);
self.instructions.push(Instruction::LocalGet(entries_local));
self.instructions.push(Instruction::I32Const(1));
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
self.instructions.push(Instruction::LocalSet(entries_local));
self.instructions.push(Instruction::Br(0));
self.instructions.push(Instruction::End); self.instructions.push(Instruction::End); }
fn emit_record_update(
&mut self,
type_name: &str,
base: &Spanned<Expr>,
updates: &[(String, Spanned<Expr>)],
) {
let Some(fields) = self.record_fields(type_name).map(|fields| fields.to_vec()) else {
self.emit_expr(base);
return;
};
let base_local = self.alloc_local(WasmType::I32);
self.emit_expr(base);
self.instructions.push(Instruction::LocalSet(base_local));
let mut update_locals = HashMap::with_capacity(updates.len());
for (field_name, expr) in updates {
let field_type = self.wasm_type_of(expr);
let field_local = self.alloc_local(field_type);
self.emit_expr(expr);
self.instructions.push(Instruction::LocalSet(field_local));
update_locals.insert(field_name.as_str(), (field_local, field_type));
}
let count = fields.len();
let field_ptr_mask = self.ptr_mask_for_types(
&fields
.iter()
.map(|(_, ty)| crate::types::parse_type_str(ty))
.collect::<Vec<_>>(),
);
let size = 8 + count * 8;
let ptr_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(size as i32));
self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(ptr_local));
self.instructions.push(Instruction::LocalGet(ptr_local));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_RECORD,
0,
field_ptr_mask as u64,
count as u64,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
for (i, (field_name, _)) in fields.iter().enumerate() {
self.instructions.push(Instruction::LocalGet(ptr_local));
if let Some(&(field_local, field_type)) = update_locals.get(field_name.as_str()) {
self.instructions.push(Instruction::LocalGet(field_local));
match field_type {
WasmType::I64 => {}
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
}
} else {
self.instructions.push(Instruction::LocalGet(base_local));
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: (8 + i * 8) as u64,
align: 3,
memory_index: 0,
}));
}
self.instructions.push(Instruction::LocalGet(ptr_local));
}
fn emit_map_literal(&mut self, entries: &[(Spanned<Expr>, Spanned<Expr>)]) {
let (key_kind, value_ptr_flag) = match entries.first() {
Some((k, v)) => {
let k_kind = self.kind_for_aver_type(Some(self.aver_type_of(k)));
let v_ptr = self.value_is_heap_aver_type(Some(self.aver_type_of(v)));
(k_kind, v_ptr)
}
None => (4, 1),
};
self.instructions.push(Instruction::I32Const(0)); for (key, val) in entries.iter().rev() {
let list_tmp = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(list_tmp));
let tuple_ptr = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::I32Const(24)); self.instructions.push(Instruction::Call(self.rt.alloc));
self.instructions.push(Instruction::LocalSet(tuple_ptr));
self.instructions.push(Instruction::LocalGet(tuple_ptr));
self.instructions
.push(Instruction::I64Const(value::make_header(
value::OBJ_TUPLE,
0,
self.ptr_mask_for_types(&[
self.aver_type_of(key).clone(),
self.aver_type_of(val).clone(),
]) as u64,
2,
) as i64));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::LocalGet(tuple_ptr));
self.emit_expr(key);
let key_type = self.wasm_type_of(key);
match key_type {
WasmType::I64 => {}
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32U),
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 8,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::LocalGet(tuple_ptr));
self.emit_expr(val);
let val_type = self.wasm_type_of(val);
match val_type {
WasmType::I64 => {}
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
}
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 16,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::LocalGet(tuple_ptr));
self.instructions.push(Instruction::I64ExtendI32U); self.instructions.push(Instruction::LocalGet(list_tmp)); self.instructions.push(Instruction::I32Const(1)); self.instructions.push(Instruction::Call(self.rt.list_cons));
}
self.instructions.push(Instruction::I32Const(key_kind));
self.instructions
.push(Instruction::I32Const(value_ptr_flag));
self.instructions
.push(Instruction::Call(self.rt.map_from_list));
}
fn emit_field_access(
&mut self,
base_expr: &Spanned<Expr>,
field_name: &str,
field_wasm_type: WasmType,
) {
self.emit_expr(base_expr);
let base_type_name = match self.aver_type_of(base_expr) {
Type::Named(name) => Some(name.clone()),
_ => None,
};
if matches!(self.rt.adapter, super::super::WasmAdapter::Fetch)
&& base_type_name.as_deref() == Some("HttpRequest")
{
let import_name = match field_name {
"method" => Some("request_method"),
"url" | "path" => Some("request_url"),
"query" => Some("request_query"),
"body" => Some("request_body"),
_ => None,
};
if let Some(name) = import_name
&& let Some(&idx) = self.host_import_indices.get(name)
{
self.instructions.push(Instruction::Drop);
self.instructions.push(Instruction::Call(idx));
return;
}
if field_name == "headers" {
self.instructions.push(Instruction::Drop);
if let Some(&idx) = self.host_import_indices.get("request_headers_load") {
self.instructions.push(Instruction::Call(idx));
} else {
self.codegen_error("missing host import `request_headers_load`");
self.instructions.push(Instruction::I32Const(0));
}
return;
}
}
let field_idx = if let Some(ref type_name) = base_type_name {
self.type_fields
.get(&(type_name.clone(), field_name.to_string()))
.copied()
.unwrap_or_else(|| {
self.type_fields
.iter()
.find(|((_, f), _)| f == field_name)
.map(|(_, &idx)| idx)
.unwrap_or(0)
})
} else {
self.type_fields
.iter()
.find(|((_, f), _)| f == field_name)
.map(|(_, &idx)| idx)
.unwrap_or(0)
};
self.instructions
.push(Instruction::I32Const(field_idx as i32));
match field_wasm_type {
WasmType::F64 => {
self.instructions.push(Instruction::Call(self.rt.obj_field));
self.instructions.push(Instruction::F64ReinterpretI64);
}
WasmType::I32 => {
self.instructions
.push(Instruction::Call(self.rt.obj_field_i32));
}
WasmType::I64 => {
self.instructions.push(Instruction::Call(self.rt.obj_field));
}
}
}
fn emit_tailcall(&mut self, tc: &TailCallData) {
let fn_name = &tc.target;
let args = tc.args.as_slice();
let resolved_name = self.resolve_user_fn_name(fn_name);
if let (Some(loop_depth), Some(dispatch_local), Some((member_id, param_slots))) = (
self.tco_loop_depth,
self.mutual_tco_dispatch_local,
self.mutual_tco_targets.get(&resolved_name).cloned(),
) {
let direct_to_slots =
self.mutual_tco_uniform && self.is_no_alloc && param_slots.len() == args.len();
for arg in args {
self.emit_expr(arg);
}
let tmp_base = if direct_to_slots {
u32::MAX } else {
let base = self.next_local;
for arg in args {
let wt = self.wasm_type_of(arg);
self.alloc_local(wt);
}
for i in (0..args.len()).rev() {
self.instructions
.push(Instruction::LocalSet(base + i as u32));
}
base
};
if self.is_no_alloc {
} else if let Some(iter_mark) = self.iter_mark_local {
let fn_mark = self.boundary_mark_local.unwrap_or(iter_mark);
if let Some(watermark) = self.gc_watermark_local {
self.instructions.push(Instruction::GlobalGet(0));
self.instructions.push(Instruction::LocalGet(watermark));
self.instructions.push(Instruction::I32Sub);
self.instructions.push(Instruction::I32Const(16384));
self.instructions.push(Instruction::I32GtU);
self.emit_if(wasm_encoder::BlockType::Empty);
self.instructions.push(Instruction::LocalGet(fn_mark));
self.emit_tco_compaction(args, tmp_base);
self.instructions.push(Instruction::GlobalGet(0));
self.instructions.push(Instruction::LocalSet(watermark));
self.emit_end();
} else {
self.instructions.push(Instruction::GlobalGet(0));
self.instructions.push(Instruction::LocalGet(iter_mark));
self.instructions.push(Instruction::I32Sub);
self.instructions.push(Instruction::I32Const(256));
self.instructions.push(Instruction::I32GtU);
self.emit_if(wasm_encoder::BlockType::Empty);
self.instructions.push(Instruction::LocalGet(fn_mark));
self.emit_tco_compaction(args, tmp_base);
self.emit_end();
}
} else if let Some(mark_local) = self.boundary_mark_local {
self.instructions.push(Instruction::LocalGet(mark_local));
self.emit_tco_compaction(args, tmp_base);
}
if direct_to_slots {
for slot in param_slots.iter().rev() {
self.instructions.push(Instruction::LocalSet(*slot));
}
} else {
for (i, slot) in param_slots.iter().enumerate() {
self.instructions
.push(Instruction::LocalGet(tmp_base + i as u32));
self.instructions.push(Instruction::LocalSet(*slot));
}
}
self.instructions
.push(Instruction::I32Const(member_id as i32));
self.instructions
.push(Instruction::LocalSet(dispatch_local));
let br_depth = self.block_depth - loop_depth;
self.instructions.push(Instruction::Br(br_depth));
self.instructions.push(Instruction::Unreachable);
return;
}
if let Some(loop_depth) = self
.tco_loop_depth
.filter(|_| resolved_name == self.current_fn_name)
{
for arg in args {
self.emit_expr(arg);
}
let arg_count = args.len();
let tmp_base = self.next_local;
for arg in args.iter() {
let wt = self.wasm_type_of(arg);
self.alloc_local(wt);
}
for i in (0..arg_count).rev() {
self.instructions
.push(Instruction::LocalSet(tmp_base + i as u32));
}
if self.is_no_alloc {
} else if let Some(iter_mark) = self.iter_mark_local {
let fn_mark = self.boundary_mark_local.unwrap_or(iter_mark);
self.instructions.push(Instruction::GlobalGet(0));
self.instructions.push(Instruction::LocalGet(iter_mark));
self.instructions.push(Instruction::I32Sub);
self.instructions.push(Instruction::I32Const(16384));
self.instructions.push(Instruction::I32GtU);
self.emit_if(wasm_encoder::BlockType::Empty);
self.instructions.push(Instruction::LocalGet(fn_mark));
self.emit_tco_compaction(args, tmp_base);
self.emit_end();
} else if let Some(mark_local) = self.boundary_mark_local {
self.instructions.push(Instruction::LocalGet(mark_local));
self.emit_tco_compaction(args, tmp_base);
}
for i in 0..arg_count {
self.instructions
.push(Instruction::LocalGet(tmp_base + i as u32));
self.instructions.push(Instruction::LocalSet(i as u32));
}
let br_depth = self.block_depth - loop_depth;
self.instructions.push(Instruction::Br(br_depth));
self.instructions.push(Instruction::Unreachable);
} else {
for arg in args {
self.emit_expr(arg);
}
if let Some(&fn_idx) = self.fn_indices.get(resolved_name.as_str()) {
self.instructions.push(Instruction::Call(fn_idx));
} else {
self.codegen_error(format!(
"missing function index for tail call to `{}`",
resolved_name
));
for _ in args {
self.instructions.push(Instruction::Drop);
}
self.emit_default_value(self.fn_return_type);
}
}
}
fn try_emit_vec_set_owned_keep(
&mut self,
option_expr: &Spanned<Expr>,
default_expr: &Spanned<Expr>,
) -> bool {
let Expr::FnCall(callee, inner_args) = &option_expr.node else {
return false;
};
if inner_args.len() != 3 {
return false;
}
let inner_plan = classify_call_plan(&callee.node, &self.ir_ctx());
if !matches!(inner_plan, CallPlan::Builtin(ref n) if n == "Vector.set") {
return false;
}
let vec_arg = &inner_args[0].node;
if vec_arg != &default_expr.node {
return false;
}
let Expr::Resolved { name, last_use, .. } = vec_arg else {
return false;
};
if !last_use.0 {
return false;
}
let Some(&vec_local_idx) = self.locals.get(name) else {
return false;
};
let idx_local = self.alloc_local(WasmType::I64);
let val_local = self.alloc_local(WasmType::I64);
let len_local = self.alloc_local(WasmType::I32);
let i_local = self.alloc_local(WasmType::I32);
self.emit_expr(&inner_args[1]);
self.instructions.push(Instruction::LocalSet(idx_local));
self.emit_expr(&inner_args[2]);
match self.wasm_type_of(&inner_args[2]) {
WasmType::I64 => {}
WasmType::I32 => self.instructions.push(Instruction::I64ExtendI32S),
WasmType::F64 => self.instructions.push(Instruction::I64ReinterpretF64),
}
self.instructions.push(Instruction::LocalSet(val_local));
self.instructions.push(Instruction::LocalGet(vec_local_idx));
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64);
self.instructions.push(Instruction::LocalSet(len_local));
self.instructions.push(Instruction::LocalGet(idx_local));
self.instructions.push(Instruction::I32WrapI64);
self.instructions.push(Instruction::LocalSet(i_local));
self.instructions.push(Instruction::LocalGet(i_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32GeS);
self.instructions.push(Instruction::LocalGet(i_local));
self.instructions.push(Instruction::LocalGet(len_local));
self.instructions.push(Instruction::I32LtS);
self.instructions.push(Instruction::I32And);
self.emit_if(wasm_encoder::BlockType::Empty);
self.instructions.push(Instruction::LocalGet(vec_local_idx));
self.instructions.push(Instruction::LocalGet(i_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Mul);
self.instructions.push(Instruction::I32Add);
self.instructions.push(Instruction::LocalGet(val_local));
self.instructions
.push(Instruction::I64Store(wasm_encoder::MemArg {
offset: 8,
align: 3,
memory_index: 0,
}));
self.emit_end();
self.instructions.push(Instruction::LocalGet(vec_local_idx));
true
}
fn try_emit_vec_get_or_default(
&mut self,
option_expr: &Spanned<Expr>,
default_expr: &Spanned<Expr>,
) -> bool {
let Expr::FnCall(callee, inner_args) = &option_expr.node else {
return false;
};
if inner_args.len() != 2 {
return false;
}
let inner_plan = classify_call_plan(&callee.node, &self.ir_ctx());
if !matches!(inner_plan, CallPlan::Builtin(ref n) if n == "Vector.get") {
return false;
}
let Expr::Literal(ref default_lit) = default_expr.node else {
return false;
};
let result_type = self.wasm_type_of(default_expr);
self.emit_expr(&inner_args[0]); self.emit_expr(&inner_args[1]);
let vec_local = self.alloc_local(WasmType::I32);
let idx_local = self.alloc_local(WasmType::I64);
let len_local = self.alloc_local(WasmType::I32);
let i_local = self.alloc_local(WasmType::I32);
self.instructions.push(Instruction::LocalSet(idx_local));
self.instructions.push(Instruction::LocalSet(vec_local));
self.instructions.push(Instruction::LocalGet(vec_local));
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
self.instructions.push(Instruction::I64Const(0xFFFFFFFF));
self.instructions.push(Instruction::I64And);
self.instructions.push(Instruction::I32WrapI64);
self.instructions.push(Instruction::LocalSet(len_local));
self.instructions.push(Instruction::LocalGet(idx_local));
self.instructions.push(Instruction::I32WrapI64);
self.instructions.push(Instruction::LocalSet(i_local));
self.instructions.push(Instruction::LocalGet(i_local));
self.instructions.push(Instruction::I32Const(0));
self.instructions.push(Instruction::I32LtS);
self.instructions.push(Instruction::LocalGet(i_local));
self.instructions.push(Instruction::LocalGet(len_local));
self.instructions.push(Instruction::I32GeS);
self.instructions.push(Instruction::I32Or);
self.emit_if(wasm_encoder::BlockType::Result(result_type.to_val_type()));
self.emit_literal(default_lit);
self.emit_else();
self.instructions.push(Instruction::LocalGet(vec_local));
self.instructions.push(Instruction::LocalGet(i_local));
self.instructions.push(Instruction::I32Const(8));
self.instructions.push(Instruction::I32Mul);
self.instructions.push(Instruction::I32Add);
self.instructions
.push(Instruction::I64Load(wasm_encoder::MemArg {
offset: 8,
align: 3,
memory_index: 0,
}));
match result_type {
WasmType::I64 => {}
WasmType::F64 => self.instructions.push(Instruction::F64ReinterpretI64),
WasmType::I32 => self.instructions.push(Instruction::I32WrapI64),
}
self.emit_end();
true
}
pub(super) fn emit_literal(&mut self, lit: &Literal) {
match lit {
Literal::Int(i) => self.instructions.push(Instruction::I64Const(*i)),
Literal::Float(f) => self
.instructions
.push(Instruction::F64Const(f64::from_bits(f.to_bits()))),
Literal::Bool(b) => {
self.instructions
.push(Instruction::I32Const(if *b { 1 } else { 0 }))
}
Literal::Str(s) => self.emit_string_literal(s),
Literal::Unit => self.instructions.push(Instruction::I32Const(0)),
}
}
pub(super) fn emit_string_literal(&mut self, s: &str) {
if let Some(&(offset, _len)) = self.string_literals.get(s) {
self.instructions.push(Instruction::I32Const(offset as i32));
} else {
self.codegen_error(format!("missing interned string literal `{}`", s));
self.emit_default_value(WasmType::I32);
}
}
pub(super) fn emit_default_init(&mut self, local: u32, wt: WasmType) {
self.emit_default_value(wt);
self.instructions.push(Instruction::LocalSet(local));
}
fn emit_tco_compaction(&mut self, args: &[Spanned<Expr>], tmp_base: u32) {
self.instructions
.push(Instruction::Call(self.rt.collect_begin));
for (arg_idx, arg) in args.iter().enumerate() {
if self.expr_is_heap_ptr_spanned(arg) {
self.instructions
.push(Instruction::LocalGet(tmp_base + arg_idx as u32));
self.instructions
.push(Instruction::Call(self.rt.retain_i32));
self.instructions
.push(Instruction::LocalSet(tmp_base + arg_idx as u32));
}
}
self.instructions
.push(Instruction::Call(self.rt.collect_end));
for (arg_idx, arg) in args.iter().enumerate() {
if self.expr_is_heap_ptr_spanned(arg) {
self.instructions
.push(Instruction::LocalGet(tmp_base + arg_idx as u32));
self.instructions
.push(Instruction::Call(self.rt.rebase_i32));
self.instructions
.push(Instruction::LocalSet(tmp_base + arg_idx as u32));
}
}
}
}