From 7c77dd317aa7ba0e60aed2793b1c240ca77f921f Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 09:10:20 +0300 Subject: [PATCH] Implement __artiq_personality. --- artiq/compiler/ir.py | 13 +- .../compiler/transforms/artiq_ir_generator.py | 5 +- .../compiler/transforms/llvm_ir_generator.py | 25 +- lit-test/libartiq_personality/Makefile | 2 +- .../libartiq_personality/artiq_terminate.c | 2 +- lit-test/test/exceptions/catch.py | 8 + soc/runtime/artiq_personality.c | 329 +++++++++++++++++- soc/runtime/artiq_personality.h | 2 +- 8 files changed, 373 insertions(+), 13 deletions(-) create mode 100644 lit-test/test/exceptions/catch.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 8e042e14e..94679fd18 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -979,10 +979,16 @@ class Raise(Terminator): """ :param value: (:class:`Value`) exception value + :param exn: (:class:`BasicBlock`) exceptional target """ - def __init__(self, value, name=""): + def __init__(self, value, exn=None, name=""): assert isinstance(value, Value) - super().__init__([value], builtins.TNone(), name) + if exn is not None: + assert isinstance(exn, BasicBlock) + operands = [value, exn] + else: + operands = [value] + super().__init__(operands, builtins.TNone(), name) def opcode(self): return "raise" @@ -990,6 +996,9 @@ class Raise(Terminator): def value(self): return self.operands[0] + def exception_target(self): + return self.operands[1] + class Invoke(Terminator): """ A function call operation that supports exception handling. diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 50ddffdb6..54b1efecf 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -472,7 +472,10 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.SetAttr(exn, "__file__", loc_file)) self.append(ir.SetAttr(exn, "__line__", loc_line)) self.append(ir.SetAttr(exn, "__col__", loc_column)) - self.append(ir.Raise(exn)) + if self.unwind_target: + self.append(ir.Raise(exn, self.unwind_target)) + else: + self.append(ir.Raise(exn)) def visit_Raise(self, node): self.raise_exn(self.visit(node.exc)) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 9f1aecf59..965ddad8c 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -582,17 +582,30 @@ class LLVMIRGenerator: return self.llbuilder.unreachable() def process_Raise(self, insn): - arg = self.map(insn.operands[0]) - llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [arg], - name=insn.name) + llexn = self.map(insn.value()) + if insn.exception_target() is not None: + llnormalblock = self.llfunction.append_basic_block("unreachable") + llnormalblock.terminator = ll.Unreachable(llnormalblock) + llnormalblock.instructions.append(llnormalblock.terminator) + + llunwindblock = self.map(insn.exception_target()) + llinsn = self.llbuilder.invoke(self.llbuiltin("__artiq_raise"), [llexn], + llnormalblock, llunwindblock, + name=insn.name) + else: + llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [llexn], + name=insn.name) + self.llbuilder.unreachable() llinsn.attributes.add('noreturn') - self.llbuilder.unreachable() return llinsn def process_LandingPad(self, insn): - lllandingpad = self.llbuilder.landingpad(ll.LiteralStructType([ll.IntType(8).as_pointer()]), + # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} + lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(), + ll.IntType(8).as_pointer()]) + lllandingpad = self.llbuilder.landingpad(lllandingpadty, self.llbuiltin("__artiq_personality")) - llrawexn = self.llbuilder.extract_value(lllandingpad, 0) + llrawexn = self.llbuilder.extract_value(lllandingpad, 1) llexn = self.llbuilder.bitcast(llrawexn, self.llty_of_type(insn.type)) llexnnameptr = self.llbuilder.gep(llexn, [self.llindex(0), self.llindex(0)]) llexnname = self.llbuilder.load(llexnnameptr) diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_personality/Makefile index 47daa9ac1..4fa32ab96 100644 --- a/lit-test/libartiq_personality/Makefile +++ b/lit-test/libartiq_personality/Makefile @@ -1,4 +1,4 @@ CC ?= clang libartiq_personality.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c - $(CC) -Wall -Werror -I. -I../../soc/runtime -fPIC -shared -o $@ $^ + $(CC) -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ diff --git a/lit-test/libartiq_personality/artiq_terminate.c b/lit-test/libartiq_personality/artiq_terminate.c index 9be7f36ca..c4c2bf7db 100644 --- a/lit-test/libartiq_personality/artiq_terminate.c +++ b/lit-test/libartiq_personality/artiq_terminate.c @@ -6,7 +6,7 @@ void __artiq_terminate(struct artiq_exception *exn) { printf("Uncaught %s: %s (%"PRIi64", %"PRIi64", %"PRIi64")\n" - "at %s:%"PRIi32":%"PRIi32"", + "at %s:%"PRIi32":%"PRIi32"\n", exn->name, exn->message, exn->param[0], exn->param[1], exn->param[1], exn->file, exn->line, exn->column + 1); diff --git a/lit-test/test/exceptions/catch.py b/lit-test/test/exceptions/catch.py new file mode 100644 index 000000000..4d2730859 --- /dev/null +++ b/lit-test/test/exceptions/catch.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# REQUIRES: exceptions + +try: + 1/0 +except ZeroDivisionError: + # CHECK-L: OK + print("OK") diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 5c18de55c..7057bde17 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -5,6 +5,198 @@ #include #include "artiq_personality.h" +/* Logging */ + +#ifndef NDEBUG +#define EH_LOG0(fmt) fprintf(stderr, "__artiq_personality: " fmt "\n") +#define EH_LOG(fmt, ...) fprintf(stderr, "__artiq_personality: " fmt "\n", __VA_ARGS__) +#else +#define EH_LOG0(fmt) +#define EH_LOG(fmt, ...) +#endif + +#define EH_FAIL(err) \ + do { \ + fprintf(stderr, "__artiq_personality fatal: %s\n", err); \ + abort(); \ + } while(0) + +#define EH_ASSERT(expr) \ + if(!(expr)) EH_FAIL(#expr) + +/* DWARF format handling */ + +enum { + DW_EH_PE_absptr = 0x00, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, + DW_EH_PE_indirect = 0x80, + DW_EH_PE_omit = 0xFF +}; + +// Read a uleb128 encoded value and advance pointer +// See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf +static uintptr_t readULEB128(const uint8_t **data) { + uintptr_t result = 0; + uintptr_t shift = 0; + unsigned char byte; + const uint8_t *p = *data; + + do { + byte = *p++; + result |= (byte & 0x7f) << shift; + shift += 7; + } + while (byte & 0x80); + + *data = p; + + return result; +} + +// Read a sleb128 encoded value and advance pointer +// See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf +static uintptr_t readSLEB128(const uint8_t **data) { + uintptr_t result = 0; + uintptr_t shift = 0; + unsigned char byte; + const uint8_t *p = *data; + + do { + byte = *p++; + result |= (byte & 0x7f) << shift; + shift += 7; + } + while (byte & 0x80); + + *data = p; + + if ((byte & 0x40) && (shift < (sizeof(result) << 3))) { + result |= (~0 << shift); + } + + return result; +} + +static unsigned getEncodingSize(uint8_t Encoding) { + if (Encoding == DW_EH_PE_omit) + return 0; + + switch (Encoding & 0x0F) { + case DW_EH_PE_absptr: + return sizeof(uintptr_t); + case DW_EH_PE_udata2: + return sizeof(uint16_t); + case DW_EH_PE_udata4: + return sizeof(uint32_t); + case DW_EH_PE_udata8: + return sizeof(uint64_t); + case DW_EH_PE_sdata2: + return sizeof(int16_t); + case DW_EH_PE_sdata4: + return sizeof(int32_t); + case DW_EH_PE_sdata8: + return sizeof(int64_t); + default: + // not supported + abort(); + } +} + +// Read a pointer encoded value and advance pointer +// See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf +static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { + uintptr_t result = 0; + const uint8_t *p = *data; + + if (encoding == DW_EH_PE_omit) + return(result); + + // first get value + switch (encoding & 0x0F) { + case DW_EH_PE_absptr: + result = *((uintptr_t*)p); + p += sizeof(uintptr_t); + break; + case DW_EH_PE_uleb128: + result = readULEB128(&p); + break; + // Note: This case has not been tested + case DW_EH_PE_sleb128: + result = readSLEB128(&p); + break; + case DW_EH_PE_udata2: + result = *((uint16_t*)p); + p += sizeof(uint16_t); + break; + case DW_EH_PE_udata4: + result = *((uint32_t*)p); + p += sizeof(uint32_t); + break; + case DW_EH_PE_udata8: + result = *((uint64_t*)p); + p += sizeof(uint64_t); + break; + case DW_EH_PE_sdata2: + result = *((int16_t*)p); + p += sizeof(int16_t); + break; + case DW_EH_PE_sdata4: + result = *((int32_t*)p); + p += sizeof(int32_t); + break; + case DW_EH_PE_sdata8: + result = *((int64_t*)p); + p += sizeof(int64_t); + break; + default: + // not supported + abort(); + break; + } + + // then add relative offset + switch (encoding & 0x70) { + case DW_EH_PE_absptr: + // do nothing + break; + case DW_EH_PE_pcrel: + result += (uintptr_t)(*data); + break; + case DW_EH_PE_textrel: + case DW_EH_PE_datarel: + case DW_EH_PE_funcrel: + case DW_EH_PE_aligned: + default: + // not supported + abort(); + break; + } + + // then apply indirection + if (encoding & DW_EH_PE_indirect) { + result = *((uintptr_t*)result); + } + + *data = p; + + return result; +} + + +/* Raising and catching */ + #define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' struct artiq_raised_exception { @@ -37,5 +229,140 @@ void __artiq_raise(struct artiq_exception *artiq_exn) { _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { - abort(); + EH_LOG("entry (actions =%s%s%s%s; class=%08lx; object=%p, context=%p)", + (actions & _UA_SEARCH_PHASE ? " search" : ""), + (actions & _UA_CLEANUP_PHASE ? " cleanup" : ""), + (actions & _UA_HANDLER_FRAME ? " handler" : ""), + (actions & _UA_FORCE_UNWIND ? " force-unwind" : ""), + exceptionClass, exceptionObject, context); + EH_ASSERT((exceptionClass == ARTIQ_EXCEPTION_CLASS) && + "Foreign exceptions are not supported"); + + struct artiq_raised_exception *inflight = + (struct artiq_raised_exception*)exceptionObject; + EH_LOG("exception name=%s", + inflight->artiq.name); + + // Get a pointer to LSDA. If there's no LSDA, this function doesn't + // actually handle any exceptions. + const uint8_t *lsda = (const uint8_t*) _Unwind_GetLanguageSpecificData(context); + if(lsda == NULL) + return _URC_CONTINUE_UNWIND; + + EH_LOG("lsda=%p", lsda); + + // Get the current instruction pointer and offset it before next + // instruction in the current frame which threw the exception. + uintptr_t pc = _Unwind_GetIP(context) - 1; + + // Get beginning of the current frame's code. + uintptr_t funcStart = _Unwind_GetRegionStart(context); + uintptr_t pcOffset = pc - funcStart; + + EH_LOG("pc=%p (%p+%p)", (void*)pc, (void*)funcStart, (void*)pcOffset); + + // Parse LSDA header. + uint8_t lpStartEncoding = *lsda++; + if (lpStartEncoding != DW_EH_PE_omit) { + readEncodedPointer(&lsda, lpStartEncoding); + } + + uint8_t ttypeEncoding = *lsda++; + const uint8_t *classInfo = NULL; + if (ttypeEncoding != DW_EH_PE_omit) { + // Calculate type info locations in emitted dwarf code which + // were flagged by type info arguments to llvm.eh.selector + // intrinsic + uintptr_t classInfoOffset = readULEB128(&lsda); + classInfo = lsda + classInfoOffset; + EH_LOG("classInfo=%p", classInfo); + } + + // Walk call-site table looking for range that includes current PC. + uint8_t callSiteEncoding = *lsda++; + uint32_t callSiteTableLength = readULEB128(&lsda); + const uint8_t *callSiteTableStart = lsda; + const uint8_t *callSiteTableEnd = callSiteTableStart + callSiteTableLength; + const uint8_t *actionTableStart = callSiteTableEnd; + const uint8_t *callSitePtr = callSiteTableStart; + + while (callSitePtr < callSiteTableEnd) { + uintptr_t start = readEncodedPointer(&callSitePtr, + callSiteEncoding); + uintptr_t length = readEncodedPointer(&callSitePtr, + callSiteEncoding); + uintptr_t landingPad = readEncodedPointer(&callSitePtr, + callSiteEncoding); + uintptr_t actionValue = readULEB128(&callSitePtr); + + EH_LOG("call site (start=+%p, len=%d, landingPad=+%p, actionValue=%d)", + (void*)start, (int)length, (void*)landingPad, (int)actionValue); + + if(landingPad == 0) { + EH_LOG0("no landing pad, skipping"); + continue; + } + + if ((start <= pcOffset) && (pcOffset < (start + length))) { + EH_LOG0("call site matches pc"); + + int exceptionMatched = 0; + if(actionValue) { + const uint8_t *actionEntry = actionTableStart + (actionValue - 1); + EH_LOG("actionEntry=%p", actionEntry); + + for(;;) { + // Each emitted DWARF action corresponds to a 2 tuple of + // type info address offset, and action offset to the next + // emitted action. + intptr_t typeInfoOffset = readSLEB128(&actionEntry); + const uint8_t *tempActionEntry = actionEntry; + intptr_t actionOffset = readSLEB128(&tempActionEntry); + EH_LOG("typeInfoOffset=%p actionOffset=%p", + (void*)typeInfoOffset, (void*)actionOffset); + EH_ASSERT((typeInfoOffset >= 0) && "Filter clauses are not supported"); + + unsigned encodingSize = getEncodingSize(ttypeEncoding); + const uint8_t *typeInfoPtrPtr = classInfo - typeInfoOffset * encodingSize; + uintptr_t typeInfoPtr = readEncodedPointer(&typeInfoPtrPtr, ttypeEncoding); + EH_LOG("encodingSize=%u typeInfoPtrPtr=%p typeInfoPtr=%p", + encodingSize, typeInfoPtrPtr, (void*)typeInfoPtr); + EH_LOG("typeInfo=%s", (char*)typeInfoPtr); + + if(inflight->artiq.typeinfo == typeInfoPtr) { + EH_LOG0("matching action found"); + exceptionMatched = 1; + break; + } + + if (!actionOffset) + break; + + actionEntry += actionOffset; + } + } + + if (!(actions & _UA_SEARCH_PHASE)) { + EH_LOG0("jumping to landing pad"); + + _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), + (uintptr_t)exceptionObject); + _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), + (uintptr_t)&inflight->artiq); + _Unwind_SetIP(context, funcStart + landingPad); + + return _URC_INSTALL_CONTEXT; + } else if(exceptionMatched) { + EH_LOG0("handler found"); + + return _URC_HANDLER_FOUND; + } else { + EH_LOG0("handler not found"); + + return _URC_CONTINUE_UNWIND; + } + } + } + + return _URC_CONTINUE_UNWIND; } diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 9c874e516..45b0ef9f1 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -5,7 +5,7 @@ struct artiq_exception { union { - void *typeinfo; + uintptr_t typeinfo; const char *name; }; const char *file;