use std::collections::VecDeque;
use std::mem::size_of;
use std::ops::RangeInclusive;
use std::rc::Rc;
use bstr::ByteSlice;
use itertools::{Itertools, Position};
use rustc_hash::FxHashMap;
use walrus::ValType::{I32, I64};
use walrus::ir::ExtendedLoad::ZeroExtend;
use walrus::ir::{
BinaryOp, InstrSeqId, InstrSeqType, LoadKind, MemArg, StoreKind, UnaryOp,
};
use walrus::{FunctionId, InstrSeqBuilder, ValType};
use crate::compiler::ir::{
Expr, ExprId, ForIn, ForOf, IR, Iterable, MatchAnchor, PatternIdx,
Quantifier,
};
use crate::compiler::{
FieldAccess, ForVars, LiteralId, OfExprTuple, OfPatternSet, PatternId,
RegexpId, RuleId, RuleInfo, Var,
};
use crate::scanner::RuntimeObjectHandle;
use crate::string_pool::{BStringPool, StringPool};
use crate::symbols::Symbol;
use crate::types::Value::Const;
use crate::types::{Array, Map, Type, TypeValue};
use crate::utils::cast;
use crate::wasm;
use crate::wasm::builder::WasmModuleBuilder;
use crate::wasm::string::RuntimeString;
use crate::wasm::{
LOOKUP_INDEXES_END, LOOKUP_INDEXES_START, MATCHING_RULES_BITMAP_BASE,
VARS_STACK_START, WasmSymbols,
};
macro_rules! emit_operands {
($ctx:ident, $ir:ident, $lhs:expr, $rhs:expr, $instr:ident) => {{
let lhs = $lhs;
let rhs = $rhs;
emit_expr($ctx, $ir, lhs, $instr);
let mut lhs_type = $ir.get(lhs).ty();
let mut rhs_type = $ir.get(rhs).ty();
if lhs_type == Type::Integer && rhs_type == Type::Float {
$instr.unop(UnaryOp::F64ConvertSI64);
lhs_type = Type::Float;
}
if lhs_type == Type::Bool {
$instr.unop(UnaryOp::I64ExtendUI32);
}
emit_expr($ctx, $ir, rhs, $instr);
if lhs_type == Type::Float && rhs_type == Type::Integer {
$instr.unop(UnaryOp::F64ConvertSI64);
rhs_type = Type::Float;
}
if rhs_type == Type::Bool {
$instr.unop(UnaryOp::I64ExtendUI32);
}
(lhs_type, rhs_type)
}};
}
macro_rules! emit_arithmetic_op {
($ctx:ident, $ir:ident, $operands:expr, $is_float:expr, $int_op:tt, $float_op:tt, $instr:ident) => {{
let mut operands = $operands.iter();
let first_operand = operands.next().unwrap();
emit_expr($ctx, $ir, *first_operand, $instr);
if $is_float && matches!($ir.get(*first_operand).ty(), Type::Integer) {
$instr.unop(UnaryOp::F64ConvertSI64);
}
while let Some(operand) = operands.next() {
emit_expr($ctx, $ir, *operand, $instr);
if $is_float {
if matches!($ir.get(*operand).ty(), Type::Integer) {
$instr.unop(UnaryOp::F64ConvertSI64);
}
$instr.binop(BinaryOp::$float_op);
} else {
$instr.binop(BinaryOp::$int_op);
}
}
}};
}
macro_rules! emit_comparison_op {
($ctx:ident, $ir:ident, $lhs:expr, $rhs:expr, $int_op:tt, $float_op:tt, $str_op:expr, $instr:ident) => {{
match emit_operands!($ctx, $ir, $lhs, $rhs, $instr) {
(Type::Integer, Type::Integer)
| (Type::Bool, Type::Bool)
| (Type::Bool, Type::Integer)
| (Type::Integer, Type::Bool) => {
$instr.binop(BinaryOp::$int_op);
}
(Type::Float, Type::Float) => {
$instr.binop(BinaryOp::$float_op);
}
(Type::String, Type::String) => {
$instr.call($ctx.function_id($str_op));
}
_ => unreachable!(),
};
}};
}
macro_rules! emit_shift_op {
($ctx:ident, $ir:ident, $lhs:expr, $rhs:expr, $int_op:tt, $instr:ident) => {{
match emit_operands!($ctx, $ir, $lhs, $rhs, $instr) {
(Type::Integer, Type::Integer) => {
$instr.local_set($ctx.wasm_symbols.i64_tmp_b);
$instr.local_set($ctx.wasm_symbols.i64_tmp_a);
$instr.local_get($ctx.wasm_symbols.i64_tmp_b);
$instr.i64_const(64);
$instr.binop(BinaryOp::I64LtS);
$instr.if_else(
I64,
|then_| {
then_.local_get($ctx.wasm_symbols.i64_tmp_a);
then_.local_get($ctx.wasm_symbols.i64_tmp_b);
then_.binop(BinaryOp::$int_op);
},
|else_| {
else_.i64_const(0);
},
)
}
_ => unreachable!(),
};
}};
}
macro_rules! emit_bitwise_op {
($ctx:ident, $ir:ident, $lhs:expr, $rhs:expr, $int_op:tt, $instr:ident) => {{
match emit_operands!($ctx, $ir, $lhs, $rhs, $instr) {
(Type::Integer, Type::Integer) => $instr.binop(BinaryOp::$int_op),
_ => unreachable!(),
};
}};
}
type ExceptionHandler = Box<dyn Fn(&mut EmitContext, &mut InstrSeqBuilder)>;
pub(crate) struct EmitContext<'a> {
pub current_rule: &'a RuleInfo,
pub wasm_symbols: &'a WasmSymbols,
pub wasm_exports: &'a FxHashMap<String, FunctionId>,
pub regexp_pool: &'a mut StringPool<RegexpId>,
pub lit_pool: &'a mut BStringPool<LiteralId>,
pub exception_handler_stack: Vec<(InstrSeqId, ExceptionHandler)>,
pub emit_search_for_pattern_stack: Vec<bool>,
pub(crate) lookup_list: Vec<(i32, bool)>,
}
impl EmitContext<'_> {
pub fn function_id(&self, fn_mangled_name: &str) -> FunctionId {
*self.wasm_exports.get(fn_mangled_name).unwrap_or_else(|| {
panic!("can't find function `{fn_mangled_name}`")
})
}
pub fn pattern_id(&self, index: PatternIdx) -> PatternId {
self.current_rule.patterns[index.as_usize()].pattern_id
}
}
pub(crate) fn emit_rule_condition(
ctx: &mut EmitContext,
ir: &IR,
rule_id: RuleId,
condition: ExprId,
builder: &mut WasmModuleBuilder,
) {
let mut instr = builder.start_rule(rule_id, ctx.current_rule.is_global);
ctx.emit_search_for_pattern_stack.push(true);
catch_undef(
ctx,
I32,
&mut instr,
|ctx, instr| {
emit_bool_expr(ctx, ir, condition, instr);
},
|_, instr| {
instr.i32_const(0);
},
);
ctx.emit_search_for_pattern_stack.pop();
assert!(ctx.emit_search_for_pattern_stack.is_empty());
builder.finish_rule();
}
fn emit_expr(
ctx: &mut EmitContext,
ir: &IR,
expr: ExprId,
instr: &mut InstrSeqBuilder,
) {
match ir.get(expr) {
Expr::Const(type_value) => match type_value {
TypeValue::Integer { value: Const(value), .. } => {
instr.i64_const(*value);
}
TypeValue::Float { value: Const(value) } => {
instr.f64_const(*value);
}
TypeValue::Bool { value: Const(value) } => {
instr.i32_const((*value).into());
}
TypeValue::String { value: Const(value), .. } => {
let literal_id = ctx.lit_pool.get_or_intern(value.as_bstr());
instr
.i64_const(RuntimeString::Literal(literal_id).into_wasm());
}
TypeValue::Regexp(Some(regexp)) => {
let re_id = ctx.regexp_pool.get_or_intern(regexp.as_str());
instr.i32_const(re_id.into());
}
t => unreachable!("{:?}", t),
},
Expr::Filesize => {
let tmp = ctx.wasm_symbols.i64_tmp_a;
instr.global_get(ctx.wasm_symbols.filesize);
instr.local_tee(tmp);
instr.i64_const(0);
instr.binop(BinaryOp::I64LtS);
instr.if_else(
I64,
|then_| throw_undef(ctx, then_),
|else_| {
else_.local_get(tmp);
},
);
}
Expr::Symbol(symbol) => {
match symbol.as_ref() {
Symbol::Rule { rule_id, .. } => {
emit_check_for_rule_match(ctx, *rule_id, instr);
}
Symbol::Var { var, .. } => {
if !matches!(var.ty(), Type::Func) {
load_var(ctx, instr, *var);
}
}
Symbol::Field { index, is_root, type_value, .. } => {
let index: i32 = (*index).try_into().unwrap();
match type_value {
TypeValue::Integer { .. } => {
ctx.lookup_list.push((index, *is_root));
emit_lookup_integer(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Float { .. } => {
ctx.lookup_list.push((index, *is_root));
emit_lookup_float(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Bool { .. } => {
ctx.lookup_list.push((index, *is_root));
emit_lookup_bool(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::String { .. } => {
ctx.lookup_list.push((index, *is_root));
emit_lookup_string(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Struct(_)
| TypeValue::Array(_)
| TypeValue::Map(_) => {
ctx.lookup_list.push((index, *is_root));
emit_lookup_object(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Func(func) => {
if let Some((_, true)) = ctx.lookup_list.first()
&& func.is_method()
{
emit_lookup_object(ctx, instr);
}
ctx.lookup_list.clear();
}
TypeValue::Regexp(_) => {
unreachable!();
}
TypeValue::Unknown => {
unreachable!();
}
}
}
Symbol::Func(_) => {
unreachable!()
}
}
}
Expr::PatternMatch { .. } | Expr::PatternMatchVar { .. } => {
emit_pattern_match(ctx, ir, expr, instr);
}
Expr::PatternCount { .. } | Expr::PatternCountVar { .. } => {
emit_pattern_count(ctx, ir, expr, instr);
}
Expr::PatternOffset { .. } | Expr::PatternOffsetVar { .. } => {
emit_pattern_offset(ctx, ir, expr, instr);
}
Expr::PatternLength { .. } | Expr::PatternLengthVar { .. } => {
emit_pattern_length(ctx, ir, expr, instr);
}
Expr::FieldAccess(field_access) => {
emit_field_access(ctx, ir, field_access, instr);
}
Expr::Defined { operand } => {
emit_defined(ctx, ir, *operand, instr);
}
Expr::Not { operand } => {
emit_not(ctx, ir, *operand, instr);
}
Expr::And { operands } => {
ctx.emit_search_for_pattern_stack.push(true);
emit_and(ctx, ir, operands.as_slice(), instr);
ctx.emit_search_for_pattern_stack.pop();
}
Expr::Or { operands } => {
ctx.emit_search_for_pattern_stack.push(true);
emit_or(ctx, ir, operands.as_slice(), instr);
ctx.emit_search_for_pattern_stack.pop();
}
Expr::Minus { operand, is_float } => {
if *is_float {
emit_expr(ctx, ir, *operand, instr);
instr.unop(UnaryOp::F64Neg);
} else {
instr.i64_const(0);
emit_expr(ctx, ir, *operand, instr);
instr.binop(BinaryOp::I64Sub);
}
}
Expr::BitwiseNot { operand } => {
emit_expr(ctx, ir, *operand, instr);
instr.i64_const(-1);
instr.binop(BinaryOp::I64Xor);
}
Expr::Add { operands, is_float } => {
emit_arithmetic_op!(
ctx, ir, operands, *is_float, I64Add, F64Add, instr
);
}
Expr::Sub { operands, is_float } => {
emit_arithmetic_op!(
ctx, ir, operands, *is_float, I64Sub, F64Sub, instr
);
}
Expr::Mul { operands, is_float } => {
emit_arithmetic_op!(
ctx, ir, operands, *is_float, I64Mul, F64Mul, instr
);
}
Expr::Div { operands, .. } => {
emit_div(ctx, ir, operands.as_slice(), instr)
}
Expr::Mod { operands } => {
emit_mod(ctx, ir, operands.as_slice(), instr)
}
Expr::Shl { lhs, rhs } => {
emit_shift_op!(ctx, ir, *lhs, *rhs, I64Shl, instr);
}
Expr::Shr { lhs, rhs } => {
emit_shift_op!(ctx, ir, *lhs, *rhs, I64ShrS, instr);
}
Expr::BitwiseAnd { lhs, rhs } => {
emit_bitwise_op!(ctx, ir, *lhs, *rhs, I64And, instr);
}
Expr::BitwiseOr { lhs, rhs } => {
emit_bitwise_op!(ctx, ir, *lhs, *rhs, I64Or, instr);
}
Expr::BitwiseXor { lhs, rhs } => {
emit_bitwise_op!(ctx, ir, *lhs, *rhs, I64Xor, instr);
}
Expr::Eq { lhs, rhs } => {
emit_comparison_op!(
ctx,
ir,
*lhs,
*rhs,
I64Eq,
F64Eq,
wasm::export__str_eq.mangled_name,
instr
);
}
Expr::Ne { lhs, rhs } => {
emit_comparison_op!(
ctx,
ir,
*lhs,
*rhs,
I64Ne,
F64Ne,
wasm::export__str_ne.mangled_name,
instr
);
}
Expr::Lt { lhs, rhs } => {
emit_comparison_op!(
ctx,
ir,
*lhs,
*rhs,
I64LtS,
F64Lt,
wasm::export__str_lt.mangled_name,
instr
);
}
Expr::Gt { lhs, rhs } => {
emit_comparison_op!(
ctx,
ir,
*lhs,
*rhs,
I64GtS,
F64Gt,
wasm::export__str_gt.mangled_name,
instr
);
}
Expr::Le { lhs, rhs } => {
emit_comparison_op!(
ctx,
ir,
*lhs,
*rhs,
I64LeS,
F64Le,
wasm::export__str_le.mangled_name,
instr
);
}
Expr::Ge { lhs, rhs } => {
emit_comparison_op!(
ctx,
ir,
*lhs,
*rhs,
I64GeS,
F64Ge,
wasm::export__str_ge.mangled_name,
instr
);
}
Expr::Contains { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr.call(
ctx.function_id(wasm::export__str_contains.mangled_name),
);
}
Expr::IContains { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr.call(
ctx.function_id(wasm::export__str_icontains.mangled_name),
);
}
Expr::StartsWith { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr.call(
ctx.function_id(wasm::export__str_startswith.mangled_name),
);
}
Expr::IStartsWith { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr.call(
ctx.function_id(wasm::export__str_istartswith.mangled_name),
);
}
Expr::EndsWith { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr.call(
ctx.function_id(wasm::export__str_endswith.mangled_name),
);
}
Expr::IEndsWith { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr.call(
ctx.function_id(wasm::export__str_iendswith.mangled_name),
);
}
Expr::IEquals { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr
.call(ctx.function_id(wasm::export__str_iequals.mangled_name));
}
Expr::Matches { lhs, rhs } => {
emit_operands!(ctx, ir, *lhs, *rhs, instr);
instr
.call(ctx.function_id(wasm::export__str_matches.mangled_name));
}
Expr::Lookup(lookup) => {
emit_expr(ctx, ir, lookup.primary, instr);
emit_expr(ctx, ir, lookup.index, instr);
match ir.get(lookup.primary).type_value() {
TypeValue::Array(array) => {
emit_array_indexing(ctx, instr, &array);
}
TypeValue::Map(map) => {
emit_map_lookup(ctx, instr, map);
}
_ => unreachable!(),
};
}
Expr::OfPatternSet(of_pattern_set) => {
emit_of_pattern_set(ctx, ir, of_pattern_set, instr);
}
Expr::OfExprTuple(of_expr_tuple) => {
emit_of_expr_tuple(ctx, ir, of_expr_tuple, instr)
}
Expr::ForOf(for_of) => {
emit_for_of_pattern_set(ctx, ir, for_of, instr);
}
Expr::ForIn(for_in) => match &for_in.iterable {
Iterable::Range(_) => {
emit_for_in_range(ctx, ir, for_in, instr);
}
Iterable::ExprTuple(_) => {
emit_for_in_expr_tuple(ctx, ir, for_in, instr);
}
Iterable::Expr(_) => {
emit_for_in_expr(ctx, ir, for_in, instr);
}
},
Expr::FuncCall(func_call) => {
if let Some(obj) = func_call.object {
emit_expr(ctx, ir, obj, instr);
}
for expr in func_call.args.iter() {
emit_expr(ctx, ir, *expr, instr);
}
if func_call.signature().result_may_be_undef() {
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(func_call.mangled_name()),
);
} else {
instr.call(ctx.function_id(func_call.mangled_name()));
}
}
Expr::With(with) => {
emit_with(ctx, ir, with.declarations.as_slice(), with.body, instr);
}
}
}
fn emit_defined(
ctx: &mut EmitContext,
ir: &IR,
operand: ExprId,
instr: &mut InstrSeqBuilder,
) {
catch_undef(
ctx,
I32,
instr,
|ctx, instr| {
emit_bool_expr(ctx, ir, operand, instr);
instr.drop();
instr.i32_const(1);
},
|_, instr| {
instr.i32_const(0);
},
);
}
fn emit_not(
ctx: &mut EmitContext,
ir: &IR,
operand: ExprId,
instr: &mut InstrSeqBuilder,
) {
emit_bool_expr(ctx, ir, operand, instr);
instr.unop(UnaryOp::I32Eqz);
}
fn emit_and(
ctx: &mut EmitContext,
ir: &IR,
operands: &[ExprId],
instr: &mut InstrSeqBuilder,
) {
catch_undef(
ctx,
I32,
instr,
|ctx, instr| {
let block_id = instr.id();
for (position, operand) in operands.iter().with_position() {
emit_bool_expr(ctx, ir, *operand, instr);
if matches!(position, Position::First | Position::Middle) {
instr.if_else(
None,
|_| {},
|else_| {
else_.i32_const(0);
else_.br(block_id);
},
);
}
}
},
|_, instr| {
instr.i32_const(0); },
);
}
fn emit_or(
ctx: &mut EmitContext,
ir: &IR,
operands: &[ExprId],
instr: &mut InstrSeqBuilder,
) {
instr.block(
I32, |block| {
let block_id = block.id();
for (position, operand) in operands.iter().with_position() {
catch_undef(
ctx,
I32,
block,
|ctx, instr| {
emit_bool_expr(ctx, ir, *operand, instr);
},
|_, instr| {
instr.i32_const(0);
},
);
if matches!(position, Position::First | Position::Middle) {
block.if_else(
None,
|then_| {
then_.i32_const(1);
then_.br(block_id);
},
|_| {},
);
}
}
},
);
}
fn emit_div(
ctx: &mut EmitContext,
ir: &IR,
operands: &[ExprId],
instr: &mut InstrSeqBuilder,
) {
let mut operands = operands.iter();
let first_operand = operands.next().unwrap();
let mut is_float = matches!(ir.get(*first_operand).ty(), Type::Float);
emit_expr(ctx, ir, *first_operand, instr);
for operand in operands {
let operand_ty = ir.get(*operand).ty();
if !is_float && matches!(operand_ty, Type::Float) {
instr.unop(UnaryOp::F64ConvertSI64);
is_float = true;
}
emit_expr(ctx, ir, *operand, instr);
if is_float && matches!(operand_ty, Type::Integer) {
instr.unop(UnaryOp::F64ConvertSI64);
}
if is_float {
instr.binop(BinaryOp::F64Div);
} else {
throw_undef_if_zero(ctx, instr);
instr.binop(BinaryOp::I64DivS);
}
}
}
fn emit_mod(
ctx: &mut EmitContext,
ir: &IR,
operands: &[ExprId],
instr: &mut InstrSeqBuilder,
) {
let mut operands = operands.iter();
let first_operand = operands.next().unwrap();
emit_expr(ctx, ir, *first_operand, instr);
for operand in operands {
emit_expr(ctx, ir, *operand, instr);
throw_undef_if_zero(ctx, instr);
instr.binop(BinaryOp::I64RemS);
}
}
fn emit_field_access(
ctx: &mut EmitContext,
ir: &IR,
field_access: &FieldAccess,
instr: &mut InstrSeqBuilder,
) {
for operand in field_access.operands.iter().dropping_back(1) {
if let Expr::Symbol(symbol) = ir.get(*operand)
&& let Symbol::Field { index, is_root, .. } = symbol.as_ref()
{
ctx.lookup_list.push((*index as i32, *is_root));
continue;
}
emit_expr(ctx, ir, *operand, instr);
}
emit_expr(ctx, ir, *field_access.operands.last().unwrap(), instr);
}
fn emit_lazy_call_to_search_for_patterns(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
) {
if *ctx.emit_search_for_pattern_stack.last().unwrap() {
instr.global_get(ctx.wasm_symbols.pattern_search_done);
instr.if_else(
None,
|_then| {
},
|_else| {
_else
.call(ctx.function_id(
wasm::export__search_for_patterns.mangled_name,
));
},
);
let top = ctx.emit_search_for_pattern_stack.last_mut().unwrap();
*top = false;
}
}
fn emit_pattern_match(
ctx: &mut EmitContext,
ir: &IR,
expr: ExprId,
instr: &mut InstrSeqBuilder,
) {
emit_lazy_call_to_search_for_patterns(ctx, instr);
let anchor = match ir.get(expr) {
Expr::PatternMatch { pattern, anchor } => {
instr.i32_const(ctx.pattern_id(*pattern).into());
anchor
}
Expr::PatternMatchVar { symbol, anchor } => {
if let Symbol::Var { var, .. } = symbol.as_ref() {
load_var(ctx, instr, *var);
instr.unop(UnaryOp::I32WrapI64);
} else {
unreachable!()
}
anchor
}
_ => unreachable!(),
};
match anchor {
MatchAnchor::None => {
instr.call(ctx.wasm_symbols.check_for_pattern_match);
}
MatchAnchor::At(offset) => {
emit_expr(ctx, ir, *offset, instr);
instr.call(
ctx.function_id(wasm::export__is_pat_match_at.mangled_name),
);
}
MatchAnchor::In(range) => {
emit_expr(ctx, ir, range.lower_bound, instr);
emit_expr(ctx, ir, range.upper_bound, instr);
instr.call(
ctx.function_id(wasm::export__is_pat_match_in.mangled_name),
);
}
}
}
fn emit_pattern_count(
ctx: &mut EmitContext,
ir: &IR,
expr: ExprId,
instr: &mut InstrSeqBuilder,
) {
emit_lazy_call_to_search_for_patterns(ctx, instr);
let range = match ir.get(expr) {
Expr::PatternCount { pattern, range } => {
instr.i32_const(ctx.pattern_id(*pattern).into());
range
}
Expr::PatternCountVar { symbol, range } => {
match symbol.as_ref() {
Symbol::Var { var, .. } => {
load_var(ctx, instr, *var);
instr.unop(UnaryOp::I32WrapI64);
}
_ => unreachable!(),
}
range
}
_ => unreachable!(),
};
match range {
Some(range) => {
emit_expr(ctx, ir, range.lower_bound, instr);
emit_expr(ctx, ir, range.upper_bound, instr);
instr.call(
ctx.function_id(wasm::export__pat_matches_in.mangled_name),
);
}
None => {
instr
.call(ctx.function_id(wasm::export__pat_matches.mangled_name));
}
}
}
fn emit_pattern_offset(
ctx: &mut EmitContext,
ir: &IR,
expr: ExprId,
instr: &mut InstrSeqBuilder,
) {
emit_lazy_call_to_search_for_patterns(ctx, instr);
let index = match ir.get(expr) {
Expr::PatternOffset { pattern, index } => {
instr.i32_const(ctx.pattern_id(*pattern).into());
index
}
Expr::PatternOffsetVar { symbol, index } => {
match symbol.as_ref() {
Symbol::Var { var, .. } => {
load_var(ctx, instr, *var);
instr.unop(UnaryOp::I32WrapI64);
}
_ => unreachable!(),
}
index
}
_ => unreachable!(),
};
match index {
Some(index) => {
emit_expr(ctx, ir, *index, instr);
}
None => {
instr.i64_const(1);
}
}
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(wasm::export__pat_offset.mangled_name),
)
}
fn emit_pattern_length(
ctx: &mut EmitContext,
ir: &IR,
expr: ExprId,
instr: &mut InstrSeqBuilder,
) {
emit_lazy_call_to_search_for_patterns(ctx, instr);
let index = match ir.get(expr) {
Expr::PatternLength { pattern, index } => {
instr.i32_const(ctx.pattern_id(*pattern).into());
index
}
Expr::PatternLengthVar { symbol, index } => {
match symbol.as_ref() {
Symbol::Var { var, .. } => {
load_var(ctx, instr, *var);
instr.unop(UnaryOp::I32WrapI64);
}
_ => unreachable!(),
}
index
}
_ => unreachable!(),
};
match index {
Some(index) => {
emit_expr(ctx, ir, *index, instr);
}
None => {
instr.i64_const(1);
}
}
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(wasm::export__pat_length.mangled_name),
)
}
fn emit_check_for_rule_match(
ctx: &mut EmitContext,
rule_id: RuleId,
instr: &mut InstrSeqBuilder,
) {
instr.i32_const(rule_id.0 / 8);
instr.load(
ctx.wasm_symbols.main_memory,
LoadKind::I32_8 { kind: ZeroExtend },
MemArg {
align: size_of::<i8>() as u32,
offset: MATCHING_RULES_BITMAP_BASE as u64,
},
);
instr.i32_const(1 << (rule_id.0 % 8));
instr.binop(BinaryOp::I32And);
instr.i32_const(rule_id.0 % 8);
instr.binop(BinaryOp::I32ShrU);
}
fn emit_array_indexing(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
array: &Rc<Array>,
) {
let func = match array.as_ref() {
Array::Integers(_) => &wasm::export__array_indexing_integer,
Array::Floats(_) => &wasm::export__array_indexing_float,
Array::Bools(_) => &wasm::export__array_indexing_bool,
Array::Strings(_) => &wasm::export__array_indexing_string,
Array::Structs(_) => &wasm::export__array_indexing_struct,
};
emit_call_and_handle_undef(ctx, instr, ctx.function_id(func.mangled_name));
}
fn emit_map_lookup_by_index(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
map: &Rc<Map>,
) {
let func = match map.as_ref() {
Map::IntegerKeys { deputy, .. } => {
match deputy.as_ref().unwrap().ty() {
Type::Integer => {
wasm::export__map_lookup_by_index_integer_integer
.mangled_name
}
Type::String => {
wasm::export__map_lookup_by_index_integer_string
.mangled_name
}
Type::Float => {
wasm::export__map_lookup_by_index_integer_float
.mangled_name
}
Type::Bool => {
wasm::export__map_lookup_by_index_integer_bool.mangled_name
}
Type::Struct => {
wasm::export__map_lookup_by_index_integer_struct
.mangled_name
}
_ => unreachable!(),
}
}
Map::StringKeys { deputy, .. } => {
match deputy.as_ref().unwrap().ty() {
Type::Integer => {
wasm::export__map_lookup_by_index_string_integer
.mangled_name
}
Type::String => {
wasm::export__map_lookup_by_index_string_string
.mangled_name
}
Type::Float => {
wasm::export__map_lookup_by_index_string_float.mangled_name
}
Type::Bool => {
wasm::export__map_lookup_by_index_string_bool.mangled_name
}
Type::Struct => {
wasm::export__map_lookup_by_index_string_struct
.mangled_name
}
_ => unreachable!(),
}
}
};
instr.call(ctx.function_id(func));
}
fn emit_map_lookup(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
map: Rc<Map>,
) {
match map.as_ref() {
Map::IntegerKeys { deputy, .. } => {
emit_map_integer_key_lookup(ctx, instr, deputy.as_ref().unwrap())
}
Map::StringKeys { deputy, .. } => {
emit_map_string_key_lookup(ctx, instr, deputy.as_ref().unwrap())
}
}
}
fn emit_map_integer_key_lookup(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
map_value: &TypeValue,
) {
let func = match map_value.ty() {
Type::Integer => &wasm::export__map_lookup_integer_integer,
Type::Float => &wasm::export__map_lookup_integer_float,
Type::Bool => &wasm::export__map_lookup_integer_bool,
Type::Struct => &wasm::export__map_lookup_integer_struct,
Type::String => &wasm::export__map_lookup_integer_string,
_ => unreachable!(),
};
emit_call_and_handle_undef(ctx, instr, ctx.function_id(func.mangled_name));
}
fn emit_map_string_key_lookup(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
map_value: &TypeValue,
) {
let func = match map_value.ty() {
Type::Integer => &wasm::export__map_lookup_string_integer,
Type::Float => &wasm::export__map_lookup_string_float,
Type::Bool => &wasm::export__map_lookup_string_bool,
Type::Struct => &wasm::export__map_lookup_string_struct,
Type::String => &wasm::export__map_lookup_string_string,
_ => unreachable!(),
};
emit_call_and_handle_undef(ctx, instr, ctx.function_id(func.mangled_name));
}
fn consecutive_ranges(
iter: impl Iterator<Item = PatternId>,
) -> impl Iterator<Item = RangeInclusive<PatternId>> {
iter.peekable().batching(|it| {
let start = it.next()?;
let mut end = start;
while let Some(&next) = it.peek() {
if next.0 == end.0 + 1 {
end = next;
it.next();
} else {
break;
}
}
Some(start..=end)
})
}
fn emit_of_pattern_set(
ctx: &mut EmitContext,
ir: &IR,
of: &OfPatternSet,
instr: &mut InstrSeqBuilder,
) {
emit_lazy_call_to_search_for_patterns(ctx, instr);
let pattern_ids = of
.items
.iter()
.map(|pattern_idx: &PatternIdx| ctx.pattern_id(*pattern_idx))
.sorted();
let mut ranges = consecutive_ranges(pattern_ids).collect::<Vec<_>>();
match (&of.quantifier, &of.items, &of.anchor) {
(Quantifier::Any, _, MatchAnchor::None) => {
instr.block(I32, |instr| {
let block = instr.id();
for (position, range) in ranges.iter().with_position() {
let start: i32 = (*range.start()).into();
let end: i32 = (*range.end()).into();
if end == start {
instr.i32_const(start);
instr.call(ctx.wasm_symbols.check_for_pattern_match);
} else {
instr.i32_const(start);
instr.i32_const(end);
instr.i64_const(1);
instr.call(ctx.function_id(
wasm::export__pat_range_match.mangled_name,
));
}
if matches!(position, Position::First | Position::Middle) {
instr.if_else(
None,
|then_| {
then_.i32_const(1);
then_.br(block);
},
|_else| {},
);
}
}
});
}
(Quantifier::All, _, MatchAnchor::None) => {
instr.block(I32, |instr| {
let block = instr.id();
for (position, range) in ranges.iter().with_position() {
let start: i32 = (*range.start()).into();
let end: i32 = (*range.end()).into();
if end == start {
instr.i32_const(start);
instr.call(ctx.wasm_symbols.check_for_pattern_match);
} else {
instr.i32_const(start);
instr.i32_const(end);
instr.i64_const(end as i64 - start as i64 + 1);
instr.call(ctx.function_id(
wasm::export__pat_range_match.mangled_name,
));
}
if !matches!(position, Position::Only | Position::Last) {
instr.if_else(
None,
|_| {},
|else_| {
else_.i32_const(0);
else_.br(block);
},
);
}
}
});
}
(Quantifier::Expr(quantifier), _, MatchAnchor::None)
if ranges.len() == 1 =>
{
let range = ranges.pop().unwrap();
let start: i32 = (*range.start()).into();
let end: i32 = (*range.end()).into();
instr.i32_const(start);
instr.i32_const(end);
emit_expr(ctx, ir, *quantifier, instr);
instr.call(
ctx.function_id(wasm::export__pat_range_match.mangled_name),
);
}
_ => emit_of_pattern_set_with_loop(ctx, ir, of, instr),
}
}
fn emit_of_pattern_set_with_loop(
ctx: &mut EmitContext,
ir: &IR,
of: &OfPatternSet,
instr: &mut InstrSeqBuilder,
) {
debug_assert!(!of.items.is_empty());
let num_patterns = of.items.len();
let mut patterns = of.items.iter();
let next_pattern_id = of.next_pattern_var;
emit_for(
ctx,
ir,
&of.for_vars,
&of.quantifier,
|ctx, instr, n, _| {
set_var(ctx, instr, n, |_, instr| {
instr.i64_const(num_patterns as i64);
});
},
|ctx, instr, i| {
set_var(ctx, instr, next_pattern_id, |ctx, instr| {
load_var(ctx, instr, i);
emit_switch(ctx, I64, instr, |ctx, instr| {
if let Some(pattern) = patterns.next() {
instr.i64_const(ctx.pattern_id(*pattern).into());
return true;
}
false
});
});
},
|ctx, instr| {
load_var(ctx, instr, next_pattern_id);
instr.unop(UnaryOp::I32WrapI64);
match &of.anchor {
MatchAnchor::None => {
instr.call(ctx.wasm_symbols.check_for_pattern_match);
}
MatchAnchor::At(offset) => {
emit_expr(ctx, ir, *offset, instr);
instr.call(ctx.function_id(
wasm::export__is_pat_match_at.mangled_name,
));
}
MatchAnchor::In(range) => {
emit_expr(ctx, ir, range.lower_bound, instr);
emit_expr(ctx, ir, range.upper_bound, instr);
instr.call(ctx.function_id(
wasm::export__is_pat_match_in.mangled_name,
));
}
}
},
|_, _, _| {},
instr,
);
}
fn emit_of_expr_tuple(
ctx: &mut EmitContext,
ir: &IR,
of: &OfExprTuple,
instr: &mut InstrSeqBuilder,
) {
let num_expressions = of.items.len();
let mut expressions = of.items.iter();
let next_item = of.next_expr_var;
emit_for(
ctx,
ir,
&of.for_vars,
&of.quantifier,
|ctx, instr, n, _| {
set_var(ctx, instr, n, |_, instr| {
instr.i64_const(num_expressions as i64);
});
},
|ctx, instr, i| {
set_var(ctx, instr, next_item, |ctx, instr| {
load_var(ctx, instr, i);
emit_switch(
ctx,
next_item.ty().into(),
instr,
|ctx, instr| {
if let Some(expr) = expressions.next() {
emit_bool_expr(ctx, ir, *expr, instr);
return true;
}
false
},
);
});
},
|ctx, instr| {
load_var(ctx, instr, next_item);
},
|_, _, _| {},
instr,
);
}
fn emit_for_of_pattern_set(
ctx: &mut EmitContext,
ir: &IR,
for_of: &ForOf,
instr: &mut InstrSeqBuilder,
) {
let num_patterns = for_of.pattern_set.len();
let mut patterns = for_of.pattern_set.iter();
let next_pattern_id = for_of.variable;
emit_for(
ctx,
ir,
&for_of.for_vars,
&for_of.quantifier,
|ctx, instr, n, _| {
set_var(ctx, instr, n, |_, instr| {
instr.i64_const(num_patterns as i64);
});
},
|ctx, instr, i| {
set_var(ctx, instr, next_pattern_id, |ctx, instr| {
load_var(ctx, instr, i);
emit_switch(ctx, I64, instr, |ctx, instr| {
if let Some(pattern) = patterns.next() {
instr.i64_const(ctx.pattern_id(*pattern).into());
return true;
}
false
});
});
},
|ctx, instr| {
emit_bool_expr(ctx, ir, for_of.body, instr);
},
|_, _, _| {},
instr,
);
}
fn emit_for_in_range(
ctx: &mut EmitContext,
ir: &IR,
for_in: &ForIn,
instr: &mut InstrSeqBuilder,
) {
let range = cast!(&for_in.iterable, Iterable::Range);
assert_eq!(for_in.variables.len(), 1);
let next_item = for_in.variables[0];
emit_for(
ctx,
ir,
&for_in.for_vars,
&for_in.quantifier,
|ctx, instr, n, loop_end| {
set_var(ctx, instr, n, |ctx, instr| {
catch_undef(
ctx,
I64,
instr,
|ctx, instr| {
emit_expr(ctx, ir, range.upper_bound, instr);
emit_expr(ctx, ir, range.lower_bound, instr);
instr.local_tee(ctx.wasm_symbols.i64_tmp_a);
instr.binop(BinaryOp::I64Sub);
instr.i64_const(1);
instr.binop(BinaryOp::I64Add);
},
|_, instr| {
instr.i64_const(0);
},
)
});
load_var(ctx, instr, n);
instr.i64_const(0);
instr.binop(BinaryOp::I64LeS);
instr.if_else(
None,
|then_| {
then_.i32_const(0);
then_.br(loop_end);
},
|_| {},
);
set_var(ctx, instr, next_item, |ctx, instr| {
instr.local_get(ctx.wasm_symbols.i64_tmp_a);
});
},
|_, _, _| {},
|ctx, instr| {
emit_bool_expr(ctx, ir, for_in.body, instr);
},
|ctx, instr, _| {
incr_var(ctx, instr, next_item);
},
instr,
);
}
fn emit_for_in_expr(
ctx: &mut EmitContext,
ir: &IR,
for_in: &ForIn,
instr: &mut InstrSeqBuilder,
) {
let expr = cast!(for_in.iterable, Iterable::Expr);
match ir.get(expr).ty() {
Type::Array => {
emit_for_in_array(ctx, ir, for_in, instr);
}
Type::Map => {
emit_for_in_map(ctx, ir, for_in, instr);
}
_ => unreachable!(),
}
}
fn emit_for_in_array(
ctx: &mut EmitContext,
ir: &IR,
for_in: &ForIn,
instr: &mut InstrSeqBuilder,
) {
assert_eq!(for_in.variables.len(), 1);
let expr = cast!(for_in.iterable, Iterable::Expr);
let array = ir.get(expr).type_value().as_array();
let next_item = for_in.variables[0];
let array_var = for_in.iterable_var;
set_var(ctx, instr, array_var, |ctx, instr| {
emit_expr(ctx, ir, expr, instr);
});
emit_for(
ctx,
ir,
&for_in.for_vars,
&for_in.quantifier,
|ctx, instr, n, loop_end| {
set_var(ctx, instr, n, |ctx, instr| {
load_var(ctx, instr, array_var);
instr.call(
ctx.function_id(wasm::export__array_len.mangled_name),
);
});
load_var(ctx, instr, n);
instr.i64_const(0);
instr.binop(BinaryOp::I64LeS);
instr.if_else(
None,
|then_| {
then_.i32_const(0);
then_.br(loop_end);
},
|_| {},
);
},
|ctx, instr, i| {
set_var(ctx, instr, next_item, |ctx, instr| {
load_var(ctx, instr, array_var);
load_var(ctx, instr, i);
emit_array_indexing(ctx, instr, &array);
});
},
|ctx, instr| {
emit_bool_expr(ctx, ir, for_in.body, instr);
},
|_, _, _| {},
instr,
);
}
fn emit_for_in_map(
ctx: &mut EmitContext,
ir: &IR,
for_in: &ForIn,
instr: &mut InstrSeqBuilder,
) {
assert_eq!(for_in.variables.len(), 2);
let expr = cast!(for_in.iterable, Iterable::Expr);
let map = ir.get(expr).type_value().as_map();
let next_key = for_in.variables[0];
let next_val = for_in.variables[1];
let map_var = for_in.iterable_var;
set_var(ctx, instr, map_var, |ctx, instr| {
emit_expr(ctx, ir, expr, instr);
});
emit_for(
ctx,
ir,
&for_in.for_vars,
&for_in.quantifier,
|ctx, instr, n, loop_end| {
set_var(ctx, instr, n, |ctx, instr| {
load_var(ctx, instr, map_var);
instr
.call(ctx.function_id(wasm::export__map_len.mangled_name));
});
load_var(ctx, instr, n);
instr.i64_const(0);
instr.binop(BinaryOp::I64LeS);
instr.if_else(
None,
|then_| {
then_.i32_const(0);
then_.br(loop_end);
},
|_| {},
);
},
|ctx, instr, i| {
set_vars(ctx, instr, &[next_key, next_val], |ctx, instr| {
load_var(ctx, instr, map_var);
load_var(ctx, instr, i);
emit_map_lookup_by_index(ctx, instr, &map);
});
},
|ctx, instr| {
emit_bool_expr(ctx, ir, for_in.body, instr);
},
|_, _, _| {},
instr,
);
}
fn emit_for_in_expr_tuple(
ctx: &mut EmitContext,
ir: &IR,
for_in: &ForIn,
instr: &mut InstrSeqBuilder,
) {
assert_eq!(for_in.variables.len(), 1);
let expressions = cast!(&for_in.iterable, Iterable::ExprTuple);
let next_item = for_in.variables[0];
let num_expressions = expressions.len();
let mut expressions = expressions.iter();
emit_for(
ctx,
ir,
&for_in.for_vars,
&for_in.quantifier,
|ctx, instr, n, _| {
set_var(ctx, instr, n, |_, instr| {
instr.i64_const(num_expressions as i64);
});
},
|ctx, instr, i| {
catch_undef(
ctx,
None,
instr,
|ctx, instr| {
set_var(ctx, instr, next_item, |ctx, instr| {
load_var(ctx, instr, i);
emit_switch(
ctx,
next_item.ty().into(),
instr,
|ctx, instr| match expressions.next() {
Some(expr) => {
emit_expr(ctx, ir, *expr, instr);
true
}
None => false,
},
);
});
},
move |ctx, instr| {
set_var_undef(ctx, instr, next_item, true);
},
);
},
|ctx, instr| {
emit_bool_expr(ctx, ir, for_in.body, instr);
},
|_, _, _| {},
instr,
);
}
#[allow(clippy::too_many_arguments)]
fn emit_for<I, B, C, A>(
ctx: &mut EmitContext,
ir: &IR,
for_vars: &ForVars,
quantifier: &Quantifier,
loop_init: I,
before_cond: B,
body: C,
after_cond: A,
instr: &mut InstrSeqBuilder,
) where
I: FnOnce(&mut EmitContext, &mut InstrSeqBuilder, Var, InstrSeqId),
B: FnOnce(&mut EmitContext, &mut InstrSeqBuilder, Var),
C: FnOnce(&mut EmitContext, &mut InstrSeqBuilder),
A: FnOnce(&mut EmitContext, &mut InstrSeqBuilder, Var),
{
let n = for_vars.n;
let i = for_vars.i;
let incr_i_and_repeat =
|ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
n: Var,
i: Var,
loop_start: InstrSeqId| {
after_cond(ctx, instr, n);
incr_var(ctx, instr, i);
load_var(ctx, instr, i);
load_var(ctx, instr, n);
instr.binop(BinaryOp::I64LtS);
instr.br_if(loop_start);
};
instr.block(I32, |instr| {
let loop_end = instr.id();
loop_init(ctx, instr, n, loop_end);
set_var(ctx, instr, i, |_, instr| {
instr.i64_const(0);
});
let p = match quantifier {
Quantifier::Percentage(expr) => Some((expr, true)),
Quantifier::Expr(expr) => Some((expr, false)),
_ => None,
};
let (max_count, count) = match p {
Some((quantifier, is_percentage)) => {
let max_count = for_vars.max_count;
let count = for_vars.count;
set_var(ctx, instr, max_count, |ctx, instr| {
if is_percentage {
load_var(ctx, instr, n);
instr.unop(UnaryOp::F64ConvertSI64);
emit_expr(ctx, ir, *quantifier, instr);
instr.unop(UnaryOp::F64ConvertSI64);
instr.binop(BinaryOp::F64Mul);
instr.f64_const(100.0);
instr.binop(BinaryOp::F64Div);
instr.unop(UnaryOp::F64Ceil);
instr.unop(UnaryOp::I64TruncSF64);
} else {
emit_expr(ctx, ir, *quantifier, instr);
}
});
set_var(ctx, instr, count, |_, instr| {
instr.i64_const(0);
});
(max_count, count)
}
_ => (
Var::default(),
Var::default(),
),
};
instr.loop_(I32, |block| {
let loop_start = block.id();
before_cond(ctx, block, i);
catch_undef(
ctx,
I32,
block,
|ctx, block| {
body(ctx, block);
},
|_, instr| {
instr.i32_const(0);
},
);
match quantifier {
Quantifier::None => {
block.if_else(
I32,
|then_| {
then_.i32_const(0);
then_.br(loop_end);
},
|else_| {
incr_i_and_repeat(ctx, else_, n, i, loop_start);
else_.i32_const(1);
else_.br(loop_end);
},
);
}
Quantifier::All => {
block.if_else(
I32,
|then_| {
incr_i_and_repeat(ctx, then_, n, i, loop_start);
then_.i32_const(1);
then_.br(loop_end);
},
|else_| {
else_.i32_const(0);
else_.br(loop_end);
},
);
}
Quantifier::Any => {
block.if_else(
I32,
|then_| {
then_.i32_const(1);
then_.br(loop_end);
},
|else_| {
incr_i_and_repeat(ctx, else_, n, i, loop_start);
else_.i32_const(0);
else_.br(loop_end);
},
);
}
Quantifier::Percentage(_) | Quantifier::Expr(_) => {
block.if_else(
None,
|then_| {
incr_var(ctx, then_, count);
load_var(ctx, then_, count);
load_var(ctx, then_, max_count);
then_.binop(BinaryOp::I64GeS);
then_.if_else(
None,
|then_| {
load_var(ctx, then_, max_count);
then_.i64_const(0);
then_.binop(BinaryOp::I64Ne);
then_.br(loop_end);
},
|_| {},
);
},
|_| {},
);
incr_i_and_repeat(ctx, block, n, i, loop_start);
load_var(ctx, block, max_count);
block.unop(UnaryOp::I64Eqz);
}
}
});
});
}
fn emit_with(
ctx: &mut EmitContext,
ir: &IR,
declarations: &[(Var, ExprId)],
body: ExprId,
instr: &mut InstrSeqBuilder,
) {
for (id, expr) in declarations.iter().cloned() {
catch_undef(
ctx,
None,
instr,
|ctx, instr| {
set_var(ctx, instr, id, |ctx, instr| {
emit_expr(ctx, ir, expr, instr);
});
},
move |ctx, instr| {
set_var_undef(ctx, instr, id, true);
},
);
}
emit_expr(ctx, ir, body, instr)
}
fn emit_switch<F>(
ctx: &mut EmitContext,
ty: ValType,
instr: &mut InstrSeqBuilder,
mut branch_generator: F,
) where
F: FnMut(&mut EmitContext, &mut InstrSeqBuilder) -> bool,
{
instr.unop(UnaryOp::I32WrapI64);
instr.local_set(ctx.wasm_symbols.i32_tmp);
let mut branch_blocks = VecDeque::new();
let mut branch_expr = instr.dangling_instr_seq(ty);
while branch_generator(ctx, &mut branch_expr) {
branch_blocks.push_back(walrus::ir::Block { seq: branch_expr.id() });
branch_expr = instr.dangling_instr_seq(ty);
}
let outermost_block = instr.dangling_instr_seq(ty);
let outermost_block_id = outermost_block.id();
let switch_block = instr.dangling_instr_seq(None);
let switch_block_id = switch_block.id();
let mut block_ids = Vec::with_capacity(branch_blocks.len());
let mut block_id = switch_block_id;
block_ids.push(block_id);
let last_branch = branch_blocks.pop_back().unwrap();
while let Some(expr_block) = branch_blocks.pop_front() {
let mut branch = instr.dangling_instr_seq(None);
branch.instr(walrus::ir::Block { seq: block_id });
branch.instr(expr_block);
branch.br(outermost_block_id);
block_id = branch.id();
block_ids.push(block_id);
}
instr
.instr_seq(switch_block_id)
.block(None, |block| {
block.local_get(ctx.wasm_symbols.i32_tmp);
block.br_table(block_ids.into(), block.id());
})
.unreachable();
instr
.instr_seq(outermost_block_id)
.instr(walrus::ir::Block { seq: block_id })
.instr(last_branch);
instr.instr(walrus::ir::Block { seq: outermost_block_id });
}
fn set_var<B>(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
var: Var,
block: B,
) where
B: FnOnce(&mut EmitContext, &mut InstrSeqBuilder),
{
let (store_kind, alignment) = match var.ty() {
Type::Bool => (StoreKind::I32 { atomic: false }, size_of::<i32>()),
Type::Float => (StoreKind::F64, size_of::<f64>()),
Type::Integer
| Type::String
| Type::Struct
| Type::Array
| Type::Map
| Type::Func => (StoreKind::I64 { atomic: false }, size_of::<i64>()),
_ => unreachable!(),
};
instr.i32_const(var.index() * Var::mem_size());
block(ctx, instr);
instr.store(
ctx.wasm_symbols.main_memory,
store_kind,
MemArg { align: alignment as u32, offset: VARS_STACK_START as u64 },
);
set_var_undef(ctx, instr, var, false);
}
fn set_vars<B>(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
vars: &[Var],
block: B,
) where
B: FnOnce(&mut EmitContext, &mut InstrSeqBuilder),
{
block(ctx, instr);
for var in vars.iter().rev() {
match var.ty() {
Type::Bool => {
instr.local_set(ctx.wasm_symbols.i32_tmp);
instr.i32_const(var.index() * Var::mem_size());
instr.local_get(ctx.wasm_symbols.i32_tmp);
instr.store(
ctx.wasm_symbols.main_memory,
StoreKind::I32 { atomic: false },
MemArg {
align: size_of::<i32>() as u32,
offset: VARS_STACK_START as u64,
},
);
}
Type::Integer
| Type::String
| Type::Struct
| Type::Array
| Type::Map
| Type::Func => {
instr.local_set(ctx.wasm_symbols.i64_tmp_a);
instr.i32_const(var.index() * Var::mem_size());
instr.local_get(ctx.wasm_symbols.i64_tmp_a);
instr.store(
ctx.wasm_symbols.main_memory,
StoreKind::I64 { atomic: false },
MemArg {
align: size_of::<i64>() as u32,
offset: VARS_STACK_START as u64,
},
);
}
Type::Float => {
instr.local_set(ctx.wasm_symbols.f64_tmp);
instr.i32_const(var.index() * Var::mem_size());
instr.local_get(ctx.wasm_symbols.f64_tmp);
instr.store(
ctx.wasm_symbols.main_memory,
StoreKind::F64,
MemArg {
align: size_of::<f64>() as u32,
offset: VARS_STACK_START as u64,
},
);
}
_ => unreachable!(),
}
set_var_undef(ctx, instr, *var, false);
}
}
fn load_var(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder, var: Var) {
instr.i32_const(var.index().saturating_div(64));
instr.load(
ctx.wasm_symbols.main_memory,
LoadKind::I64 { atomic: false },
MemArg { align: 8, offset: 0 },
);
instr.i64_const(1 << var.index().wrapping_rem(64));
instr.binop(BinaryOp::I64And);
instr.unop(UnaryOp::I64Eqz);
instr.if_else(None, |_then| {}, |_else| throw_undef(ctx, _else));
instr.i32_const(var.index() * Var::mem_size());
let (load_kind, alignment) = match var.ty() {
Type::Bool => (LoadKind::I32 { atomic: false }, size_of::<i32>()),
Type::Float => (LoadKind::F64, size_of::<i64>()),
Type::Integer
| Type::String
| Type::Struct
| Type::Array
| Type::Map
| Type::Func => (LoadKind::I64 { atomic: false }, size_of::<i64>()),
_ => unreachable!(),
};
instr.load(
ctx.wasm_symbols.main_memory,
load_kind,
MemArg { align: alignment as u32, offset: VARS_STACK_START as u64 },
);
}
fn set_var_undef(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
var: Var,
is_undef: bool,
) {
instr.i32_const(var.index().saturating_div(64));
instr.i32_const(var.index().saturating_div(64));
instr.load(
ctx.wasm_symbols.main_memory,
LoadKind::I64 { atomic: false },
MemArg { align: 8, offset: 0 },
);
let bit = 1i64 << var.index().wrapping_rem(64);
if is_undef {
instr.i64_const(bit);
instr.binop(BinaryOp::I64Or);
} else {
instr.i64_const(!bit);
instr.binop(BinaryOp::I64And);
}
instr.store(
ctx.wasm_symbols.main_memory,
StoreKind::I64 { atomic: false },
MemArg { align: 8, offset: 0 },
);
}
fn incr_var(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder, var: Var) {
assert_eq!(var.ty(), Type::Integer);
set_var(ctx, instr, var, |ctx, instr| {
load_var(ctx, instr, var);
instr.i64_const(1);
instr.binop(BinaryOp::I64Add);
});
}
fn emit_bool_expr(
ctx: &mut EmitContext,
ir: &IR,
expr: ExprId,
instr: &mut InstrSeqBuilder,
) {
emit_expr(ctx, ir, expr, instr);
match ir.get(expr).ty() {
Type::Bool => {
}
Type::Integer => {
instr.i64_const(0);
instr.binop(BinaryOp::I64Ne);
}
Type::Float => {
instr.f64_const(0.0);
instr.binop(BinaryOp::F64Ne);
}
Type::String => {
instr.call(ctx.function_id(wasm::export__str_len.mangled_name));
instr.i64_const(0);
instr.binop(BinaryOp::I64Ne);
}
ty => unreachable!("type `{:?}` can't be casted to boolean", ty),
}
}
fn emit_call_and_handle_undef(
ctx: &mut EmitContext,
instr: &mut InstrSeqBuilder,
fn_id: walrus::FunctionId,
) {
instr.call(fn_id);
instr.if_else(
None,
|then_| {
throw_undef(ctx, then_);
},
|_| {
},
);
}
fn emit_lookup_common(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
let num_lookup_indexes = ctx.lookup_list.len();
let main_memory = ctx.wasm_symbols.main_memory;
let root = ctx.lookup_list.first().unwrap().1;
if root {
instr.i64_const(RuntimeObjectHandle::NULL.into());
}
for (i, (field_index, _)) in ctx.lookup_list.drain(0..).enumerate() {
let offset = (i * size_of::<i32>()) as i32;
assert!(
(0..LOOKUP_INDEXES_END - LOOKUP_INDEXES_START).contains(&offset)
);
instr.i32_const(offset);
instr.i32_const(field_index);
instr.store(
main_memory,
StoreKind::I32 { atomic: false },
MemArg {
align: size_of::<i32>() as u32,
offset: LOOKUP_INDEXES_START as u64,
},
);
}
instr.i32_const(num_lookup_indexes as i32);
}
#[inline]
fn emit_lookup_integer(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
emit_lookup_common(ctx, instr);
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(wasm::export__lookup_integer.mangled_name),
);
}
#[inline]
fn emit_lookup_float(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
emit_lookup_common(ctx, instr);
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(wasm::export__lookup_float.mangled_name),
);
}
#[inline]
fn emit_lookup_bool(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
emit_lookup_common(ctx, instr);
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(wasm::export__lookup_bool.mangled_name),
);
}
#[inline]
fn emit_lookup_string(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
emit_lookup_common(ctx, instr);
emit_call_and_handle_undef(
ctx,
instr,
ctx.function_id(wasm::export__lookup_string.mangled_name),
);
}
#[inline]
fn emit_lookup_object(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
emit_lookup_common(ctx, instr);
instr.call(ctx.function_id(wasm::export__lookup_object.mangled_name));
}
fn catch_undef(
ctx: &mut EmitContext,
ty: impl Into<InstrSeqType>,
instr: &mut InstrSeqBuilder,
expr: impl FnOnce(&mut EmitContext, &mut InstrSeqBuilder),
catch: impl Fn(&mut EmitContext, &mut InstrSeqBuilder) + 'static,
) {
instr.block(ty, |block| {
ctx.exception_handler_stack.push((block.id(), Box::new(catch)));
expr(ctx, block);
});
ctx.exception_handler_stack.pop();
}
fn throw_undef(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
let innermost_handler = ctx
.exception_handler_stack
.pop()
.expect("calling `raise` from outside `try` block");
innermost_handler.1(ctx, instr);
instr.br(innermost_handler.0);
ctx.exception_handler_stack.push(innermost_handler);
}
fn throw_undef_if_zero(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) {
let tmp = ctx.wasm_symbols.i64_tmp_a;
instr.local_tee(tmp);
instr.unop(UnaryOp::I64Eqz);
instr.if_else(
I64,
|then| {
throw_undef(ctx, then);
},
|else_| {
else_.local_get(tmp);
},
);
}