LLVM 3.0异常处理重设计

原文:http://blog.llvm.org/2011/11/llvm-30-exception-handling-redesign.html

LLVM 3.0中最大的IR改变之一是重新设计、实现了LLVMIR异常处理模型。旧的模型,虽然对大多数情形都工作,在某些关键情形下跌倒,导致隐蔽的误编译,未达成的优化,以及差劲的编译用时。本文讲述LLVM3.0中的改变,以及如何将一个现有的LLVM前端迁移到新的设计。它假定你对ItaniumC++ ABI异常处理部分有一定了解。

异常处理相同的目标

异常处理需要是LLVM IR的一等公民。这允许我们以一个聪明的方式操作异常处理信息(比如在内联期间)。同样,代码生成需要能够可靠地找出与指定invoke调用关联的各种信息(比如与一个调用一起使用的personality函数)。最后,我们需要遵守已制定的异常处理ABI来确保与其他编译器的二进制兼容。

尽管许多细节正确,异常处理才能工作(就ABI而言),我们的目标是保持LLVMIR的生成尽可能简单,操作尽可能灵活。通过使EH成为一等公民,新指令将具有简单、容易理解的语法,以及在每个代码转换后可以被测试确保IR正确的约束。

旧的异常处理系统

旧的系统使用LLVM固有函数来向代码生成器传递异常处理信息。旧系统的主要问题是,没有什么可以将这些固有函数绑定到可以回滚的invoke调用,这使得代码生成脆弱,像内联这样的优化无从表示(在通常情形下)。

另外,代码转换很难正确维护及更新固有函数:我们可以频繁地获取带有不正确信息的异常表(比如在没有在原始程序中指定时,指定一个指定的类型不能传播越过某点)。而不花费大量的工作,也不可能处理“清理”情形。

因为正常的代码移动,持有代码生成器生成正确表所需信息的固有函数,可以远离它们原本关联的invoke指令,即它们可以会离开invoke的着陆场(landingpad)。这使得前面异常处理结构的代码生成变得脆弱,有时会导致异常处理代码的错误编译,这是不可接受的。

最后(有时是理论上)的问题是旧系统仅对标准的personality函数工作。通过它使用定制的personality函数几乎不可能(比如返回在一个着陆场中的3个寄存器,而不是2个)。尽管对此我们没有特别的用例,我们不能使用定制的personality函数来优化代码大小或C++异常的执行。

LLVM 3.0异常处理系统

新异常处理系统的骨干是两个新指令landingpad与resume:

Landingpad

定义了一个着陆场基本块。它包含代码生成器生成正确EH表所需的所有信息。它也被要求是一个invoke指令的回滚终点里的第一条非PHI指令。另外,一个着陆场可能仅能从一条invoke指令的回滚边跳转过来。这些约束确保将回滚信息正确匹配到一个invoke调用总是可能的。它替换了固有函数@llvm.eh.exception及@llvm.eh.selector。

Resume

导致当前异常在栈里继续向上传播。它替换了固有函数@llvm.eh.resume。

下面是一个展示新语法的简单例子。对这个程序:

  void bar();
  void foo() throw (const char *) {
    try {
      bar();
    } catch (int) {
    }
  }

IR看起来像这样:

@_ZTIPKc = external constant i8*
  @_ZTIi = external constant i8*
  define void @_Z3foov() uwtable ssp {
  entry:
    invoke void @_Z3barv()
            to label %try.cont unwindlabel %lpad

  lpad:
    %0 = landingpad { i8*, i32 }personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*)
                                   catchi8* bitcast (i8** @_ZTIi to i8*)
                                   filter [1 xi8*] [i8* bitcast (i8** @_ZTIPKc to i8*)]
    %1 = extractvalue { i8*, i32 } %0, 0
    %2 = extractvalue { i8*, i32 } %0, 1
    %3 = tail call [email protected](i8* bitcast (i8** @_ZTIi to i8*)) nounwind
    %matches = icmp eq i32 %2, %3
    br i1 %matches, label %catch, label%filter.dispatch

  filter.dispatch:
    %ehspec.fails = icmp slt i32 %2, 0
    br i1 %ehspec.fails, label%ehspec.unexpected, label %eh.resume

  ehspec.unexpected:
    tail call void@__cxa_call_unexpected(i8* %1) no return
    unreachable

  catch:
    %4 = tail call i8*@__cxa_begin_catch(i8* %1) nounwind
    tail call void @__cxa_end_catch()nounwind
    br label %try.cont

  try.cont:
    ret void

  eh.resume:
    resume { i8*, i32 } %0
  }

