feat(llvm): Implement exception handling IR generation

This commit adds the code to generate `landingpads` and `catch`es in
LLVM.
This commit is contained in:
Edoardo Marangoni
2025-01-15 12:10:02 +01:00
parent 00898f1520
commit dc227424e1
4 changed files with 989 additions and 159 deletions

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use super::{
intrinsics::{
tbaa_label, type_to_llvm, CtxType, FunctionCache, GlobalCache, Intrinsics, MemoryCache,
@ -14,21 +16,20 @@ use inkwell::{
targets::{FileType, TargetMachine},
types::{BasicType, BasicTypeEnum, FloatMathType, IntType, PointerType, VectorType},
values::{
BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, FunctionValue,
AnyValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, FunctionValue,
InstructionOpcode, InstructionValue, IntValue, PhiValue, PointerValue, VectorValue,
},
AddressSpace, AtomicOrdering, AtomicRMWBinOp, DLLStorageClass, FloatPredicate, IntPredicate,
};
use itertools::Itertools;
use smallvec::SmallVec;
use target_lexicon::BinaryFormat;
use crate::object_file::{load_object_file, CompiledFunction};
use crate::{
abi::{get_abi, Abi},
error::err,
};
use crate::{
config::{CompiledKind, LLVM},
error::err_nt,
error::{err, err_nt},
object_file::{load_object_file, CompiledFunction},
};
use wasmer_compiler::{
from_binaryreadererror_wasmerror,
@ -36,33 +37,51 @@ use wasmer_compiler::{
relocation::RelocationTarget,
symbols::{Symbol, SymbolRegistry},
},
wasmparser::{MemArg, Operator},
wasmparser::{Catch, MemArg, Operator},
wpheaptype_to_type, wptype_to_type, FunctionBinaryReader, FunctionBodyData,
MiddlewareBinaryReader, ModuleMiddlewareChain, ModuleTranslationState,
};
use wasmer_types::entity::PrimaryMap;
use wasmer_types::{entity::PrimaryMap, TagIndex};
use wasmer_types::{
CompileError, FunctionIndex, FunctionType, GlobalIndex, LocalFunctionIndex, MemoryIndex,
ModuleInfo, SignatureIndex, TableIndex, Type,
};
use wasmer_vm::{MemoryStyle, TableStyle, VMOffsets};
const FUNCTION_SECTION: &str = "__TEXT,wasmer_function";
const FUNCTION_SECTION_ELF: &str = "__TEXT,wasmer_function";
const FUNCTION_SECTION_MACHO: &str = "__TEXT";
const FUNCTION_SEGMENT_MACHO: &str = "__text";
pub struct FuncTranslator {
ctx: Context,
target_machine: TargetMachine,
abi: Box<dyn Abi>,
binary_fmt: BinaryFormat,
func_section: String,
}
impl FuncTranslator {
pub fn new(target_machine: TargetMachine) -> Self {
pub fn new(
target_machine: TargetMachine,
binary_fmt: BinaryFormat,
) -> Result<Self, CompileError> {
let abi = get_abi(&target_machine);
Self {
Ok(Self {
ctx: Context::create(),
target_machine,
abi,
}
func_section: match binary_fmt {
BinaryFormat::Elf => FUNCTION_SECTION_ELF.to_string(),
BinaryFormat::Macho => FUNCTION_SEGMENT_MACHO.to_string(),
_ => {
return Err(CompileError::UnsupportedTarget(format!(
"Unsupported binary format: {:?}",
binary_fmt
)))
}
},
binary_fmt,
})
}
#[allow(clippy::too_many_arguments)]
@ -111,8 +130,25 @@ impl FuncTranslator {
}
func.add_attribute(AttributeLoc::Function, intrinsics.stack_probe);
func.add_attribute(AttributeLoc::Function, intrinsics.uwtable);
func.add_attribute(AttributeLoc::Function, intrinsics.frame_pointer);
let section = match self.binary_fmt {
BinaryFormat::Elf => FUNCTION_SECTION_ELF.to_string(),
BinaryFormat::Macho => {
format!("{FUNCTION_SECTION_MACHO},{FUNCTION_SEGMENT_MACHO}")
}
_ => {
return Err(CompileError::UnsupportedTarget(format!(
"Unsupported binary format: {:?}",
self.binary_fmt
)))
}
};
func.set_personality_function(intrinsics.personality);
func.as_global_value().set_section(Some(FUNCTION_SECTION));
func.as_global_value().set_section(Some(&section));
func.set_linkage(Linkage::DLLExport);
func.as_global_value()
.set_dll_storage_class(DLLStorageClass::Export);
@ -213,7 +249,14 @@ impl FuncTranslator {
symbol_registry,
abi: &*self.abi,
config,
exception_types_cache: HashMap::new(),
tags_cache: HashMap::new(),
ptr_size: self
.target_machine
.get_target_data()
.get_pointer_byte_size(None),
};
fcg.ctx.add_func(
func_index,
func.as_global_value().as_pointer_value(),
@ -236,6 +279,15 @@ impl FuncTranslator {
let mut passes = vec![];
// -- Uncomment to enable dumping intermediate LLVM objects
//
//module
// .print_to_file(format!(
// "{}/obj_unv.ll",
// std::env!("LLVM_EH_TESTS_DUMP_DIR")
// ))
// .unwrap();
if config.enable_verifier {
passes.push("verify");
}
@ -275,6 +327,11 @@ impl FuncTranslator {
)
.unwrap();
// -- Uncomment to enable dumping intermediate LLVM objects
//module
// .print_to_file(format!("{}/obj.ll", std::env!("LLVM_EH_TESTS_DUMP_DIR")))
// .unwrap();
if let Some(ref callbacks) = config.callbacks {
callbacks.postopt_ir(&function, &module);
}
@ -310,6 +367,15 @@ impl FuncTranslator {
.write_to_memory_buffer(&module, FileType::Object)
.unwrap();
// -- Uncomment to enable dumping intermediate LLVM objects
//target_machine
// .write_to_file(
// &module,
// FileType::Assembly,
// std::path::Path::new(&format!("{}/obj.asm", std::env!("LLVM_EH_TESTS_DUMP_DIR"))),
// )
// .unwrap();
if let Some(ref callbacks) = config.callbacks {
callbacks.obj_memory_buffer(&function, &memory_buffer);
}
@ -317,19 +383,29 @@ impl FuncTranslator {
let mem_buf_slice = memory_buffer.as_slice();
load_object_file(
mem_buf_slice,
FUNCTION_SECTION,
&self.func_section,
RelocationTarget::LocalFunc(*local_func_index),
|name: &str| {
Ok(
Ok({
let name = if matches!(self.binary_fmt, BinaryFormat::Macho) {
if name.starts_with("_") {
name.replacen("_", "", 1)
} else {
name.to_string()
}
} else {
name.to_string()
};
if let Some(Symbol::LocalFunction(local_func_index)) =
symbol_registry.name_to_symbol(name)
symbol_registry.name_to_symbol(&name)
{
Some(RelocationTarget::LocalFunc(local_func_index))
} else {
None
},
)
}
})
},
self.binary_fmt,
)
}
}
@ -1349,6 +1425,77 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
}
Ok(())
}
fn get_or_insert_global_tag(&mut self, tag: u32) -> BasicValueEnum<'ctx> {
if let Some(tag) = self.tags_cache.get(&tag) {
return tag.clone();
}
let tag_ty = self
.context
.struct_type(&[self.intrinsics.i64_ty.into()], false);
let tag_glbl = self.module.add_global(
tag_ty,
Some(AddressSpace::default()),
&format!("__wasmer_eh_type_info_{tag}"),
);
tag_glbl.set_initializer(
&tag_ty
.const_named_struct(&[self
.intrinsics
.i64_ty
.const_int(self.encode_tag(tag) as _, false)
.into()])
.as_basic_value_enum(),
);
tag_glbl.set_linkage(Linkage::External);
tag_glbl.set_constant(true);
tag_glbl.set_section(Some("__wasmer_eh_type_info"));
let tag_glbl = tag_glbl.as_basic_value_enum();
self.tags_cache.insert(tag, tag_glbl.clone());
tag_glbl
}
fn get_or_insert_exception_type(
&mut self,
tag: u32,
signature: &FunctionType,
) -> Result<inkwell::types::StructType<'ctx>, CompileError> {
if let Some(ty) = self.exception_types_cache.get(&tag) {
return Ok(ty.clone());
}
let types = signature
.params()
.iter()
.map(|v| type_to_llvm(self.intrinsics, *v))
.collect::<Result<Vec<_>, CompileError>>()?;
let ty = self.context.struct_type(&types, false);
self.exception_types_cache.insert(tag, ty.clone());
Ok(ty)
}
// Encode a tag - an u32 identifying an event - into a unique value that can be used in
// catch clauses.
//
// This is to avoid that the #0 tag is reinterpreted as nullptr in the catch clause of a
// landing pad.
#[inline]
fn encode_tag(&self, tag: u32) -> usize {
let size = self.ptr_size;
if size == 4 {
(tag + 2099) as usize
} else if size == 8 {
let mut base = vec!['w' as u8, 'a' as u8, 's' as u8, 'm' as u8];
base.append(&mut tag.to_be_bytes().to_vec());
u64::from_be_bytes(base.try_into().unwrap()) as usize
} else {
panic!()
}
}
}
/*
@ -1462,9 +1609,26 @@ pub struct LLVMFunctionCodeGenerator<'ctx, 'a> {
symbol_registry: &'a dyn SymbolRegistry,
abi: &'a dyn Abi,
config: &'a LLVM,
tags_cache: HashMap<u32, BasicValueEnum<'ctx>>,
exception_types_cache: HashMap<u32, inkwell::types::StructType<'ctx>>,
ptr_size: u32,
}
impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
//fn add_fn_to_got(&mut self, func: &FunctionValue<'ctx>, name: &str) {
// //let throw_intrinsic = self.intrinsics.throw.as_global_value().as_pointer_value();
// if !self.got_cache.contains(name) {
// let func_ptr = func.as_global_value().as_pointer_value();
// let global =
// self.module
// .add_global(func_ptr.get_type(), Some(AddressSpace::from(1u16)), name);
// global.set_initializer(&func_ptr);
// global.set_section(Some("__DATA_CONST,__got"));
// self.got_cache.insert(name.to_string());
// }
//}
fn translate_operator(&mut self, op: Operator, _source_loc: u32) -> Result<(), CompileError> {
// TODO: remove this vmctx by moving everything into CtxType. Values
// computed off vmctx usually benefit from caching.
@ -1883,7 +2047,9 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
}
self.builder.position_at_end(*if_else);
err!(self.builder.build_unconditional_branch(*next));
}
} else if let ControlFrame::Landingpad { .. } = &frame {
self.state.pop_landingpad();
};
self.builder.position_at_end(*frame.code_after());
self.state.reset_stack(&frame);
@ -2331,17 +2497,33 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
}
}
*/
let call_site = err!(self.builder.build_indirect_call(
llvm_func_type,
func,
params
.iter()
.copied()
.map(Into::into)
.collect::<Vec<BasicMetadataValueEnum>>()
.as_slice(),
"",
));
let call_site = if let Some(lpad) = self.state.get_landingpad() {
let then_block = self.context.append_basic_block(self.function, "then_block");
let ret = err!(self.builder.build_indirect_invoke(
llvm_func_type,
func,
params.as_slice(),
then_block,
lpad,
"",
));
self.builder.position_at_end(then_block);
ret
} else {
err!(self.builder.build_indirect_call(
llvm_func_type,
func,
params
.iter()
.copied()
.map(Into::into)
.collect::<Vec<BasicMetadataValueEnum>>()
.as_slice(),
"",
))
};
for (attr, attr_loc) in attrs {
call_site.add_attribute(attr_loc, attr);
}
@ -2647,17 +2829,34 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
}
}
*/
let call_site = err!(self.builder.build_indirect_call(
llvm_func_type,
typed_func_ptr,
params
.iter()
.copied()
.map(Into::into)
.collect::<Vec<BasicMetadataValueEnum>>()
.as_slice(),
"indirect_call",
));
let call_site = if let Some(lpad) = self.state.get_landingpad() {
let then_block = self.context.append_basic_block(self.function, "then_block");
let ret = err!(self.builder.build_indirect_invoke(
llvm_func_type,
typed_func_ptr,
params.as_slice(),
then_block,
lpad,
"",
));
self.builder.position_at_end(then_block);
ret
} else {
err!(self.builder.build_indirect_call(
llvm_func_type,
typed_func_ptr,
params
.iter()
.copied()
.map(Into::into)
.collect::<Vec<BasicMetadataValueEnum>>()
.as_slice(),
"indirect_call",
))
};
for (attr, attr_loc) in llvm_func_attrs {
call_site.add_attribute(attr_loc, attr);
}
@ -7956,7 +8155,7 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
// let res = self
// .builder
// .build_signed_int_to_float(v, self.intrinsics.f64x2_ty, "");
// let res = err!(self.builder.build_bit_cast(res, self.intrinsics.i128_ty, ""));
// let res = chck_err!(self.builder.build_bit_cast(res, self.intrinsics.i128_ty, ""));
// self.state.push1(res);
// }
// Operator::F64x2ConvertI64x2U => {
@ -7968,7 +8167,7 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
// let res = self
// .builder
// .build_unsigned_int_to_float(v, self.intrinsics.f64x2_ty, "");
// let res = err!(self.builder.build_bit_cast(res, self.intrinsics.i128_ty, ""));
// let res = chck_err!(self.builder.build_bit_cast(res, self.intrinsics.i128_ty, ""));
// self.state.push1(res);
// }
Operator::I32ReinterpretF32 => {
@ -11966,6 +12165,490 @@ impl<'ctx, 'a> LLVMFunctionCodeGenerator<'ctx, 'a> {
));
self.state.push1(cnt.try_as_basic_value().left().unwrap());
}
Operator::TryTable { try_table } => {
let current_block = self
.builder
.get_insert_block()
.ok_or_else(|| CompileError::Codegen("not currently in a block".to_string()))?;
let can_throw_block = self.context.append_basic_block(self.function, "try_begin");
let catch_block = self.context.append_basic_block(self.function, "catch");
let rethrow_block = self.context.append_basic_block(self.function, "rethrow");
self.builder.position_at_end(current_block);
let end_block = self.context.append_basic_block(self.function, "try_end");
let end_phis = {
self.builder.position_at_end(end_block);
let phis = self
.module_translation
.blocktype_params_results(&try_table.ty)?
.1
.iter()
.map(|&wp_ty| {
err_nt!(wptype_to_type(wp_ty)).and_then(|wasm_ty| {
type_to_llvm(self.intrinsics, wasm_ty)
.and_then(|ty| err_nt!(self.builder.build_phi(ty, "")))
})
})
.collect::<Result<_, _>>()?;
self.builder.position_at_end(current_block);
phis
};
self.builder.position_at_end(current_block);
err!(self.builder.build_unconditional_branch(can_throw_block));
self.builder.position_at_end(catch_block);
// Collect unique catches. It is not a "hard" error on the wasm side,
// but LLVM will definitely complain about having the same identifier
// match two different branches in the switch below.
let catches: Vec<_> = try_table
.catches
.into_iter()
.unique_by(|v| match v {
Catch::One { label, .. }
| Catch::OneRef { label, .. }
| Catch::All { label }
| Catch::AllRef { label } => *label,
})
.collect();
let block_param_types = self
.module_translation
.blocktype_params_results(&try_table.ty)?
.0
.iter()
.map(|&wp_ty| {
err_nt!(wptype_to_type(wp_ty))
.and_then(|wasm_ty| type_to_llvm(self.intrinsics, wasm_ty))
})
.collect::<Result<Vec<_>, _>>()?;
// Build the landing pad.
let null = self.intrinsics.ptr_ty.const_zero();
let exception_type = self.context.struct_type(
&[self.intrinsics.ptr_ty.into(), self.intrinsics.i32_ty.into()],
false,
);
let mut tags = vec![];
let clauses: Vec<BasicValueEnum<'ctx>> = catches
.iter()
.map(|catch| match catch {
Catch::All { .. } | Catch::AllRef { .. } => {
tags.push(0);
Ok(null.into())
}
Catch::One { tag, .. } | Catch::OneRef { tag, .. } => {
//let (int_tag, tag_ptr) = self.tag_to_catch_clause(*tag);
//let tag_ptr = tag_ptr?;
//tags.push(int_tag);
//Ok(tag_ptr.into())
tags.push(*tag);
Ok(self.get_or_insert_global_tag(*tag))
}
})
.collect::<Result<Vec<BasicValueEnum<'ctx>>, CompileError>>()?;
let res = err!(self.builder.build_landing_pad(
exception_type,
self.intrinsics.personality,
&clauses,
false,
"exc",
));
let res = res.into_struct_value();
let uw_exc = err!(self.builder.build_extract_value(res, 0, "exc"));
let exc_ty = err!(self.builder.build_extract_value(res, 1, "exc_ty"));
// The exception we get at this point is a wrapper around the [`WasmerException`]
// type. This is needed because when we create the exception to pass it to
// libunwind, the exception object needs to begin with a specific set of fields
// (those in `__Unwind_Exception`). During unwinding, libunwind uses some of these
// fields and we need to pass them back when rethrowing. This to say, we need to
// keep the original exception passed to us by libunwind to be able to rethrow it.
let uw_exc = uw_exc.into_pointer_value();
let exc = err!(self.builder.build_call(
self.intrinsics.read_exception,
&[uw_exc.into()],
"wasmer_exc_ptr"
));
let exc = exc.as_any_value_enum().into_pointer_value();
let exc_ty = exc_ty.into_int_value();
let mut catch_blocks = vec![];
for catch in catches.iter() {
match catch {
Catch::All { label } => {
let b = self
.context
.append_basic_block(self.function, "catch_all_clause");
self.builder.position_at_end(b);
let frame = self.state.frame_at_depth(*label)?;
err!(self.builder.build_unconditional_branch(*frame.br_dest()));
self.builder.position_at_end(catch_block);
catch_blocks.push(b);
}
Catch::One { tag, label } => {
let tag_idx = self.wasm_module.tags[TagIndex::from_u32(*tag)];
let signature = &self.wasm_module.signatures[tag_idx];
let params = signature.params();
let b = self
.context
.append_basic_block(self.function, "catch_one_clause");
self.builder.position_at_end(b);
// Get the type of the exception.
let inner_exc_ty =
self.get_or_insert_exception_type(*tag, signature)?;
// Cast the outer exception - a pointer - to a pointer to a struct of
// type WasmerException.
// let wasmer_exc_ptr = err!(self.builder.build_pointer_cast(
// exc,
// self.intrinsics.exc_ty,
// "wasmer_exc_ptr"
// ));
//
// Not actually needed, since ptr is a single, unique type.
// Points at the `data` field of the exception.
let wasmer_exc_data_ptr_ptr = err!(self.builder.build_struct_gep(
self.intrinsics.exc_ty,
exc,
1,
"wasmer_exc_data_ptr"
));
let wasmer_exc_data_ptr = err!(self.builder.build_load(
self.intrinsics.ptr_ty,
wasmer_exc_data_ptr_ptr,
"wasmer_exc_data_ptr"
));
let wasmer_exc_data_ptr = wasmer_exc_data_ptr.into_pointer_value();
// Read each value from the data ptr.
let values = params
.into_iter()
.enumerate()
.map(|(i, v)| {
let name = format!("value{i}");
let ptr = err!(self.builder.build_struct_gep(
inner_exc_ty,
wasmer_exc_data_ptr,
i as u32,
&(name.clone() + "ptr")
));
err_nt!(self.builder.build_load(
type_to_llvm(self.intrinsics, *v)?,
ptr,
&name,
))
})
.collect::<Result<Vec<_>, CompileError>>()?;
let frame = self.state.frame_at_depth(*label)?;
// todo: check that the types are compatible
for (phi, value) in frame.phis().iter().zip(values.iter()) {
phi.add_incoming(&[(value, b)])
}
err!(self.builder.build_unconditional_branch(*frame.br_dest()));
self.builder.position_at_end(catch_block);
catch_blocks.push(b);
}
Catch::OneRef { label, tag } => {
let tag_idx = self.wasm_module.tags[TagIndex::from_u32(*tag)];
let signature = &self.wasm_module.signatures[tag_idx];
let params = signature.params();
let b = self
.context
.append_basic_block(self.function, "catch_one_clause");
self.builder.position_at_end(b);
// Get the type of the exception.
let inner_exc_ty =
self.get_or_insert_exception_type(*tag, signature)?;
// Cast the outer exception - a pointer - to a pointer to a struct of
// type WasmerException.
// let wasmer_exc_ptr = err!(self.builder.build_pointer_cast(
// exc,
// self.intrinsics.exc_ty,
// "wasmer_exc_ptr"
// ));
//
// Not actually needed, since ptr is a single, unique type.
// Points at the `data` field of the exception.
let wasmer_exc_data_ptr_ptr = err!(self.builder.build_struct_gep(
self.intrinsics.exc_ty,
exc,
1,
"wasmer_exc_data_ptr"
));
let wasmer_exc_data_ptr = err!(self.builder.build_load(
self.intrinsics.ptr_ty,
wasmer_exc_data_ptr_ptr,
"wasmer_exc_data_ptr"
));
let wasmer_exc_data_ptr = wasmer_exc_data_ptr.into_pointer_value();
// Read each value from the data ptr.
let mut values = params
.into_iter()
.enumerate()
.map(|(i, v)| {
let name = format!("value{i}");
let ptr = err!(self.builder.build_struct_gep(
inner_exc_ty,
wasmer_exc_data_ptr,
i as u32,
&(name.clone() + "ptr")
));
err_nt!(self.builder.build_load(
type_to_llvm(self.intrinsics, *v)?,
ptr,
&name,
))
})
.collect::<Result<Vec<_>, CompileError>>()?;
values.push(uw_exc.as_basic_value_enum());
let frame = self.state.frame_at_depth(*label)?;
// todo: check that the types are compatible
for (phi, value) in frame.phis().iter().zip(values.iter()) {
phi.add_incoming(&[(value, b)])
}
err!(self.builder.build_unconditional_branch(*frame.br_dest()));
self.builder.position_at_end(catch_block);
catch_blocks.push(b);
}
Catch::AllRef { label } => {
let b = self
.context
.append_basic_block(self.function, "catch_one_clause");
self.builder.position_at_end(b);
let frame = self.state.frame_at_depth(*label)?;
let phis = frame.phis();
// sanity check (todo): check that the phi has only one element
for phi in phis.iter() {
phi.add_incoming(&[(&uw_exc.as_basic_value_enum(), b)])
}
err!(self.builder.build_unconditional_branch(*frame.br_dest()));
self.builder.position_at_end(catch_block);
catch_blocks.push(b);
}
}
}
err!(self.builder.build_switch(
exc_ty,
rethrow_block,
catch_blocks
.into_iter()
.enumerate()
.map(|(i, v)| (self.intrinsics.i32_ty.const_int(tags[i] as _, false), v))
.collect::<Vec<_>>()
.as_slice()
));
// -- end
// -- The rethrow block
self.builder.position_at_end(rethrow_block);
err!(self
.builder
.build_call(self.intrinsics.rethrow, &[uw_exc.into()], "rethrow"));
// can't reach after an explicit throw!
err!(self.builder.build_unreachable());
// Move to the block that can throw exceptions
self.builder.position_at_end(can_throw_block);
let can_throw_phis: SmallVec<[PhiValue<'ctx>; 1]> = block_param_types
.iter()
.map(|&ty| err_nt!(self.builder.build_phi(ty, "")))
.collect::<Result<SmallVec<_>, _>>()?;
for phi in can_throw_phis.iter() {
self.state.push1(phi.as_basic_value());
}
self.state.push_landingpad(
catch_block,
can_throw_block,
can_throw_phis,
end_block,
end_phis,
&tags,
);
}
Operator::Throw { tag_index } => {
let current_block = self
.builder
.get_insert_block()
.ok_or_else(|| CompileError::Codegen("not currently in a block".to_string()))?;
let tag = self.wasm_module.tags[TagIndex::from_u32(tag_index)];
let signature = &self.wasm_module.signatures[tag];
let params = signature.params();
let values = self.state.popn_save_extra(params.len())?;
values
.iter()
.enumerate()
.map(|(i, (v, _))| {
let t = type_to_llvm(self.intrinsics, params[i])?;
if t != v.get_type() {
return Err(CompileError::Codegen(format!(
"Incompatible types: {:?} != {:?}",
t,
v.get_type()
)));
}
Ok(())
})
.collect::<Result<(), CompileError>>()?;
let exception_type: inkwell::types::StructType = self
.get_or_insert_exception_type(tag_index, signature)?
.clone();
let size = exception_type.size_of().unwrap();
// Allocate the necessary bytes for the exception.
let exc = err!(self.builder.build_direct_call(
self.intrinsics.alloc_exception,
&[size.into()],
"exception_ptr",
));
let exc = exc.try_as_basic_value().left().unwrap();
let exc = exc.into_pointer_value();
for (i, value) in values.into_iter().enumerate() {
let ptr = err!(self.builder.build_struct_gep(
exception_type,
exc,
i as u32,
i.to_string().as_str(),
));
err!(self.builder.build_store(ptr, value.0));
}
let encoded_tag = self.encode_tag(tag_index);
if let Some(pad) = self.state.get_landingpad_for_tag(tag_index) {
let unreachable_block = self
.context
.append_basic_block(self.function, "_throw_unreachable");
err!(self.builder.build_invoke(
self.intrinsics.throw,
&[
self.intrinsics
.i64_ty
.const_int(encoded_tag as _, false)
.into(),
exc.into(),
size.into()
],
unreachable_block,
pad,
"throw",
));
self.builder.position_at_end(unreachable_block);
// can't reach after an explicit throw!
err!(self.builder.build_unreachable());
self.builder.position_at_end(current_block);
} else {
err!(self.builder.build_call(
self.intrinsics.throw,
&[
self.intrinsics
.i64_ty
.const_int(encoded_tag as _, false)
.into(),
exc.into(),
size.into()
],
"throw"
));
// can't reach after an explicit throw!
err!(self.builder.build_unreachable());
}
self.state.reachable = false;
}
Operator::ThrowRef => {
let current_block = self
.builder
.get_insert_block()
.ok_or_else(|| CompileError::Codegen("not currently in a block".to_string()))?;
let exc = self.state.pop1()?;
if let Some(pad) = self.state.get_landingpad() {
let unreachable_block = self
.context
.append_basic_block(self.function, "_rethrow_unreachable");
err!(self.builder.build_invoke(
self.intrinsics.rethrow,
&[exc.into()],
unreachable_block,
pad,
"throw",
));
self.builder.position_at_end(unreachable_block);
// can't reach after an explicit throw!
err!(self.builder.build_unreachable());
self.builder.position_at_end(current_block);
} else {
err!(self.builder.build_call(
self.intrinsics.rethrow,
&[exc.into(),],
"rethrow"
));
// can't reach after an explicit throw!
err!(self.builder.build_unreachable());
}
self.state.reachable = false;
}
_ => {
return Err(CompileError::Codegen(format!(
"Operator {:?} unimplemented",