[eddyb/rebase cleanup] abstracted Funclet
This commit is contained in:
parent
566fa4d003
commit
35b40f51fb
9 changed files with 59 additions and 86 deletions
|
@ -9,10 +9,10 @@
|
|||
// except according to those terms.
|
||||
|
||||
use llvm::{AtomicRmwBinOp, AtomicOrdering, SynchronizationScope, AsmDialect};
|
||||
use llvm::{self, False, OperandBundleDef, BasicBlock};
|
||||
use common;
|
||||
use llvm::{self, False, BasicBlock};
|
||||
use rustc_codegen_utils::common::{IntPredicate, TypeKind, RealPredicate};
|
||||
use rustc_codegen_utils;
|
||||
use common::Funclet;
|
||||
use context::CodegenCx;
|
||||
use type_::Type;
|
||||
use type_of::LayoutLlvmExt;
|
||||
|
@ -66,6 +66,7 @@ impl BackendTypes for Builder<'_, 'll, 'tcx> {
|
|||
type BasicBlock = <CodegenCx<'ll, 'tcx> as BackendTypes>::BasicBlock;
|
||||
type Type = <CodegenCx<'ll, 'tcx> as BackendTypes>::Type;
|
||||
type Context = <CodegenCx<'ll, 'tcx> as BackendTypes>::Context;
|
||||
type Funclet = <CodegenCx<'ll, 'tcx> as BackendTypes>::Funclet;
|
||||
|
||||
type DIScope = <CodegenCx<'ll, 'tcx> as BackendTypes>::DIScope;
|
||||
}
|
||||
|
@ -218,12 +219,14 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn invoke(&self,
|
||||
fn invoke(
|
||||
&self,
|
||||
llfn: &'ll Value,
|
||||
args: &[&'ll Value],
|
||||
then: &'ll BasicBlock,
|
||||
catch: &'ll BasicBlock,
|
||||
funclet: Option<&common::Funclet<&'ll Value>>) -> &'ll Value {
|
||||
funclet: Option<&Funclet<'ll>>,
|
||||
) -> &'ll Value {
|
||||
self.count_insn("invoke");
|
||||
|
||||
debug!("Invoke {:?} with args ({:?})",
|
||||
|
@ -232,7 +235,6 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
|
||||
let args = self.check_call("invoke", llfn, args);
|
||||
let bundle = funclet.map(|funclet| funclet.bundle());
|
||||
let bundle = bundle.map(OperandBundleDef::from_generic);
|
||||
let bundle = bundle.as_ref().map(|b| &*b.raw);
|
||||
|
||||
unsafe {
|
||||
|
@ -1123,7 +1125,7 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
|
||||
fn cleanup_pad(&self,
|
||||
parent: Option<&'ll Value>,
|
||||
args: &[&'ll Value]) -> &'ll Value {
|
||||
args: &[&'ll Value]) -> Funclet<'ll> {
|
||||
self.count_insn("cleanuppad");
|
||||
let name = const_cstr!("cleanuppad");
|
||||
let ret = unsafe {
|
||||
|
@ -1133,23 +1135,23 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
args.as_ptr(),
|
||||
name.as_ptr())
|
||||
};
|
||||
ret.expect("LLVM does not have support for cleanuppad")
|
||||
Funclet::new(ret.expect("LLVM does not have support for cleanuppad"))
|
||||
}
|
||||
|
||||
fn cleanup_ret(
|
||||
&self, cleanup: &'ll Value,
|
||||
&self, funclet: &Funclet<'ll>,
|
||||
unwind: Option<&'ll BasicBlock>,
|
||||
) -> &'ll Value {
|
||||
self.count_insn("cleanupret");
|
||||
let ret = unsafe {
|
||||
llvm::LLVMRustBuildCleanupRet(self.llbuilder, cleanup, unwind)
|
||||
llvm::LLVMRustBuildCleanupRet(self.llbuilder, funclet.cleanuppad(), unwind)
|
||||
};
|
||||
ret.expect("LLVM does not have support for cleanupret")
|
||||
}
|
||||
|
||||
fn catch_pad(&self,
|
||||
parent: &'ll Value,
|
||||
args: &[&'ll Value]) -> &'ll Value {
|
||||
args: &[&'ll Value]) -> Funclet<'ll> {
|
||||
self.count_insn("catchpad");
|
||||
let name = const_cstr!("catchpad");
|
||||
let ret = unsafe {
|
||||
|
@ -1157,13 +1159,13 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
args.len() as c_uint, args.as_ptr(),
|
||||
name.as_ptr())
|
||||
};
|
||||
ret.expect("LLVM does not have support for catchpad")
|
||||
Funclet::new(ret.expect("LLVM does not have support for catchpad"))
|
||||
}
|
||||
|
||||
fn catch_ret(&self, pad: &'ll Value, unwind: &'ll BasicBlock) -> &'ll Value {
|
||||
fn catch_ret(&self, funclet: &Funclet<'ll>, unwind: &'ll BasicBlock) -> &'ll Value {
|
||||
self.count_insn("catchret");
|
||||
let ret = unsafe {
|
||||
llvm::LLVMRustBuildCatchRet(self.llbuilder, pad, unwind)
|
||||
llvm::LLVMRustBuildCatchRet(self.llbuilder, funclet.cleanuppad(), unwind)
|
||||
};
|
||||
ret.expect("LLVM does not have support for catchret")
|
||||
}
|
||||
|
@ -1356,8 +1358,12 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
self.call(lifetime_intrinsic, &[self.cx.const_u64(size), ptr], None);
|
||||
}
|
||||
|
||||
fn call(&self, llfn: &'ll Value, args: &[&'ll Value],
|
||||
funclet: Option<&common::Funclet<&'ll Value>>) -> &'ll Value {
|
||||
fn call(
|
||||
&self,
|
||||
llfn: &'ll Value,
|
||||
args: &[&'ll Value],
|
||||
funclet: Option<&Funclet<'ll>>,
|
||||
) -> &'ll Value {
|
||||
self.count_insn("call");
|
||||
|
||||
debug!("Call {:?} with args ({:?})",
|
||||
|
@ -1366,7 +1372,6 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
|
||||
let args = self.check_call("call", llfn, args);
|
||||
let bundle = funclet.map(|funclet| funclet.bundle());
|
||||
let bundle = bundle.map(OperandBundleDef::from_generic);
|
||||
let bundle = bundle.as_ref().map(|b| &*b.raw);
|
||||
|
||||
unsafe {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
//! Code that is useful in various codegen modules.
|
||||
|
||||
use llvm::{self, True, False, Bool, BasicBlock};
|
||||
use llvm::{self, True, False, Bool, BasicBlock, OperandBundleDef};
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::middle::lang_items::LangItem;
|
||||
use abi;
|
||||
|
@ -23,7 +23,7 @@ use type_of::LayoutLlvmExt;
|
|||
use value::Value;
|
||||
use interfaces::*;
|
||||
|
||||
use rustc::ty::{self, Ty, TyCtxt};
|
||||
use rustc::ty::{Ty, TyCtxt};
|
||||
use rustc::ty::layout::{HasDataLayout, LayoutOf, self, TyLayout, Size};
|
||||
use rustc::mir::interpret::{Scalar, AllocType, Allocation};
|
||||
use rustc::hir;
|
||||
|
@ -35,36 +35,10 @@ use libc::{c_uint, c_char};
|
|||
|
||||
use syntax::symbol::LocalInternedString;
|
||||
use syntax::ast::Mutability;
|
||||
use syntax_pos::{Span, DUMMY_SP};
|
||||
use syntax_pos::Span;
|
||||
|
||||
pub use context::CodegenCx;
|
||||
|
||||
pub fn type_needs_drop<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
ty.needs_drop(tcx, ty::ParamEnv::reveal_all())
|
||||
}
|
||||
|
||||
pub fn type_is_sized<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
ty.is_sized(tcx.at(DUMMY_SP), ty::ParamEnv::reveal_all())
|
||||
}
|
||||
|
||||
pub fn type_is_freeze<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
ty.is_freeze(tcx, ty::ParamEnv::reveal_all(), DUMMY_SP)
|
||||
}
|
||||
|
||||
pub struct OperandBundleDef<'a, V> {
|
||||
pub name: &'a str,
|
||||
pub val: V
|
||||
}
|
||||
|
||||
impl<'a, V> OperandBundleDef<'a, V> {
|
||||
pub fn new(name: &'a str, val: V) -> Self {
|
||||
OperandBundleDef {
|
||||
name,
|
||||
val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A note on nomenclature of linking: "extern", "foreign", and "upcall".
|
||||
*
|
||||
|
@ -105,24 +79,24 @@ impl<'a, V> OperandBundleDef<'a, V> {
|
|||
/// When inside of a landing pad, each function call in LLVM IR needs to be
|
||||
/// annotated with which landing pad it's a part of. This is accomplished via
|
||||
/// the `OperandBundleDef` value created for MSVC landing pads.
|
||||
pub struct Funclet<'a, V> {
|
||||
cleanuppad: V,
|
||||
operand: OperandBundleDef<'a, V>,
|
||||
pub struct Funclet<'ll> {
|
||||
cleanuppad: &'ll Value,
|
||||
operand: OperandBundleDef<'ll>,
|
||||
}
|
||||
|
||||
impl<'a, V: CodegenObject> Funclet<'a, V> {
|
||||
pub fn new(cleanuppad: V) -> Self {
|
||||
impl Funclet<'ll> {
|
||||
pub fn new(cleanuppad: &'ll Value) -> Self {
|
||||
Funclet {
|
||||
cleanuppad,
|
||||
operand: OperandBundleDef::new("funclet", cleanuppad),
|
||||
operand: OperandBundleDef::new("funclet", &[cleanuppad]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanuppad(&self) -> V {
|
||||
pub fn cleanuppad(&self) -> &'ll Value {
|
||||
self.cleanuppad
|
||||
}
|
||||
|
||||
pub fn bundle(&self) -> &OperandBundleDef<'a, V> {
|
||||
pub fn bundle(&self) -> &OperandBundleDef<'ll> {
|
||||
&self.operand
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +106,7 @@ impl BackendTypes for CodegenCx<'ll, 'tcx> {
|
|||
type BasicBlock = &'ll BasicBlock;
|
||||
type Type = &'ll Type;
|
||||
type Context = &'ll llvm::Context;
|
||||
type Funclet = Funclet<'ll>;
|
||||
|
||||
type DIScope = &'ll llvm::debuginfo::DIScope;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ pub trait BackendTypes {
|
|||
type BasicBlock: Copy;
|
||||
type Type: CodegenObject;
|
||||
type Context;
|
||||
type Funclet;
|
||||
|
||||
type DIScope: Copy;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ use super::intrinsic::IntrinsicCallMethods;
|
|||
use super::type_::ArgTypeMethods;
|
||||
use super::HasCodegen;
|
||||
use builder::MemFlags;
|
||||
use common::*;
|
||||
use libc::c_char;
|
||||
use mir::operand::OperandRef;
|
||||
use mir::place::PlaceRef;
|
||||
|
@ -58,7 +57,7 @@ pub trait BuilderMethods<'a, 'tcx: 'a>:
|
|||
args: &[Self::Value],
|
||||
then: Self::BasicBlock,
|
||||
catch: Self::BasicBlock,
|
||||
funclet: Option<&Funclet<Self::Value>>,
|
||||
funclet: Option<&Self::Funclet>,
|
||||
) -> Self::Value;
|
||||
fn unreachable(&self);
|
||||
fn add(&self, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
|
||||
|
@ -213,10 +212,10 @@ pub trait BuilderMethods<'a, 'tcx: 'a>:
|
|||
fn add_clause(&self, landing_pad: Self::Value, clause: Self::Value);
|
||||
fn set_cleanup(&self, landing_pad: Self::Value);
|
||||
fn resume(&self, exn: Self::Value) -> Self::Value;
|
||||
fn cleanup_pad(&self, parent: Option<Self::Value>, args: &[Self::Value]) -> Self::Value;
|
||||
fn cleanup_ret(&self, cleanup: Self::Value, unwind: Option<Self::BasicBlock>) -> Self::Value;
|
||||
fn catch_pad(&self, parent: Self::Value, args: &[Self::Value]) -> Self::Value;
|
||||
fn catch_ret(&self, pad: Self::Value, unwind: Self::BasicBlock) -> Self::Value;
|
||||
fn cleanup_pad(&self, parent: Option<Self::Value>, args: &[Self::Value]) -> Self::Funclet;
|
||||
fn cleanup_ret(&self, funclet: &Self::Funclet, unwind: Option<Self::BasicBlock>) -> Self::Value;
|
||||
fn catch_pad(&self, parent: Self::Value, args: &[Self::Value]) -> Self::Funclet;
|
||||
fn catch_ret(&self, funclet: &Self::Funclet, unwind: Self::BasicBlock) -> Self::Value;
|
||||
fn catch_switch(
|
||||
&self,
|
||||
parent: Option<Self::Value>,
|
||||
|
@ -276,7 +275,7 @@ pub trait BuilderMethods<'a, 'tcx: 'a>:
|
|||
&self,
|
||||
llfn: Self::Value,
|
||||
args: &[Self::Value],
|
||||
funclet: Option<&Funclet<Self::Value>>,
|
||||
funclet: Option<&Self::Funclet>,
|
||||
) -> Self::Value;
|
||||
fn zext(&self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ pub trait HasCodegen<'tcx>: Backend<'tcx> {
|
|||
BasicBlock = Self::BasicBlock,
|
||||
Type = Self::Type,
|
||||
Context = Self::Context,
|
||||
Funclet = Self::Funclet,
|
||||
DIScope = Self::DIScope,
|
||||
>;
|
||||
}
|
||||
|
|
|
@ -894,7 +894,7 @@ fn codegen_msvc_try(
|
|||
Some(did) => cx.get_static(did),
|
||||
None => bug!("msvc_try_filter not defined"),
|
||||
};
|
||||
let tok = catchpad.catch_pad(cs, &[tydesc, cx.const_i32(0), slot]);
|
||||
let funclet = catchpad.catch_pad(cs, &[tydesc, cx.const_i32(0), slot]);
|
||||
let addr = catchpad.load(slot, ptr_align);
|
||||
|
||||
let i64_align = bx.tcx().data_layout.i64_align;
|
||||
|
@ -904,7 +904,7 @@ fn codegen_msvc_try(
|
|||
let local_ptr = catchpad.bitcast(local_ptr, i64p);
|
||||
catchpad.store(arg1, local_ptr, i64_align);
|
||||
catchpad.store(arg2, catchpad.inbounds_gep(local_ptr, &[val1]), i64_align);
|
||||
catchpad.catch_ret(tok, caught.llbb());
|
||||
catchpad.catch_ret(&funclet, caught.llbb());
|
||||
|
||||
caught.ret(cx.const_i32(1));
|
||||
});
|
||||
|
|
|
@ -28,7 +28,6 @@ use std::ffi::CStr;
|
|||
use std::cell::RefCell;
|
||||
use libc::{self, c_uint, c_char, size_t};
|
||||
use rustc_data_structures::small_c_str::SmallCStr;
|
||||
use common;
|
||||
|
||||
pub mod archive_ro;
|
||||
pub mod diagnostic;
|
||||
|
@ -272,10 +271,6 @@ impl OperandBundleDef<'a> {
|
|||
};
|
||||
OperandBundleDef { raw: def }
|
||||
}
|
||||
|
||||
pub fn from_generic(bundle: &common::OperandBundleDef<'a, &'a Value>) -> Self {
|
||||
Self::new(bundle.name, &[bundle.val])
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OperandBundleDef<'a> {
|
||||
|
|
|
@ -17,7 +17,7 @@ use abi::{Abi, FnType, PassMode};
|
|||
use rustc_target::abi::call::ArgType;
|
||||
use base;
|
||||
use builder::MemFlags;
|
||||
use common::{self, Funclet};
|
||||
use common;
|
||||
use rustc_codegen_utils::common::IntPredicate;
|
||||
use meth;
|
||||
use monomorphize;
|
||||
|
@ -67,7 +67,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
funclet_bb: Option<mir::BasicBlock>
|
||||
) -> impl for<'b> Fn(
|
||||
&'b FunctionCx<'a, 'tcx, Bx>,
|
||||
) -> Option<&'b Funclet<'static, Bx::Value>> {
|
||||
) -> Option<&'b Bx::Funclet> {
|
||||
move |this| {
|
||||
match funclet_bb {
|
||||
Some(funclet_bb) => this.funclets[funclet_bb].as_ref(),
|
||||
|
@ -77,8 +77,6 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
}
|
||||
let funclet = funclet_closure_factory(funclet_bb);
|
||||
|
||||
let cleanup_pad = |this: &Self| funclet(this).map(|lp| lp.cleanuppad());
|
||||
|
||||
let lltarget = |this: &mut Self, target: mir::BasicBlock| {
|
||||
let lltarget = this.blocks[target];
|
||||
let target_funclet = this.cleanup_kinds[target].funclet_bb(target);
|
||||
|
@ -106,7 +104,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
debug!("llblock: creating cleanup trampoline for {:?}", target);
|
||||
let name = &format!("{:?}_cleanup_trampoline_{:?}", bb, target);
|
||||
let trampoline = this.new_block(name);
|
||||
trampoline.cleanup_ret(cleanup_pad(this).unwrap(), Some(lltarget));
|
||||
trampoline.cleanup_ret(funclet(this).unwrap(), Some(lltarget));
|
||||
trampoline.llbb()
|
||||
} else {
|
||||
lltarget
|
||||
|
@ -119,7 +117,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
if is_cleanupret {
|
||||
// micro-optimization: generate a `ret` rather than a jump
|
||||
// to a trampoline.
|
||||
bx.cleanup_ret(cleanup_pad(this).unwrap(), Some(lltarget));
|
||||
bx.cleanup_ret(funclet(this).unwrap(), Some(lltarget));
|
||||
} else {
|
||||
bx.br(lltarget);
|
||||
}
|
||||
|
@ -175,8 +173,8 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
self.set_debug_loc(&bx, terminator.source_info);
|
||||
match terminator.kind {
|
||||
mir::TerminatorKind::Resume => {
|
||||
if let Some(cleanup_pad) = cleanup_pad(self) {
|
||||
bx.cleanup_ret(cleanup_pad, None);
|
||||
if let Some(funclet) = funclet(self) {
|
||||
bx.cleanup_ret(funclet, None);
|
||||
} else {
|
||||
let slot = self.get_personality_slot(&bx);
|
||||
let lp0 = bx.load_operand(slot.project_field(&bx, 0)).immediate();
|
||||
|
|
|
@ -18,7 +18,6 @@ use rustc::ty::subst::Substs;
|
|||
use rustc::session::config::DebugInfo;
|
||||
use base;
|
||||
use debuginfo::{self, VariableAccess, VariableKind, FunctionDebugContext};
|
||||
use common::Funclet;
|
||||
use monomorphize::Instance;
|
||||
use abi::{FnType, PassMode};
|
||||
use interfaces::*;
|
||||
|
@ -70,7 +69,7 @@ pub struct FunctionCx<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> {
|
|||
|
||||
/// When targeting MSVC, this stores the cleanup info for each funclet
|
||||
/// BB. This is initialized as we compute the funclets' head block in RPO.
|
||||
funclets: IndexVec<mir::BasicBlock, Option<Funclet<'static, Bx::Value>>>,
|
||||
funclets: IndexVec<mir::BasicBlock, Option<Bx::Funclet>>,
|
||||
|
||||
/// This stores the landing-pad block for a given BB, computed lazily on GNU
|
||||
/// and eagerly on MSVC.
|
||||
|
@ -372,7 +371,7 @@ fn create_funclets<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
cleanup_kinds: &IndexVec<mir::BasicBlock, CleanupKind>,
|
||||
block_bxs: &IndexVec<mir::BasicBlock, Bx::BasicBlock>)
|
||||
-> (IndexVec<mir::BasicBlock, Option<Bx::BasicBlock>>,
|
||||
IndexVec<mir::BasicBlock, Option<Funclet<'static, Bx::Value>>>)
|
||||
IndexVec<mir::BasicBlock, Option<Bx::Funclet>>)
|
||||
{
|
||||
block_bxs.iter_enumerated().zip(cleanup_kinds).map(|((bb, &llbb), cleanup_kind)| {
|
||||
match *cleanup_kind {
|
||||
|
@ -380,7 +379,7 @@ fn create_funclets<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
_ => return (None, None)
|
||||
}
|
||||
|
||||
let cleanup;
|
||||
let funclet;
|
||||
let ret_llbb;
|
||||
match mir[bb].terminator.as_ref().map(|t| &t.kind) {
|
||||
// This is a basic block that we're aborting the program for,
|
||||
|
@ -417,18 +416,18 @@ fn create_funclets<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
// represents that this is a catch-all block.
|
||||
let null = bx.cx().const_null(bx.cx().type_i8p());
|
||||
let sixty_four = bx.cx().const_i32(64);
|
||||
cleanup = cp_bx.catch_pad(cs, &[null, sixty_four, null]);
|
||||
funclet = cp_bx.catch_pad(cs, &[null, sixty_four, null]);
|
||||
cp_bx.br(llbb);
|
||||
}
|
||||
_ => {
|
||||
let cleanup_bx = bx.build_sibling_block(&format!("funclet_{:?}", bb));
|
||||
ret_llbb = cleanup_bx.llbb();
|
||||
cleanup = cleanup_bx.cleanup_pad(None, &[]);
|
||||
funclet = cleanup_bx.cleanup_pad(None, &[]);
|
||||
cleanup_bx.br(llbb);
|
||||
}
|
||||
};
|
||||
|
||||
(Some(ret_llbb), Some(Funclet::new(cleanup)))
|
||||
(Some(ret_llbb), Some(funclet))
|
||||
}).unzip()
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue