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和classof(cast(V)), 其中第二个条件判断的是Instruction的Opcode成员.

// 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时发现捕捉不到这类cast情况, 测试用例如下:

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就返回false.
这是因为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), 因此在扩展子类时也要考虑原有类型设计.

posted @ 2020-10-03 21:26  Five100Miles  阅读(2110)  评论(0编辑  收藏  举报