您的位置:首页 > 其它

LLVM 3.0异常处理重设计

2016-07-08 11:45 330 查看
原文: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 i32@llvm.eh.typeid.for(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