LLVM笔记(14) - LLVM自定义RTTI的实现
趁着放假把之前记得笔记整理下, 顺带看下是否有新的理解, 首先从ADT与Support目录开始.
LLVM扩展了运行时类型识别能力, 提供一组自定义接口用于安全的动态判断与类型转换(isa<>, dyn_cast<>, .etc), 熟悉LLVM代码的开发者应该比较熟悉其使用方法了, 对于不熟悉的开发者可以先阅读官方文档增加了解.
实现方式
与标准C++的dynamic_cast实现类似, LLVM的cast机制也是利用模板实现, 其实现参见include/llvm/Support/Casting.h, 这里截取部分代码说明.
isa<>
LLVM提供了两个接口isa<>以及isa_and_nonull<>, 其中后者相比前者还额外做了指针非空的判断(由于isa<>实现中会访问内存, 因此在不确定指针一定非空时可以使用后者).
两者通过调用isa_impl_wrap<>::doit()最终调用isa_impl<>::doit().
template <typename To, typename From, typename Enabler = void>
struct isa_impl {
static inline bool doit(const From &Val) {
return To::classof(&Val);
}
};
template <typename To, typename From> struct isa_impl_cl {
static inline bool doit(const From &Val) {
return isa_impl<To, From>::doit(Val);
}
};
template<typename To, typename From, typename SimpleFrom> struct isa_impl_wrap {
// When From != SimplifiedType, we can simplify the type some more by using the simplify_type template.
static bool doit(const From &Val) {
return isa_impl_wrap<To, SimpleFrom, typename simplify_type<SimpleFrom>::SimpleType>::doit(
simplify_type<const From>::getSimplifiedValue(Val));
}
};
template<typename To, typename FromTy> struct isa_impl_wrap<To, FromTy, FromTy> {
// When From == SimpleType, we are as simple as we are going to get.
static bool doit(const FromTy &Val) {
return isa_impl_cl<To,FromTy>::doit(Val);
}
};
template <class X, class Y> LLVM_NODISCARD inline bool isa(const Y &Val) {
return isa_impl_wrap<X, const Y, typename simplify_type<const Y>::SimpleType>::doit(Val);
}
template <class X, class Y>
LLVM_NODISCARD inline bool isa_and_nonnull(const Y &Val) {
if (!Val)
return false;
return isa<X>(Val);
}
而isa_impl<>::doit()会调用目标类(To)的classof接口, 以Instruction类为例其通过判断传入的Value类的ValueID判断是否可以转换为Instruction, 又如AllocaInst类, 其作为Instruction的子类需要判断两个条件isa
// member of Instruction class
static bool classof(const Value *V) {
return V->getValueID() >= Value::InstructionVal;
}
// member of AllocaInst class
static bool classof(const Instruction *I) {
return (I->getOpcode() == Instruction::Alloca);
}
// member of AllocaInst class
static bool classof(const Value *V) {
return isa<Instruction>(V) && classof(cast<Instruction>(V));
}
总而言之, 如果需要对自定义类使能isa<>接口就需要为每个类设置一个特殊的枚举, 并在对应类的classof()中判断传入的对象ID是否为期望值.
另外要注意的一点是, 对于基类是不用实现classof()接口的, i.e. Instruction类的基类是Value(defined in include/llvm/IR/Value.h)就没有实现classof().
这是因为isa<>默认是往down cast, 对于up cast的情况实现了fast path, 而Value类作为基类一定是up cast的, 因此会直接cast.
/// Always allow upcasts, and perform no dynamic check for them.
template <typename To, typename From>
struct isa_impl<To, From, typename std::enable_if<std::is_base_of<To, From>::value>::type> {
static inline bool doit(const From &) { return true; }
};
dyn_cast<>
与isa<>类似dyn_cast<>也有两种接口, 其中dyn_cast_or_null<>可以在输入非安全(空指针)的情况下使用. dyn_cast<>首先通过isa<>判断cast操作是否安全, 然后调用cast<>, 后者会调用cast_convert_val<>. cast_convert_val<>会递归转换类型直到满足传入类型From与SimpleFrom类型一致, 再做const_cast<>.
template<class To, class From, class SimpleFrom> struct cast_convert_val {
// This is not a simple type, use the template to simplify it...
static typename cast_retty<To, From>::ret_type doit(From &Val) {
return cast_convert_val<To, SimpleFrom, typename simplify_type<SimpleFrom>::SimpleType>::doit(
simplify_type<From>::getSimplifiedValue(Val));
}
};
template<class To, class FromTy> struct cast_convert_val<To,FromTy,FromTy> {
// This _is_ a simple type, just cast it.
static typename cast_retty<To, FromTy>::ret_type doit(const FromTy &Val) {
typename cast_retty<To, FromTy>::ret_type Res2 = (typename cast_retty<To, FromTy>::ret_type)const_cast<FromTy&>(Val);
return Res2;
}
};
template <class X, class Y> inline typename cast_retty<X, Y *>::ret_type cast(Y *Val) {
assert(isa<X>(Val) && "cast<Ty>() argument of incompatible type!");
return cast_convert_val<X, Y*, typename simplify_type<Y*>::SimpleType>::doit(Val);
}
template <class X, class Y> LLVM_NODISCARD inline typename cast_retty<X, Y *>::ret_type dyn_cast(Y *Val) {
return isa<X>(Val) ? cast<X>(Val) : nullptr;
}
template <class X, class Y> LLVM_NODISCARD inline typename cast_retty<X, Y *>::ret_type dyn_cast_or_null(Y *Val) {
return (Val && isa<X>(Val)) ? cast<X>(Val) : nullptr;
}
相关问题
问题背景是同事遇到的一个问题: 在判断phi节点来源时调用isa
int *test() {
return (int *)255;
}
编译成IR:
; Function Attrs: norecurse nounwind readnone uwtable
define dso_local i32* @test() local_unnamed_addr #0 {
entry:
ret i32* inttoptr (i64 255 to i32*)
}
从IR上看inttoptr仿佛是一条IntToPtrInst, 但是用isa<>却捕捉不到这条指令. 打印classof()返回值发现isa
这是因为ConstExpr与Instruction类都有IntToPtr这一表达式, 而LLVM为了复用枚举与打印, 在创建ConstExpr时也使用Instruction::IntToPtr.
以ConstantExpr::getIntToPtr()(defined in lib/IR/Constants.cpp)为例:
Constant *ConstantExpr::getIntToPtr(Constant *C, Type *DstTy, bool OnlyIfReduced) {
assert(C->getType()->isIntOrIntVectorTy() && "IntToPtr source must be integer or integer vector");
assert(DstTy->isPtrOrPtrVectorTy() && "IntToPtr destination must be a pointer or pointer vector");
assert(isa<VectorType>(C->getType()) == isa<VectorType>(DstTy));
if (isa<VectorType>(C->getType()))
assert(C->getType()->getVectorNumElements()==DstTy->getVectorNumElements()&&
"Invalid cast between a different number of vector elements");
return getFoldedCast(Instruction::IntToPtr, C, DstTy, OnlyIfReduced);
}
这样就可以复用Instruction::getOpcodeName()(defined in lib/IR/Instruction.cpp)打印同一表达式:
const char *Instruction::getOpcodeName(unsigned OpCode) {
switch (Opcode) {
case IntToPtr: return "inttoptr";
}
所以在做类型判断时要考虑从基类开始的类型判断, 就这个例子而言Value类的子类可能是Expr或Instruction, 那么首先要判断是否是Instrcution再判断是否是Instruction的子类, 而这两个判断所依赖的条件并不相同(前者判断Value->ValueID, 后者判断Instruction->Opcode), 因此在扩展子类时也要考虑原有类型设计.