Landingpad指令指定了EH运行时使用的personality函数,一组类型它可以捕捉的类型(int),以及一组foo被允许抛出的类型(constchar*)。

Resume指令重启异常的传播,如果它没有被捕捉并且是允许的类型。

转换到LLVM 3.0异常处理系统

从旧的EH API转换到新的EHAPI相当简单,因为许多复杂性被移除了。在LLVM2.9中为了生成EH代码,你必须类似这样做:

  Function*ExcIntr =
    Intrinsic::getDeclaration(TheModule,Intrinsic::eh_exception);
  Function *SlctrIntr =
    Intrinsic::getDeclaration(TheModule,Intrinsic::eh_selector);
  Function *PersonalityFn =
   Function::Create(FunctionType::get(Type::getInt32Ty(Context), true),
                    Function::ExternalLinkage,
                    "__gxx_personality_v0", TheModule);

  // Theexception pointer.
  Value *ExnPtr =Builder.CreateCall(ExcIntr, "exn");

  // Thearguments to the @llvm.eh.selector instruction.
  std::vector<Value*>Args;  Args.push_back(ExnPtr);
 Args.push_back(Builder.CreateBitCast(PersonalityFn,
                                      Type::getInt8PtrTy(Context)));

  // ...Complex code to add catch types, filters, cleanups, and catch-alls to Args ...
  // Theselector call.
  Value *Sel =Builder.CreateCall(SlctrIntr, Args, "exn.sel");

现在作为替代,你应该生成一条landingpad指令,返回一个异常对象以及选择符值:

 LandingPadInst *LPadInst =
   Builder.CreateLandingPad(StructType::get(Int8PtrTy, Int32Ty, NULL),
                             PersonalityFn, 0);
  Value *ExnPtr =Builder.CreateExtractValue(LPadInst, 0);
  Value *Sel =Builder.CreateExtractValue(LPadInst, 1);

现在向landingpad指令添加独立的分支变得微不足道。

  // Adding a catch clause
  Constant *TypeInfo =getTypeInfo();
  LPadInst->addClause(TypeInfo);

  // Adding aC++ catch-all
 LPadInst->addClause(Constant::getNullValue(Builder.getInt8PtrTy()));

  // Adding acleanup
  LPadInst->setCleanup(true);

  // Adding afilter clause
  std::vector<Value*> TypeInfos;
  Constant *TypeInfo = getFilterTypeInfo();
 TypeInfos.push_back(Builder.CreateBitCast(TypeInfo,Builder.getInt8PtrTy()));
  ArrayType *FilterTy =ArrayType::get(Int8PtrTy, TypeInfos.size());
 LPadInst-<addClause(ConstantArray::get(FilterTy, TypeInfos));

从固有函数@llvm.eh.resume转换到resume指令是简单的。它接受由landingpad指令返回的异常指针与异常选择符值:

  Type*UnwindDataTy = StructType::get(Builder.getInt8PtrTy(),
                                      Builder.getInt32Ty(), NULL);
  Value *UnwindData =UndefValue::get(UnwindDataTy);
  Value *ExcPtr = Builder.CreateLoad(getExceptionObjSlot());
  Value *ExcSel =Builder.CreateLoad(getExceptionSelSlot());
  UnwindData =Builder.CreateInsertValue(UnwindData, ExcPtr, 0, "exc_ptr");
  UnwindData =Builder.CreateInsertValue(UnwindData, ExcSel, 1, "exc_sel");
  Builder.CreateResume(UnwindData);

结论

新的EH系统比旧系统要好多了。它不那么脆弱、复杂。当你必须读IR来找出发生什么时,它更容易理解。更重要的,它允许我们比之前更遵从ABI。

更好的是,从旧系统转换到新系统相当简单。事实上,你可以看到你的代码简单多了!如果你对更多细节及参考信息感兴趣,请参考LLVMIR异常处理文档。

你可能感兴趣的:(compiler,编译器,llvm)