| // Copyright 2024 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| // Must define _GNU_SOURCE before includes to get Dl_info/dladdr from dlfcn.h. |
| #define _GNU_SOURCE |
| |
| #include "iree/base/target_platform.h" |
| |
| #if defined(IREE_PLATFORM_APPLE) |
| #include <dlfcn.h> |
| #include <execinfo.h> |
| #define IREE_HAVE_BACKTRACE 1 |
| #define IREE_STATUS_HAVE_STACK_TRACE_SUPPORT 1 |
| #elif defined(IREE_PLATFORM_WINDOWS) |
| #pragma warning(disable : 4091) |
| #include <dbghelp.h> |
| #define IREE_STATUS_HAVE_STACK_TRACE_SUPPORT 1 |
| #elif (defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_LINUX)) && \ |
| defined(IREE_HAVE_LIBBACKTRACE) |
| // Include our wrapper header which re-exports backtrace.h. |
| // This allows Bazel's layering check to see the header dependency. |
| #include <dlfcn.h> |
| #include <iree_libbacktrace.h> |
| #define IREE_USE_LIBBACKTRACE 1 |
| #define IREE_STATUS_HAVE_STACK_TRACE_SUPPORT 1 |
| #endif // IREE_PLATFORM_* |
| |
| #include <assert.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| |
| #include "iree/base/alignment.h" |
| #include "iree/base/allocator.h" |
| #include "iree/base/assert.h" |
| #include "iree/base/config.h" |
| #include "iree/base/printf.h" |
| #include "iree/base/status.h" |
| #include "iree/base/status_payload.h" |
| #include "iree/base/tracing.h" |
| |
| #if defined(IREE_STATUS_HAVE_STACK_TRACE_SUPPORT) && \ |
| ((IREE_STATUS_FEATURES & IREE_STATUS_FEATURE_STACK_TRACE) != 0) |
| |
| // TODO(benvanik): make a string_view utility to share with console tracing. |
| static iree_string_view_t iree_status_trim_file_path(const char* file_name) { |
| size_t file_name_length = strlen(file_name); |
| for (int i = (int)file_name_length - 1; i >= 0; --i) { |
| char c = file_name[i]; |
| if (c == '/' || c == '\\') { |
| return iree_make_string_view(file_name + i + 1, file_name_length - i - 1); |
| } |
| } |
| return iree_make_string_view(file_name, file_name_length); |
| } |
| |
| static iree_host_size_t iree_string_buffer_append_cstr( |
| iree_host_size_t buffer_capacity, char* buffer, |
| iree_host_size_t buffer_length, const char* str) { |
| iree_host_size_t n = |
| iree_snprintf(buffer ? buffer + buffer_length : NULL, |
| buffer ? buffer_capacity - buffer_length : 0, "%s", str); |
| return IREE_UNLIKELY(n < 0) ? 0 : buffer_length + n; |
| } |
| |
| static iree_host_size_t IREE_PRINTF_ATTRIBUTE(4, 5) |
| iree_string_buffer_append_format(iree_host_size_t buffer_capacity, |
| char* buffer, |
| iree_host_size_t buffer_length, |
| const char* format, ...) { |
| va_list varargs; |
| va_start(varargs, format); |
| iree_host_size_t n = iree_vsnprintf( |
| buffer ? buffer + buffer_length : NULL, |
| buffer ? buffer_capacity - buffer_length : 0, format, varargs); |
| va_end(varargs); |
| return IREE_UNLIKELY(n < 0) ? 0 : buffer_length + n; |
| } |
| |
| #if defined(IREE_USE_LIBBACKTRACE) |
| |
| // pthread_once is used for thread-safe one-time initialization. |
| // We inline it here rather than using iree/base/threading/call_once.h because: |
| // 1. The :threading target has a circular dependency with :base. |
| // 2. Bare metal builds may not have pthreads, but libbacktrace requires it. |
| #include <pthread.h> |
| |
| // Global backtrace state - initialized on first use. |
| static struct backtrace_state* iree_libbacktrace_state = NULL; |
| static pthread_once_t iree_libbacktrace_init_once = PTHREAD_ONCE_INIT; |
| |
| static void iree_libbacktrace_error_callback(void* data, const char* msg, |
| int errnum) { |
| // Silently ignore errors - we don't want errors while reporting errors. |
| (void)data; |
| (void)msg; |
| (void)errnum; |
| } |
| |
| static void iree_libbacktrace_initialize(void) { |
| iree_libbacktrace_state = |
| backtrace_create_state(NULL, // Let libbacktrace find the executable. |
| 1, // threaded = true |
| iree_libbacktrace_error_callback, NULL); |
| } |
| |
| static struct backtrace_state* iree_libbacktrace_get_state(void) { |
| pthread_once(&iree_libbacktrace_init_once, iree_libbacktrace_initialize); |
| return iree_libbacktrace_state; |
| } |
| |
| // Capture callback for backtrace_simple(). |
| typedef struct { |
| uintptr_t* addresses; |
| int max_frames; |
| int frame_count; |
| } iree_libbacktrace_capture_state_t; |
| |
| static int iree_libbacktrace_capture_callback(void* data, uintptr_t pc) { |
| iree_libbacktrace_capture_state_t* state = |
| (iree_libbacktrace_capture_state_t*)data; |
| if (state->frame_count >= state->max_frames) return 1; // Stop. |
| state->addresses[state->frame_count++] = pc; |
| return 0; // Continue. |
| } |
| |
| // Format callback state for backtrace_pcinfo(). |
| typedef struct { |
| iree_host_size_t buffer_capacity; |
| char* buffer; |
| iree_host_size_t buffer_length; |
| bool found; |
| } iree_libbacktrace_format_state_t; |
| |
| static int iree_libbacktrace_pcinfo_callback(void* data, uintptr_t pc, |
| const char* filename, int lineno, |
| const char* function) { |
| iree_libbacktrace_format_state_t* state = |
| (iree_libbacktrace_format_state_t*)data; |
| state->found = true; |
| |
| // Format: module <function+offset> (file:line) |
| // We use dladdr for module name since libbacktrace doesn't provide it |
| // directly in this callback. |
| Dl_info info; |
| if (dladdr((void*)pc, &info) != 0 && info.dli_fname) { |
| iree_string_view_t fname = iree_status_trim_file_path(info.dli_fname); |
| state->buffer_length = iree_string_buffer_append_format( |
| state->buffer_capacity, state->buffer, state->buffer_length, "%.*s", |
| (int)fname.size, fname.data); |
| } else { |
| state->buffer_length = iree_string_buffer_append_cstr( |
| state->buffer_capacity, state->buffer, state->buffer_length, "???"); |
| } |
| |
| state->buffer_length = iree_string_buffer_append_cstr( |
| state->buffer_capacity, state->buffer, state->buffer_length, " <"); |
| if (function) { |
| state->buffer_length = iree_string_buffer_append_cstr( |
| state->buffer_capacity, state->buffer, state->buffer_length, function); |
| } else { |
| state->buffer_length = iree_string_buffer_append_cstr( |
| state->buffer_capacity, state->buffer, state->buffer_length, "???"); |
| } |
| state->buffer_length = iree_string_buffer_append_cstr( |
| state->buffer_capacity, state->buffer, state->buffer_length, ">"); |
| |
| if (filename && lineno > 0) { |
| iree_string_view_t file = iree_status_trim_file_path(filename); |
| state->buffer_length = iree_string_buffer_append_format( |
| state->buffer_capacity, state->buffer, state->buffer_length, |
| " (%.*s:%d)", (int)file.size, file.data, lineno); |
| } |
| |
| return 0; // Continue (for inlined frames). |
| } |
| |
| #endif // IREE_USE_LIBBACKTRACE |
| |
| #if defined(IREE_PLATFORM_WINDOWS) |
| |
| typedef BOOL(WINAPI* PFN_SymInitialize)(HANDLE, PCSTR, BOOL); |
| typedef BOOL(WINAPI* PFN_SymCleanup)(HANDLE); |
| typedef BOOL(WINAPI* PFN_SymGetModuleInfo64)(HANDLE, DWORD64, |
| PIMAGEHLP_MODULE64); |
| typedef BOOL(WINAPI* PFN_SymFromAddr)(HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO); |
| typedef BOOL(WINAPI* PFN_SymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, |
| PIMAGEHLP_LINE); |
| typedef struct iree_symbol_resolver_t { |
| SRWLOCK mutex; |
| HMODULE library; |
| PFN_SymInitialize SymInitialize; |
| PFN_SymCleanup SymCleanup; |
| PFN_SymGetModuleInfo64 SymGetModuleInfo64; |
| PFN_SymFromAddr SymFromAddr; |
| PFN_SymGetLineFromAddr64 SymGetLineFromAddr64; |
| } iree_symbol_resolver_t; |
| |
| // If tracy is enabled then it has its own dbghelp lock we need to use. |
| // If not enabled then we |
| #if defined(TRACY_ENABLE) |
| void IREEDbgHelpInit(void); |
| void IREEDbgHelpLock(void); |
| void IREEDbgHelpUnlock(void); |
| static void iree_symbol_resolver_initialize_mutex( |
| iree_symbol_resolver_t* resolver) { |
| IREEDbgHelpInit(); |
| } |
| static void iree_symbol_resolver_lock(iree_symbol_resolver_t* resolver) { |
| IREEDbgHelpLock(); |
| } |
| static void iree_symbol_resolver_unlock(iree_symbol_resolver_t* resolver) { |
| IREEDbgHelpUnlock(); |
| } |
| #else |
| static void iree_symbol_resolver_initialize_mutex( |
| iree_symbol_resolver_t* resolver) { |
| InitializeSRWLock(&resolver->mutex); |
| } |
| static void iree_symbol_resolver_lock(iree_symbol_resolver_t* resolver) { |
| AcquireSRWLockExclusive(&resolver->mutex); |
| } |
| static void iree_symbol_resolver_unlock(iree_symbol_resolver_t* resolver) { |
| ReleaseSRWLockExclusive(&resolver->mutex); |
| } |
| #endif // TRACY_ENABLE |
| |
| static void iree_symbol_resolver_initialize( |
| iree_symbol_resolver_t* out_resolver) { |
| memset(out_resolver, 0, sizeof(*out_resolver)); |
| iree_symbol_resolver_initialize_mutex(out_resolver); |
| out_resolver->library = LoadLibraryA("dbghelp.dll"); |
| if (!out_resolver->library) return; |
| out_resolver->SymInitialize = |
| (PFN_SymInitialize)GetProcAddress(out_resolver->library, "SymInitialize"); |
| out_resolver->SymCleanup = |
| (PFN_SymCleanup)GetProcAddress(out_resolver->library, "SymCleanup"); |
| out_resolver->SymGetModuleInfo64 = (PFN_SymGetModuleInfo64)GetProcAddress( |
| out_resolver->library, "SymGetModuleInfo64"); |
| out_resolver->SymFromAddr = |
| (PFN_SymFromAddr)GetProcAddress(out_resolver->library, "SymFromAddr"); |
| out_resolver->SymGetLineFromAddr64 = (PFN_SymGetLineFromAddr64)GetProcAddress( |
| out_resolver->library, "SymGetLineFromAddr64"); |
| if (!out_resolver->SymInitialize || !out_resolver->SymCleanup || |
| !out_resolver->SymGetModuleInfo64 || !out_resolver->SymFromAddr || |
| !out_resolver->SymGetLineFromAddr64) { |
| FreeLibrary(out_resolver->library); |
| memset(out_resolver, 0, sizeof(*out_resolver)); |
| return; |
| } |
| out_resolver->SymInitialize(GetCurrentProcess(), /*UserSearchPath=*/NULL, |
| /*fInvadeProcess=*/TRUE); |
| } |
| |
| static void iree_symbol_resolver_deinitialize( |
| iree_symbol_resolver_t* resolver) { |
| if (resolver->SymCleanup) { |
| resolver->SymCleanup(GetCurrentProcess()); |
| } |
| if (resolver->library) { |
| FreeLibrary(resolver->library); |
| resolver->library = NULL; |
| } |
| memset(resolver, 0, sizeof(*resolver)); |
| } |
| |
| static BOOL CALLBACK iree_symbol_resolver_setup(PINIT_ONCE InitOnce, |
| PVOID Parameter, |
| PVOID* Context) { |
| ((void)InitOnce); |
| ((void)Context); |
| iree_symbol_resolver_initialize((iree_symbol_resolver_t*)Parameter); |
| return TRUE; |
| } |
| |
| static INIT_ONCE instance_flag = INIT_ONCE_STATIC_INIT; |
| static iree_symbol_resolver_t instance; |
| static iree_symbol_resolver_t* iree_symbol_resolver_get(void) { |
| InitOnceExecuteOnce(&instance_flag, iree_symbol_resolver_setup, |
| (PVOID)&instance, NULL); |
| return &instance; |
| } |
| |
| static bool iree_symbol_resolver_format_frame(iree_symbol_resolver_t* resolver, |
| void* address, |
| iree_host_size_t buffer_capacity, |
| char* buffer, |
| iree_host_size_t* buffer_length) { |
| if (IREE_UNLIKELY(!resolver->library)) return false; |
| |
| HANDLE process = GetCurrentProcess(); |
| |
| // Query generic information like what module the address is located in and |
| // what the offset of the address is within its parent symbol. |
| char symbol_buffer[512]; |
| SYMBOL_INFO* symbol_info = (SYMBOL_INFO*)symbol_buffer; |
| symbol_info->SizeOfStruct = sizeof(*symbol_info); |
| symbol_info->MaxNameLen = sizeof(symbol_buffer) - sizeof(*symbol_info); |
| DWORD64 displacement64 = 0; |
| iree_symbol_resolver_lock(resolver); |
| BOOL result = resolver->SymFromAddr(process, (DWORD64)address, |
| &displacement64, symbol_info); |
| iree_symbol_resolver_unlock(resolver); |
| if (!result) { |
| // Failed to get any information; bail. |
| return false; |
| } |
| |
| IMAGEHLP_MODULE64 module; |
| module.SizeOfStruct = sizeof(module); |
| if (resolver->SymGetModuleInfo64(process, symbol_info->ModBase, &module)) { |
| *buffer_length = iree_string_buffer_append_cstr( |
| buffer_capacity, buffer, *buffer_length, module.ModuleName); |
| } else { |
| *buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| *buffer_length, "???"); |
| } |
| |
| *buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| *buffer_length, " <"); |
| if (symbol_info->NameLen > 0) { |
| *buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, *buffer_length, "%.*s", |
| (int)symbol_info->NameLen, symbol_info->Name); |
| } else { |
| *buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| *buffer_length, "???"); |
| } |
| if (displacement64) { |
| *buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, *buffer_length, "+0x%0" PRIx64, |
| displacement64); |
| } |
| *buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| *buffer_length, ">"); |
| |
| // Note that the returned name pointer is only valid while the lock is held. |
| IMAGEHLP_LINE64 line; |
| line.SizeOfStruct = sizeof(line); |
| DWORD displacement32 = 0; |
| iree_symbol_resolver_lock(resolver); |
| if (resolver->SymGetLineFromAddr64(process, (DWORD64)address, &displacement32, |
| &line)) { |
| *buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, *buffer_length, " (%s:%" PRIu32 ")", |
| line.FileName, (uint32_t)line.LineNumber); |
| } |
| iree_symbol_resolver_unlock(resolver); |
| |
| return true; |
| } |
| |
| #endif // IREE_PLATFORM_WINDOWS |
| |
| static iree_host_size_t iree_status_payload_stack_trace_format_frame( |
| void* address, iree_host_size_t buffer_capacity, char* buffer) { |
| iree_host_size_t buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, 0, " 0x%016" PRIx64 " ", |
| (uint64_t)(uintptr_t)address); |
| #if defined(IREE_HAVE_BACKTRACE) |
| Dl_info info; |
| if (dladdr(address, &info) != 0) { |
| if (info.dli_fname) { |
| iree_string_view_t fname = iree_status_trim_file_path(info.dli_fname); |
| buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, buffer_length, "%.*s", (int)fname.size, |
| fname.data); |
| } else { |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| } |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, " <"); |
| if (info.dli_sname) { |
| iree_string_view_t sname = iree_make_cstring_view(info.dli_sname); |
| buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, buffer_length, "%.*s", (int)sname.size, |
| sname.data); |
| } else { |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| } |
| void* saddr = info.dli_saddr ? info.dli_saddr : address; |
| ptrdiff_t diff = (ptrdiff_t)address - (ptrdiff_t)saddr; |
| if (diff) { |
| buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, buffer_length, "+0x%0" PRIx32, |
| (int32_t)diff); |
| } |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, ">"); |
| } else { |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| } |
| #elif defined(IREE_PLATFORM_WINDOWS) |
| if (!iree_symbol_resolver_format_frame(iree_symbol_resolver_get(), address, |
| buffer_capacity, buffer, |
| &buffer_length)) { |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| } |
| #elif defined(IREE_USE_LIBBACKTRACE) |
| // Use libbacktrace for file:line:function info. |
| struct backtrace_state* state = iree_libbacktrace_get_state(); |
| if (state) { |
| iree_libbacktrace_format_state_t format_state = { |
| .buffer_capacity = buffer_capacity, |
| .buffer = buffer, |
| .buffer_length = buffer_length, |
| .found = false, |
| }; |
| backtrace_pcinfo(state, (uintptr_t)address, |
| iree_libbacktrace_pcinfo_callback, |
| iree_libbacktrace_error_callback, &format_state); |
| if (format_state.found) { |
| buffer_length = format_state.buffer_length; |
| } else { |
| // pcinfo failed, fallback to dladdr for basic symbol info. |
| Dl_info info; |
| if (dladdr(address, &info) != 0 && info.dli_sname) { |
| if (info.dli_fname) { |
| iree_string_view_t fname = iree_status_trim_file_path(info.dli_fname); |
| buffer_length = iree_string_buffer_append_format( |
| buffer_capacity, buffer, buffer_length, "%.*s", (int)fname.size, |
| fname.data); |
| } |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, " <"); |
| buffer_length = iree_string_buffer_append_cstr( |
| buffer_capacity, buffer, buffer_length, info.dli_sname); |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, ">"); |
| } else { |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| } |
| } |
| } else { |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| } |
| #else |
| // Symbol resolution not implemented on the platform. |
| buffer_length = iree_string_buffer_append_cstr(buffer_capacity, buffer, |
| buffer_length, "???"); |
| #endif |
| return iree_string_buffer_append_cstr(buffer_capacity, buffer, buffer_length, |
| "\n"); |
| } |
| |
| static void iree_status_payload_stack_trace_formatter( |
| const iree_status_payload_t* base_payload, iree_host_size_t buffer_capacity, |
| char* buffer, iree_host_size_t* out_buffer_length) { |
| iree_status_payload_stack_trace_t* payload = |
| (iree_status_payload_stack_trace_t*)base_payload; |
| if (payload->frame_count - payload->skip_frames == 0) return; |
| iree_host_size_t buffer_length = |
| iree_string_buffer_append_cstr(buffer_capacity, buffer, 0, "stack:\n"); |
| for (iree_host_size_t i = payload->skip_frames + 1; i < payload->frame_count; |
| ++i) { |
| buffer_length += iree_status_payload_stack_trace_format_frame( |
| (void*)payload->addresses[i], |
| buffer ? buffer_capacity - buffer_length : 0, |
| buffer ? buffer + buffer_length : NULL); |
| if (buffer_length > buffer_capacity) buffer = NULL; |
| } |
| *out_buffer_length = buffer_length; |
| } |
| |
| // Captures the current stack and attaches it to the status storage. |
| // A count of |skip_frames| will be skipped from the top of the stack. |
| // Setting |skip_frames|=0 will include the caller in the stack while |
| // |skip_frames|=1 will exclude it. |
| iree_status_t iree_status_attach_stack_trace(iree_status_t status, |
| iree_status_storage_t* storage, |
| int skip_frames) { |
| // Reserve storage for the number of stack frames so we can capture directly |
| // into the storage even if we don't need them all. At the point we are |
| // mallocing the exact size doesn't really matter. |
| iree_status_payload_stack_trace_t* payload = NULL; |
| iree_host_size_t total_size = |
| sizeof(*payload) + |
| sizeof(payload->addresses[0]) * IREE_STATUS_MAX_STACK_TRACE_FRAMES; |
| |
| iree_allocator_t allocator = iree_allocator_system(); |
| iree_status_ignore( |
| iree_allocator_malloc(allocator, total_size, (void**)&payload)); |
| if (IREE_UNLIKELY(!payload)) return status; |
| memset(payload, 0, sizeof(*payload)); |
| payload->header.type = IREE_STATUS_PAYLOAD_TYPE_STACK_TRACE; |
| payload->header.allocator = allocator; |
| payload->header.formatter = iree_status_payload_stack_trace_formatter; |
| |
| #if defined(IREE_HAVE_BACKTRACE) |
| // Capture up to the max frame count and skip some frames when formatting - |
| // this means that our actual backtrace() max frame count is smaller than the |
| // defined value. We could instead overallocate by skip_frames to waste a bit |
| // of memory but keep the processing simpler. |
| payload->skip_frames = skip_frames; |
| payload->frame_count = backtrace((void**)&payload->addresses, |
| IREE_STATUS_MAX_STACK_TRACE_FRAMES); |
| #elif defined(IREE_PLATFORM_WINDOWS) |
| // NOTE: Win32 supports skip frames by default so we don't lose any storage. |
| payload->frame_count = |
| CaptureStackBackTrace(skip_frames, IREE_STATUS_MAX_STACK_TRACE_FRAMES, |
| (void**)&payload->addresses, NULL); |
| #elif defined(IREE_USE_LIBBACKTRACE) |
| // Use libbacktrace's backtrace_simple for unwinding. |
| struct backtrace_state* state = iree_libbacktrace_get_state(); |
| if (state) { |
| iree_libbacktrace_capture_state_t capture_state = { |
| .addresses = payload->addresses, |
| .max_frames = IREE_STATUS_MAX_STACK_TRACE_FRAMES, |
| .frame_count = 0, |
| }; |
| payload->skip_frames = skip_frames; |
| backtrace_simple(state, 0, iree_libbacktrace_capture_callback, |
| iree_libbacktrace_error_callback, &capture_state); |
| payload->frame_count = capture_state.frame_count; |
| } |
| #endif |
| |
| return iree_status_append_payload(status, storage, |
| (iree_status_payload_t*)payload); |
| } |
| |
| #else |
| |
| iree_status_t iree_status_attach_stack_trace(iree_status_t status, |
| iree_status_storage_t* storage, |
| int skip_frames) { |
| return status; |
| } |
| |
| #endif // has IREE_STATUS_FEATURE_STACK_TRACE |