Exception
In this section, you will see how to reuse c++ exception in LLVM.
First, we need to setup a set of function from c++ ABI:
type ModuleWithException struct {
*ir.Module
_ZTIi *ir.Global
__cxa_allocate_exception *ir.Func
__cxa_throw *ir.Func
__cxa_begin_catch *ir.Func
__cxa_end_catch *ir.Func
__cxa_call_unexpected *ir.Func
llvm_eh_typeid_for *ir.Func
}
func NewModuleWithException() *ModuleWithException {
m := ir.NewModule()
mWithE := &ModuleWithException{
Module: m,
_ZTIi: m.NewGlobal("_ZTIi", TPtr(TI8)),
__cxa_allocate_exception: m.NewFunc("__cxa_allocate_exception", TPtr(TI8),
ir.NewParam("", TI64),
),
__cxa_throw: m.NewFunc("__cxa_throw", TVoid,
ir.NewParam("exception_header", TPtr(TI8)),
ir.NewParam("", TPtr(TI8)),
ir.NewParam("", TPtr(TI8)),
),
__cxa_begin_catch: m.NewFunc("__cxa_begin_catch", TPtr(TI8), ir.NewParam("", TPtr(TI8))),
__cxa_end_catch: m.NewFunc("__cxa_end_catch", TVoid),
__cxa_call_unexpected: m.NewFunc("__cxa_call_unexpected", TVoid, ir.NewParam("", TPtr(TI8))),
llvm_eh_typeid_for: m.NewFunc("llvm.eh.typeid.for", TI32, ir.NewParam("", TPtr(TI8))),
}
mWithE._ZTIi.Linkage = enum.LinkageExternal
return mWithE
}
And a helper for throw exception from a block:
func throwException(m *ModuleWithException, bb *ir.Block) {
// C++ requires one allocate an exception first
payload := bb.NewCall(m.__cxa_allocate_exception, CI64(4))
// now we stores I32 `1` into payload
bb.NewStore(CI32(1), bb.NewBitCast(payload, TPtr(TI32)))
// finally, we call `__cxa_throw` to throw exception
bb.NewCall(m.__cxa_throw,
payload,
constant.NewBitCast(m._ZTIi, TPtr(TI8)),
constant.NewNull(TPtr(TI8)),
)
}
Finally, is our full example
m := NewModuleWithException()
exceptionThrower := m.NewFunc("I throw exception!", TI32)
bb := exceptionThrower.NewBlock("")
throwException(m, bb)
bb.NewRet(CI32(1))
main := m.NewFunc("main", TI32)
main.Personality = constant.True
mainB := main.NewBlock("")
normalRetB := main.NewBlock("normalRet")
exceptionRetB := main.NewBlock("exceptionRet")
// we must use invoke when a function might throw exception, we need to give
// 1. normal return block for function returns normally
// 2. exception return block for function throws an exception
mainB.NewInvoke(exceptionThrower, []value.Value{}, normalRetB, exceptionRetB)
normalRetB.NewRet(CI32(0))
// landingpad stands for catch and cleanup
exc := exceptionRetB.NewLandingPad(types.NewStruct(TPtr(TI8), TI32),
ir.NewClause(enum.ClauseTypeCatch, constant.NewBitCast(m._ZTIi, TPtr(TI8))),
ir.NewClause(enum.ClauseTypeFilter,
constant.NewArray(types.NewArray(1, TPtr(TI8)), constant.NewBitCast(m._ZTIi, TPtr(TI8))),
),
)
exc.Cleanup = true
exc_ptr := exceptionRetB.NewExtractValue(exc, 0)
exc_sel := exceptionRetB.NewExtractValue(exc, 1)
tid_int := exceptionRetB.NewCall(m.llvm_eh_typeid_for, constant.NewBitCast(m._ZTIi, TPtr(TI8)))
tid_int.Tail = enum.TailTail
catchintB := main.NewBlock("catchint")
cleanupB := main.NewBlock("cleanup")
cleanupB.NewResume(exc)
// we check typeinfo is as expected
// 1. if type info is same as our expection, we going to catchint block to handle threw I32
// 2. if not, we cleanup exception
tst_int := exceptionRetB.NewICmp(enum.IPredEQ, exc_sel, tid_int)
exceptionRetB.NewCondBr(tst_int, catchintB, cleanupB)
// in catchint block, we
// 1. call `__cxa_begin_catch` to begin catching
// 2. load payload to get threw I32
// 3. call `__cxa_end_catch` to end catching
payload := catchintB.NewCall(m.__cxa_begin_catch, exc_ptr)
payload.Tail = enum.TailTail
payload_int := catchintB.NewBitCast(payload, TPtr(TI32))
retval := catchintB.NewLoad(TI32, payload_int)
end_catch := catchintB.NewCall(m.__cxa_end_catch)
end_catch.Tail = enum.TailTail
// Finally, we return threw value from main
returnB := main.NewBlock("return")
catchintB.NewBr(returnB)
returnB.NewRet(retval)