use wasm_encoder::{BlockType, HeapType, Ieee64, Instruction, RefType, ValType};
use crate::Type;
use crate::codegen::CodegenCtx;
use crate::codegen::classes::VTABLE_PARENT_SLOT;
use crate::codegen::function_emitter::{FunctionEmitter, ReturnTarget};
use crate::codegen::symbol_table::is_nullable_ref;
use crate::typechecker::infer::narrowing::{TruthinessClass, truthiness_class};
pub fn emit_nominal_instance_test(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
target_vtable_global_idx: u32,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let intr = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
crate::codegen::internal_failure("intrinsic types declared before body emission")
})?;
let ref_null_to = |idx: u32| {
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(idx),
})
};
let obj_local = emitter.add_anonymous_local(ref_null_to(intr.object))?;
let raw_vt_local = emitter.add_anonymous_local(ref_null_to(intr.vtable))?;
let vt_local = emitter.add_anonymous_local(ref_null_to(intr.class_vtable))?;
emitter.instruction(Instruction::LocalSet(obj_local));
emitter.emit_block(BlockType::Result(ValType::I32)); emitter.emit_block(BlockType::Empty); emitter.instruction(Instruction::LocalGet(obj_local));
emitter.instruction(Instruction::BrOnNull(0));
emitter.instruction(Instruction::StructGet {
struct_type_index: intr.object,
field_index: 0,
});
emitter.instruction(Instruction::LocalTee(raw_vt_local));
emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(
intr.class_vtable,
)));
emitter.instruction(Instruction::I32Eqz);
emitter.instruction(Instruction::BrIf(0));
emitter.instruction(Instruction::LocalGet(raw_vt_local));
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
intr.class_vtable,
)));
emitter.instruction(Instruction::LocalSet(vt_local));
emitter.emit_loop(BlockType::Empty); emitter.instruction(Instruction::LocalGet(vt_local));
emitter.instruction(Instruction::GlobalGet(target_vtable_global_idx));
emitter.instruction(Instruction::RefEq);
emitter.emit_if(BlockType::Empty);
emitter.instruction(Instruction::I32Const(1));
emitter.instruction(Instruction::Br(3)); emitter.emit_end();
emitter.instruction(Instruction::LocalGet(vt_local));
emitter.instruction(Instruction::StructGet {
struct_type_index: intr.class_vtable,
field_index: VTABLE_PARENT_SLOT,
});
emitter.instruction(Instruction::BrOnNull(1)); emitter.instruction(Instruction::LocalSet(vt_local));
emitter.instruction(Instruction::Br(0)); emitter.emit_end(); emitter.emit_end(); emitter.instruction(Instruction::I32Const(0));
emitter.emit_end();
Ok(())
}
pub fn emit_box(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
ty: &Type,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let ty = ty.peel();
match ty {
Type::Number | Type::NumberLiteral(_) => {
let boxed_idx = ctx
.symbols
.boxed_number_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("boxed_number type registered"))?;
let vtable_global = ctx
.symbols
.prelude_global_idx("boxed_number_vtable")
.ok_or_else(|| crate::codegen::internal_failure("boxed_number_vtable imported"))?;
let scratch = emitter.add_anonymous_local(wasm_encoder::ValType::F64)?;
emitter.instruction(Instruction::LocalSet(scratch));
emitter.instruction(Instruction::GlobalGet(vtable_global));
emitter.instruction(Instruction::LocalGet(scratch));
emitter.instruction(Instruction::StructNew(boxed_idx));
}
Type::Boolean | Type::BooleanLiteral(_) => {
let boxed_idx = ctx
.symbols
.boxed_boolean_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("boxed_boolean type registered"))?;
let vtable_global = ctx
.symbols
.prelude_global_idx("boxed_boolean_vtable")
.ok_or_else(|| crate::codegen::internal_failure("boxed_boolean_vtable imported"))?;
let scratch = emitter.add_anonymous_local(wasm_encoder::ValType::I32)?;
emitter.instruction(Instruction::LocalSet(scratch));
emitter.instruction(Instruction::GlobalGet(vtable_global));
emitter.instruction(Instruction::LocalGet(scratch));
emitter.instruction(Instruction::StructNew(boxed_idx));
}
Type::String
| Type::StringLiteral(_)
| Type::BigInt
| Type::BigIntLiteral(_)
| Type::Object { .. }
| Type::Array(_)
| Type::Tuple(_)
| Type::Uint8Array
| Type::Function { .. } => {
}
Type::TypeVar(_) | Type::GenericParam { .. } | Type::Unknown | Type::Never => {
}
Type::Null => {
}
Type::Undefined => {
let idx = ctx
.symbols
.intrinsic_type_indices()
.ok_or_else(|| {
crate::codegen::internal_failure("undefined intrinsic not registered")
})?
.undefined;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::Void => {}
Type::Error => {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
}
Type::NumberEnum { .. } | Type::StringEnum { .. } => {}
Type::Union(_) => {
match ctx.symbols.value_type(ty)? {
ValType::F64 => emit_box(emitter, ctx, &Type::Number)?,
ValType::I32 => emit_box(emitter, ctx, &Type::Boolean)?,
ValType::Ref(_) => {}
_ => {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
}
}
}
Type::InterfaceRef { .. } | Type::ClassRef { .. } => {}
Type::AliasRef { .. } => {}
Type::Alias { .. } | Type::Refined { .. } | Type::Readonly(_) => {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
}
};
Ok(())
}
pub fn emit_coerce_to_slot(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
source_ty: &Type,
target_ty: &Type,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let source_val = ctx.symbols.value_type(source_ty)?;
let target_val = ctx.symbols.value_type(target_ty)?;
if source_val == target_val {
return Ok(());
}
if matches!(source_ty.peel(), Type::Never) {
emit_cast_to(emitter, ctx, target_ty)?;
return Ok(());
}
emit_coerce_to_wasm_slot(emitter, ctx, source_ty, target_val)?;
Ok(())
}
pub fn emit_coerce_to_wasm_slot(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
source_ty: &Type,
target_val: ValType,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let source_val = ctx.symbols.value_type(source_ty)?;
if source_val == target_val {
return Ok(());
}
if matches!(source_ty.peel(), Type::Never) {
emitter.instruction(Instruction::Unreachable);
return Ok(());
}
if crate::codegen::closure_coercions::emit_coercion(emitter, ctx, source_ty, target_val)? {
return Ok(());
}
if source_ty == &Type::Unknown && target_val == ctx.symbols.value_type(&Type::String)? {
let function = ctx
.symbols
.prelude_func_idx("__value_to_string")
.ok_or_else(|| {
crate::codegen::internal_failure("dynamic string conversion collected")
})?;
emitter.instruction(Instruction::Call(function));
return Ok(());
}
match (&source_val, &target_val) {
(ValType::Ref(_), ValType::F64) if matches!(source_ty, Type::Unknown) => {
let function = ctx
.symbols
.prelude_func_idx("__value_to_number")
.ok_or_else(|| {
crate::codegen::internal_failure("dynamic number conversion collected")
})?;
emitter.instruction(Instruction::Call(function));
}
(ValType::Ref(_), ValType::I32) if matches!(source_ty, Type::Unknown) => {
emit_condition_to_i32(emitter, ctx, source_ty)?;
}
(ValType::Ref(_), ValType::F64) => {
emit_cast_to(emitter, ctx, &Type::Number)?;
}
(ValType::F64 | ValType::I32, ValType::Ref(_)) => emit_box(emitter, ctx, source_ty)?,
(ValType::Ref(source), ValType::Ref(target))
if ctx.symbols.ref_fits_slot(*source, *target) => {}
(ValType::Ref(_), ValType::Ref(target)) => {
if target.nullable {
emitter.instruction(Instruction::RefCastNullable(target.heap_type));
} else {
emitter.instruction(Instruction::RefCastNonNull(target.heap_type));
}
}
_ => {
return Err(crate::codegen::internal_failure(
"value representation does not fit the recorded slot",
));
}
};
Ok(())
}
pub fn emit_coerce_to_return_slot(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
source_ty: &Type,
) -> Result<(), crate::compiler_error::CompilerFailure> {
match emitter.return_target().clone() {
ReturnTarget::Slot(slot) => emit_coerce_to_wasm_slot(emitter, ctx, source_ty, slot)?,
ReturnTarget::Declared(ret) => emit_coerce_to_slot(emitter, ctx, source_ty, &ret)?,
ReturnTarget::NoResult => {}
};
Ok(())
}
pub fn emit_condition_to_i32(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
cond_ty: &Type,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let val = ctx.symbols.value_type(cond_ty)?;
match val {
ValType::F64 => {
emitter.instruction(Instruction::F64Abs);
emitter.instruction(Instruction::F64Const(Ieee64::from(0.0)));
emitter.instruction(Instruction::F64Gt);
}
ValType::Ref(rt) => emit_ref_truthiness(emitter, ctx, cond_ty, rt.nullable)?,
_ => {}
};
Ok(())
}
fn emit_ref_truthiness(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
cond_ty: &Type,
nullable: bool,
) -> Result<(), crate::compiler_error::CompilerFailure> {
use TruthinessClass::*;
let peeled = cond_ty.peel();
let members: Vec<&Type> = match peeled {
Type::Union(members) => members.iter().collect(),
single => vec![single],
};
let classes: Vec<TruthinessClass> = members
.iter()
.filter(|m| !matches!(m.peel(), Type::Null))
.map(|m| match m.peel() {
Type::Void => Dynamic,
Type::Undefined => AlwaysFalsy,
_ => truthiness_class(m),
})
.collect();
if classes.iter().all(|c| *c == AlwaysTruthy) {
if nullable {
emitter.instruction(Instruction::RefIsNull);
emitter.instruction(Instruction::I32Eqz);
} else {
emitter.instruction(Instruction::Drop);
emitter.instruction(Instruction::I32Const(1));
}
return Ok(());
}
if classes.iter().all(|c| *c == AlwaysFalsy) {
emitter.instruction(Instruction::Drop);
emitter.instruction(Instruction::I32Const(0));
return Ok(());
}
let needs = |class: TruthinessClass, literal: fn(&Type) -> bool| {
classes.contains(&class)
|| classes.contains(&Dynamic)
|| members.iter().any(|m| literal(m.peel()))
};
let test_string = needs(StringLike, |m| matches!(m, Type::StringLiteral(_)));
let test_number = needs(NumberLike, |m| matches!(m, Type::NumberLiteral(_)));
let test_boolean = needs(BooleanLike, |m| matches!(m, Type::BooleanLiteral(_)));
let test_bigint = needs(BigIntLike, |_| false);
let test_undefined = classes.contains(&Dynamic)
|| members
.iter()
.any(|member| matches!(member.peel(), Type::Undefined));
let slot = ctx.symbols.value_type(cond_ty)?;
let tmp = emitter.add_anonymous_local(slot)?;
emitter.instruction(Instruction::LocalSet(tmp));
let mut open_blocks = 0u32;
if nullable {
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefIsNull);
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::I32Const(0));
emitter.emit_else();
open_blocks += 1;
}
if test_undefined {
emitter.instruction(Instruction::LocalGet(tmp));
super::expr::emit_is_undefined(emitter, ctx)?;
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::I32Const(0));
emitter.emit_else();
open_blocks += 1;
}
if test_string {
let idx = ctx
.symbols
.string_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("$string intrinsic type registered"))?;
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(idx)));
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
emitter.instruction(Instruction::StructGet {
struct_type_index: idx,
field_index: 1,
});
emitter.instruction(Instruction::ArrayLen);
emitter.emit_else();
open_blocks += 1;
}
if test_number {
let idx = ctx.symbols.boxed_number_type_idx().ok_or_else(|| {
crate::codegen::internal_failure("$BoxedNumber intrinsic type registered")
})?;
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(idx)));
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
emitter.instruction(Instruction::StructGet {
struct_type_index: idx,
field_index: 1,
});
emitter.instruction(Instruction::F64Abs);
emitter.instruction(Instruction::F64Const(0.0.into()));
emitter.instruction(Instruction::F64Gt);
emitter.emit_else();
open_blocks += 1;
}
if test_boolean {
let idx = ctx.symbols.boxed_boolean_type_idx().ok_or_else(|| {
crate::codegen::internal_failure("$BoxedBoolean intrinsic type registered")
})?;
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(idx)));
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
emitter.instruction(Instruction::StructGet {
struct_type_index: idx,
field_index: 1,
});
emitter.emit_else();
open_blocks += 1;
}
if test_bigint {
let idx = ctx
.symbols
.bigint_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("$bigint intrinsic type registered"))?;
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(idx)));
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::LocalGet(tmp));
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
emitter.instruction(Instruction::StructGet {
struct_type_index: idx,
field_index: 1,
});
emitter.emit_else();
open_blocks += 1;
}
emitter.instruction(Instruction::I32Const(1));
for _ in 0..open_blocks {
emitter.emit_end();
}
Ok(())
}
pub fn emit_narrowing_cast(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
cast_info: &crate::typechecker::infer::narrowing::CastInfo,
) -> Result<(), crate::compiler_error::CompilerFailure> {
use crate::typechecker::infer::narrowing::CastKind;
if matches!(cast_info.to_ty.peel(), Type::Never) {
emitter.instruction(Instruction::Drop);
emitter.instruction(Instruction::Unreachable);
return Ok(());
}
let from_val = ctx.symbols.value_type(&cast_info.from_ty)?;
let to_val = ctx.symbols.value_type(&cast_info.to_ty)?;
if from_val == to_val {
return Ok(());
}
if let (ValType::Ref(s), ValType::Ref(t)) = (&from_val, &to_val)
&& s.heap_type == t.heap_type
&& s.nullable
&& !t.nullable
{
emitter.instruction(Instruction::RefAsNonNull);
return Ok(());
}
match cast_info.cast_kind {
CastKind::NonNull | CastKind::Unbox => {
emit_cast_to(emitter, ctx, &cast_info.to_ty)?;
}
CastKind::RefSubtype => {
let ValType::Ref(target_ref) = to_val else {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
};
let cast = if target_ref.nullable {
Instruction::RefCastNullable(target_ref.heap_type)
} else {
Instruction::RefCastNonNull(target_ref.heap_type)
};
emitter.instruction(cast);
}
};
Ok(())
}
pub fn emit_unerase(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
ty: &Type,
) -> Result<(), crate::compiler_error::CompilerFailure> {
if ctx.symbols.slot_value_type(ty)? != ctx.symbols.value_type(ty)? {
emit_cast_to(emitter, ctx, ty)?;
};
Ok(())
}
pub fn emit_cast_to(
emitter: &mut FunctionEmitter<'_>,
ctx: &CodegenCtx<'_>,
ty: &Type,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let ty = ty.peel();
if !target_allows_null(ctx, ty)? {
emitter.instruction(Instruction::RefAsNonNull);
}
match ty {
Type::Number | Type::NumberLiteral(_) => {
let boxed_idx = ctx
.symbols
.boxed_number_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("boxed_number type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(boxed_idx)));
emitter.instruction(Instruction::StructGet {
struct_type_index: boxed_idx,
field_index: 1,
});
}
Type::Boolean | Type::BooleanLiteral(_) => {
let boxed_idx = ctx
.symbols
.boxed_boolean_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("boxed_boolean type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(boxed_idx)));
emitter.instruction(Instruction::StructGet {
struct_type_index: boxed_idx,
field_index: 1,
});
}
Type::String | Type::StringLiteral(_) => {
let idx = ctx
.symbols
.string_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("string type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::BigInt | Type::BigIntLiteral(_) => {
let idx = ctx
.symbols
.bigint_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("bigint type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::Object { .. } => {
let idx = ctx
.symbols
.object_shape_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("$ObjectShape type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::Array(_) | Type::Tuple(_) => {
let idx = ctx
.symbols
.array_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("array type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::Uint8Array => {
let idx = ctx
.symbols
.uint8_array_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("uint8_array type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::TypeVar(_) | Type::GenericParam { .. } | Type::Unknown | Type::Never => {
}
Type::Function { .. } => {
crate::codegen::closure_coercions::emit_erased_cast(
emitter,
ctx,
crate::codegen::closures::classify(ty)?,
)?;
}
Type::Null => {
}
Type::Undefined => {
let idx = ctx
.symbols
.intrinsic_type_indices()
.ok_or_else(|| {
crate::codegen::internal_failure("undefined intrinsic not registered")
})?
.undefined;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::Void => {}
Type::Error => {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
}
Type::Union(_) => {
match ctx.symbols.value_type(ty)? {
ValType::F64 => emit_cast_to(emitter, ctx, &Type::Number)?,
ValType::I32 => emit_cast_to(emitter, ctx, &Type::Boolean)?,
ValType::Ref(target_ref) => {
let cast = if target_ref.nullable {
Instruction::RefCastNullable(target_ref.heap_type)
} else {
Instruction::RefCastNonNull(target_ref.heap_type)
};
emitter.instruction(cast);
}
_ => {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
}
}
}
Type::NumberEnum { .. } => {
let boxed_idx = ctx
.symbols
.boxed_number_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("boxed_number type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(boxed_idx)));
}
Type::StringEnum { .. } => {
let idx = ctx
.symbols
.string_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("string type registered"))?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::InterfaceRef { .. } => {
}
Type::ClassRef { mangled, .. } => {
let idx = ctx.symbols.class_struct_type_idx(mangled).ok_or_else(|| {
crate::codegen::internal_failure("class struct type is not registered")
})?;
emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(idx)));
}
Type::AliasRef { .. } => {
}
Type::Alias { .. } | Type::Refined { .. } | Type::Readonly(_) => {
return Err(crate::codegen::internal_failure(
"invalid type representation during cast lowering",
));
}
};
Ok(())
}
pub fn target_allows_null(
ctx: &CodegenCtx<'_>,
ty: &Type,
) -> Result<bool, crate::compiler_error::CompilerFailure> {
let ty = ty.peel();
Ok(matches!(
ty,
Type::Null
| Type::Void
| Type::Unknown
| Type::Never
| Type::TypeVar(_)
| Type::GenericParam { .. }
| Type::AliasRef { .. }
) || (matches!(ty, Type::Union(_)) && is_nullable_ref(ctx.symbols.value_type(ty)?)))
}