mirror of
https://github.com/mii443/wasmer.git
synced 2025-08-24 01:09:26 +00:00
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:
@ -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(§ion));
|
||||
|
||||
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",
|
||||
|
Reference in New Issue
Block a user