use wasm_encoder::{BlockType, Instruction, ValType};
use crate::codegen::CodegenCtx;
use crate::codegen::function_emitter::{FunctionEmitter, emit_const_string_by_text};
use crate::runtime::prelude::index::IndexAccess;
use crate::{ExprId, TypedAst, TypedExprKind};
pub(crate) fn receiver_name(
ta: &TypedAst,
mut receiver: ExprId,
) -> Result<String, crate::compiler_error::CompilerFailure> {
let mut fields = Vec::new();
for _ in 0..8 {
let expr = ta
.try_expr(receiver)
.map_err(crate::codegen::arena_failure)?;
let mut name = match &expr.kind {
TypedExprKind::LocalRef { ident, .. } if is_identifier(&ident.name) => {
ident.name.clone()
}
TypedExprKind::GlobalRef { name, .. } if is_identifier(&name.name) => name.name.clone(),
TypedExprKind::This => "this".to_string(),
TypedExprKind::LocalNarrowRef { path, .. }
if path.chain.len() <= 8
&& is_identifier(&path.root.render())
&& path.chain.iter().all(|segment| {
matches!(
segment,
crate::typechecker::infer::narrowing::PathElem::Field(name)
if is_identifier(name)
)
}) =>
{
path.render()
}
TypedExprKind::Narrowed { inner, .. } => {
receiver = *inner;
continue;
}
TypedExprKind::Cast { value, .. } | TypedExprKind::NonNullAssert { value } => {
receiver = *value;
continue;
}
TypedExprKind::FieldAccess {
receiver: base,
name,
}
| TypedExprKind::InterfacePropertyAccess {
receiver: base,
name,
..
} => {
if !is_identifier(&name.name) {
return Ok(String::new());
}
fields.push(name.name.as_str());
receiver = *base;
continue;
}
_ => return Ok(String::new()),
};
for field in fields.iter().rev() {
name.push('.');
name.push_str(field);
}
if name.len() <= 64 {
return Ok(name);
}
return Ok(String::new());
}
Ok(String::new())
}
pub(crate) fn chain_receiver_name(
ta: &TypedAst,
base: ExprId,
preceding: &[crate::TypedChainPart],
) -> Result<String, crate::compiler_error::CompilerFailure> {
let mut name = receiver_name(ta, base)?;
for part in preceding {
if name.is_empty() {
break;
}
match part {
crate::TypedChainPart::Field { name: field, .. }
| crate::TypedChainPart::InterfaceProperty { name: field, .. } => {
if !is_identifier(&field.name)
|| name
.len()
.saturating_add(field.name.len())
.saturating_add(1)
> 64
{
return Ok(String::new());
}
name.push('.');
name.push_str(&field.name);
}
crate::TypedChainPart::NonNull { .. } | crate::TypedChainPart::Constraint { .. } => {}
_ => return Ok(String::new()),
}
}
Ok(name)
}
fn is_identifier(name: &str) -> bool {
let mut chars = name.chars();
chars
.next()
.is_some_and(|c| c == '_' || c == '$' || unicode_ident::is_xid_start(c))
&& chars.all(|c| c == '_' || c == '$' || unicode_ident::is_xid_continue(c))
}
pub fn stash_index_operand(
emitter: &mut FunctionEmitter,
) -> Result<u32, crate::compiler_error::CompilerFailure> {
let idx_f64_local = emitter.add_anonymous_local(ValType::F64)?;
emitter.instruction(Instruction::LocalSet(idx_f64_local));
Ok(idx_f64_local)
}
pub(crate) fn emit_checked_index(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
raw_arr_local: u32,
idx_f64_local: u32,
receiver: &str,
access: IndexAccess,
) -> Result<u32, crate::compiler_error::CompilerFailure> {
let len = emitter.add_anonymous_local(ValType::I32)?;
emitter.instruction(Instruction::LocalGet(raw_arr_local));
emitter.instruction(Instruction::ArrayLen);
emitter.instruction(Instruction::LocalSet(len));
emit_checked_index_with_length(emitter, ctx, len, idx_f64_local, receiver, access)
}
pub fn stash_array_length(
emitter: &mut FunctionEmitter,
array_type: u32,
) -> Result<u32, crate::compiler_error::CompilerFailure> {
let receiver = emitter.add_anonymous_local(ValType::Ref(wasm_encoder::RefType {
nullable: false,
heap_type: wasm_encoder::HeapType::Concrete(array_type),
}))?;
let len = emitter.add_anonymous_local(ValType::I32)?;
emitter.instruction(Instruction::LocalTee(receiver));
emitter.instruction(Instruction::StructGet {
struct_type_index: array_type,
field_index: 2,
});
emitter.instruction(Instruction::LocalSet(len));
emitter.instruction(Instruction::LocalGet(receiver));
Ok(len)
}
pub(crate) fn emit_checked_index_with_length(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
len: u32,
idx_f64_local: u32,
receiver: &str,
access: IndexAccess,
) -> Result<u32, crate::compiler_error::CompilerFailure> {
emit_index_in_range(emitter, len, idx_f64_local);
emitter.instruction(Instruction::I32Eqz);
emitter.emit_if(BlockType::Empty);
emit_index_oob_throw(emitter, ctx, len, idx_f64_local, receiver, access)?;
emitter.emit_end();
let idx_local = emitter.add_anonymous_local(ValType::I32)?;
emitter.instruction(Instruction::LocalGet(idx_f64_local));
emitter.instruction(Instruction::I32TruncSatF64U);
emitter.instruction(Instruction::LocalSet(idx_local));
Ok(idx_local)
}
pub(crate) fn emit_index_in_range(emitter: &mut FunctionEmitter, len: u32, idx_f64_local: u32) {
emitter.instruction(Instruction::LocalGet(idx_f64_local));
emitter.instruction(Instruction::F64Const(0.0.into()));
emitter.instruction(Instruction::F64Ge);
emitter.instruction(Instruction::LocalGet(idx_f64_local));
emitter.instruction(Instruction::LocalGet(len));
emitter.instruction(Instruction::F64ConvertI32U);
emitter.instruction(Instruction::F64Lt);
emitter.instruction(Instruction::I32And);
emitter.instruction(Instruction::LocalGet(idx_f64_local));
emitter.instruction(Instruction::LocalGet(idx_f64_local));
emitter.instruction(Instruction::F64Floor);
emitter.instruction(Instruction::F64Eq);
emitter.instruction(Instruction::I32And);
}
pub(crate) fn emit_group_participation_check(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
len: u32,
idx_f64_local: u32,
receiver: &str,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let undefined_idx = ctx
.symbols
.intrinsic_type_indices()
.map(|indices| indices.undefined)
.ok_or_else(|| crate::codegen::internal_failure("undefined intrinsic registered"))?;
let element = emitter.add_anonymous_local(ctx.symbols.value_type(&crate::Type::Unknown)?)?;
emitter.instruction(Instruction::LocalTee(element));
emitter.instruction(Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(undefined_idx),
));
emitter.emit_if(BlockType::Empty);
emit_index_oob_throw(
emitter,
ctx,
len,
idx_f64_local,
receiver,
IndexAccess::Group,
)?;
emitter.emit_end();
emitter.instruction(Instruction::LocalGet(element));
Ok(())
}
fn emit_index_oob_throw(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
len: u32,
index: u32,
receiver: &str,
access: IndexAccess,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let helper = ctx
.symbols
.prelude_func_idx("index_error")
.ok_or_else(|| crate::codegen::internal_failure("index error helper imported"))?;
emitter.instruction(Instruction::LocalGet(index));
emitter.instruction(Instruction::LocalGet(len));
emitter.instruction(Instruction::F64ConvertI32U);
emit_const_string_by_text(emitter, ctx, receiver)?;
emitter.instruction(Instruction::F64Const((access as u32 as f64).into()));
emitter.instruction(Instruction::Call(helper));
emitter.instruction(Instruction::Unreachable);
Ok(())
}