blob: 071a880b705781add42a305a30d41971a582ca74 [file] [log] [blame]
// Copyright 2020 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
#include "iree/hal/local/loaders/system_library_loader.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "iree/base/internal/dynamic_library.h"
#include "iree/base/tracing.h"
#include "iree/hal/api.h"
#include "iree/hal/local/executable_library.h"
#include "iree/hal/local/local_executable.h"
#include "iree/hal/local/local_executable_layout.h"
//===----------------------------------------------------------------------===//
// iree_hal_system_executable_footer_t
//===----------------------------------------------------------------------===//
// An optional footer that may exist on the system library that is used to add
// additional debug information for use directly by IREE, such as PDB or dSYM
// files. This is only expected to be present when there is a debug database
// but we may want to extend it in the future.
typedef struct iree_hal_system_executable_footer_t {
uint8_t magic[8]; // IREE_HAL_SYSTEM_EXECUTABLE_FOOTER_MAGIC
uint32_t version; // IREE_HAL_SYSTEM_EXECUTABLE_FOOTER_VERSION
uint32_t flags; // reserved
// Offset of the library within the parent data stream.
// Almost always zero but here in case we want to allow for chaining.
uint64_t library_offset;
// Size of the system library in bytes.
uint64_t library_size;
// Offset of the start of the embedded debug database within the parent data
// stream. There may be padding between the library and this offset.
uint64_t debug_offset;
// Size of the debug database in bytes.
uint64_t debug_size;
} iree_hal_system_executable_footer_t;
// EXPERIMENTAL: this is not a stable interface yet. The binary format may
// change at any time.
#define IREE_HAL_SYSTEM_EXECUTABLE_FOOTER_MAGIC "IREEDBG\0"
#define IREE_HAL_SYSTEM_EXECUTABLE_FOOTER_VERSION 0
// Tries to find an iree_hal_system_executable_footer_t at the end of the
// given executable data stream.
static const iree_hal_system_executable_footer_t*
iree_hal_system_executable_try_query_footer(
iree_const_byte_span_t executable_data) {
if (executable_data.data_length <
sizeof(iree_hal_system_executable_footer_t)) {
return NULL;
}
const uint8_t* footer_ptr = executable_data.data +
executable_data.data_length -
sizeof(iree_hal_system_executable_footer_t);
const iree_hal_system_executable_footer_t* footer =
(const iree_hal_system_executable_footer_t*)(footer_ptr);
static_assert(sizeof(IREE_HAL_SYSTEM_EXECUTABLE_FOOTER_MAGIC) - /*NUL*/ 1 ==
sizeof(footer->magic),
"magic number value must match struct size");
if (memcmp(footer->magic, IREE_HAL_SYSTEM_EXECUTABLE_FOOTER_MAGIC,
sizeof(footer->magic)) != 0) {
return NULL;
}
return footer;
}
//===----------------------------------------------------------------------===//
// iree_hal_system_executable_t
//===----------------------------------------------------------------------===//
typedef struct iree_hal_system_executable_t {
iree_hal_local_executable_t base;
// Loaded platform dynamic library.
iree_dynamic_library_t* handle;
// Name used for the file field in tracy and debuggers.
iree_string_view_t identifier;
// Queried metadata from the library.
union {
const iree_hal_executable_library_header_t** header;
const iree_hal_executable_library_v0_t* v0;
} library;
iree_hal_local_executable_layout_t* layouts[];
} iree_hal_system_executable_t;
static const iree_hal_local_executable_vtable_t
iree_hal_system_executable_vtable;
// Loads the executable and optional debug database from the given
// |executable_data| in memory. The memory must remain live for the lifetime
// of the executable.
static iree_status_t iree_hal_system_executable_load(
iree_hal_system_executable_t* executable,
iree_const_byte_span_t executable_data, iree_allocator_t host_allocator) {
// Check to see if the library has a footer indicating embedded debug data.
iree_const_byte_span_t library_data = iree_make_const_byte_span(NULL, 0);
iree_const_byte_span_t debug_data = iree_make_const_byte_span(NULL, 0);
const iree_hal_system_executable_footer_t* footer =
iree_hal_system_executable_try_query_footer(executable_data);
if (footer) {
// Debug file present; split the data contents.
iree_host_size_t data_length =
executable_data.data_length - sizeof(*footer);
if (footer->library_size > data_length ||
footer->debug_offset + footer->debug_size > data_length) {
return iree_make_status(
IREE_STATUS_OUT_OF_RANGE,
"system library footer references out of range bytes");
}
library_data =
iree_make_const_byte_span(executable_data.data, footer->library_size);
debug_data = iree_make_const_byte_span(
executable_data.data + footer->debug_offset, footer->debug_size);
} else {
// Entire data contents are the library.
library_data = executable_data;
}
IREE_RETURN_IF_ERROR(iree_dynamic_library_load_from_memory(
iree_make_cstring_view("aot"), library_data,
IREE_DYNAMIC_LIBRARY_FLAG_NONE, host_allocator, &executable->handle));
if (debug_data.data_length > 0) {
IREE_RETURN_IF_ERROR(iree_dynamic_library_attach_symbols_from_memory(
executable->handle, debug_data));
}
return iree_ok_status();
}
static iree_status_t iree_hal_system_executable_query_library(
iree_hal_system_executable_t* executable) {
// Get the exported symbol used to get the library metadata.
iree_hal_executable_library_query_fn_t query_fn = NULL;
IREE_RETURN_IF_ERROR(iree_dynamic_library_lookup_symbol(
executable->handle, IREE_HAL_EXECUTABLE_LIBRARY_EXPORT_NAME,
(void**)&query_fn));
// Query for a compatible version of the library.
executable->library.header =
query_fn(IREE_HAL_EXECUTABLE_LIBRARY_LATEST_VERSION,
&executable->base.environment);
if (!executable->library.header) {
return iree_make_status(
IREE_STATUS_FAILED_PRECONDITION,
"executable does not support this version of the runtime (%d)",
IREE_HAL_EXECUTABLE_LIBRARY_LATEST_VERSION);
}
const iree_hal_executable_library_header_t* header =
*executable->library.header;
// Ensure that if the library is built for a particular sanitizer that we also
// were compiled with that sanitizer enabled.
switch (header->sanitizer) {
case IREE_HAL_EXECUTABLE_LIBRARY_SANITIZER_NONE:
// Always safe even if the host has a sanitizer enabled; it just means
// that we won't be able to catch anything from within the executable,
// however checks outside will (often) still trigger when guard pages are
// dirtied/etc.
break;
#if defined(IREE_SANITIZER_ADDRESS)
case IREE_HAL_EXECUTABLE_LIBRARY_SANITIZER_ADDRESS:
// ASAN is compiled into the host and we can load this library.
break;
#else
case IREE_HAL_EXECUTABLE_LIBRARY_SANITIZER_ADDRESS:
return iree_make_status(
IREE_STATUS_UNAVAILABLE,
"executable library is compiled with ASAN support but the host "
"runtime is not compiled with it enabled; add -fsanitize=address to "
"the runtime compilation options");
#endif // IREE_SANITIZER_ADDRESS
default:
return iree_make_status(
IREE_STATUS_UNAVAILABLE,
"executable library requires a sanitizer the host runtime is not "
"compiled to enable/understand: %u",
(uint32_t)header->sanitizer);
}
executable->identifier = iree_make_cstring_view(header->name);
executable->base.dispatch_attrs = executable->library.v0->exports.attrs;
return iree_ok_status();
}
static int iree_hal_system_executable_import_thunk_v0(
iree_hal_executable_import_v0_t fn_ptr, void* import_params) {
return fn_ptr(import_params);
}
// Resolves all of the imports declared by the executable using the given
// |import_provider|.
static iree_status_t iree_hal_system_executable_resolve_imports(
iree_hal_system_executable_t* executable,
const iree_hal_executable_import_provider_t import_provider) {
const iree_hal_executable_import_table_v0_t* import_table =
&executable->library.v0->imports;
if (!import_table->count) return iree_ok_status();
IREE_TRACE_ZONE_BEGIN(z0);
// Pass all imports right through.
executable->base.environment.import_thunk =
iree_hal_system_executable_import_thunk_v0;
// Allocate storage for the imports.
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0,
iree_allocator_malloc(
executable->base.host_allocator,
import_table->count * sizeof(*executable->base.environment.imports),
(void**)&executable->base.environment.imports));
// Try to resolve each import.
// NOTE: imports are sorted alphabetically and if we cared we could use this
// information to more efficiently resolve the symbols from providers (O(n)
// walk vs potential O(nlogn)/O(n^2)).
for (uint32_t i = 0; i < import_table->count; ++i) {
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0,
iree_hal_executable_import_provider_resolve(
import_provider, iree_make_cstring_view(import_table->symbols[i]),
(void**)&executable->base.environment.imports[i]));
}
IREE_TRACE_ZONE_END(z0);
return iree_ok_status();
}
static iree_status_t iree_hal_system_executable_create(
iree_const_byte_span_t executable_data,
iree_host_size_t executable_layout_count,
iree_hal_executable_layout_t* const* executable_layouts,
const iree_hal_executable_import_provider_t import_provider,
iree_allocator_t host_allocator, iree_hal_executable_t** out_executable) {
IREE_ASSERT_ARGUMENT(executable_data.data && executable_data.data_length);
IREE_ASSERT_ARGUMENT(!executable_layout_count || executable_layouts);
IREE_ASSERT_ARGUMENT(out_executable);
*out_executable = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
iree_hal_system_executable_t* executable = NULL;
iree_host_size_t total_size =
sizeof(*executable) +
executable_layout_count * sizeof(*executable->layouts);
iree_status_t status =
iree_allocator_malloc(host_allocator, total_size, (void**)&executable);
if (iree_status_is_ok(status)) {
iree_hal_local_executable_initialize(
&iree_hal_system_executable_vtable, executable_layout_count,
executable_layouts, &executable->layouts[0], host_allocator,
&executable->base);
}
if (iree_status_is_ok(status)) {
// Attempt to extract the embedded library and load it.
status = iree_hal_system_executable_load(executable, executable_data,
host_allocator);
}
if (iree_status_is_ok(status)) {
// Query metadata and get the entry point function pointers.
status = iree_hal_system_executable_query_library(executable);
}
if (iree_status_is_ok(status)) {
// Resolve imports, if any.
status =
iree_hal_system_executable_resolve_imports(executable, import_provider);
}
if (iree_status_is_ok(status)) {
// Check to make sure that the entry point count matches the layouts
// provided.
if (executable->library.v0->exports.count != executable_layout_count) {
status = iree_make_status(
IREE_STATUS_FAILED_PRECONDITION,
"executable provides %u entry points but caller "
"provided %zu; must match",
executable->library.v0->exports.count, executable_layout_count);
}
}
if (iree_status_is_ok(status)) {
*out_executable = (iree_hal_executable_t*)executable;
} else {
iree_hal_executable_release((iree_hal_executable_t*)executable);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
static void iree_hal_system_executable_destroy(
iree_hal_executable_t* base_executable) {
iree_hal_system_executable_t* executable =
(iree_hal_system_executable_t*)base_executable;
iree_allocator_t host_allocator = executable->base.host_allocator;
IREE_TRACE_ZONE_BEGIN(z0);
iree_dynamic_library_release(executable->handle);
if (executable->base.environment.imports != NULL) {
iree_allocator_free(host_allocator,
(void*)executable->base.environment.imports);
}
iree_hal_local_executable_deinitialize(
(iree_hal_local_executable_t*)base_executable);
iree_allocator_free(host_allocator, executable);
IREE_TRACE_ZONE_END(z0);
}
static iree_status_t iree_hal_system_executable_issue_call(
iree_hal_local_executable_t* base_executable, iree_host_size_t ordinal,
const iree_hal_executable_dispatch_state_v0_t* dispatch_state,
const iree_hal_vec3_t* workgroup_id, iree_byte_span_t local_memory) {
iree_hal_system_executable_t* executable =
(iree_hal_system_executable_t*)base_executable;
const iree_hal_executable_library_v0_t* library = executable->library.v0;
if (IREE_UNLIKELY(ordinal >= library->exports.count)) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"entry point ordinal out of bounds");
}
#if IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
iree_string_view_t entry_point_name = iree_string_view_empty();
if (library->exports.names != NULL) {
entry_point_name = iree_make_cstring_view(library->exports.names[ordinal]);
}
if (iree_string_view_is_empty(entry_point_name)) {
entry_point_name = iree_make_cstring_view("unknown_dylib_call");
}
IREE_TRACE_ZONE_BEGIN_EXTERNAL(
z0, executable->identifier.data, executable->identifier.size, ordinal,
entry_point_name.data, entry_point_name.size, NULL, 0);
if (library->exports.tags != NULL) {
const char* tag = library->exports.tags[ordinal];
if (tag) {
IREE_TRACE_ZONE_APPEND_TEXT(z0, tag);
}
}
#endif // IREE_TRACING_FEATURES & IREE_TRACING_FEATURE_INSTRUMENTATION
int ret = library->exports.ptrs[ordinal](dispatch_state, workgroup_id,
local_memory.data);
IREE_TRACE_ZONE_END(z0);
return ret == 0 ? iree_ok_status()
: iree_make_status(
IREE_STATUS_INTERNAL,
"executable entry point returned catastrophic error %d",
ret);
}
static const iree_hal_local_executable_vtable_t
iree_hal_system_executable_vtable = {
.base =
{
.destroy = iree_hal_system_executable_destroy,
},
.issue_call = iree_hal_system_executable_issue_call,
};
//===----------------------------------------------------------------------===//
// iree_hal_system_library_loader_t
//===----------------------------------------------------------------------===//
typedef struct iree_hal_system_library_loader_t {
iree_hal_executable_loader_t base;
iree_allocator_t host_allocator;
} iree_hal_system_library_loader_t;
static const iree_hal_executable_loader_vtable_t
iree_hal_system_library_loader_vtable;
iree_status_t iree_hal_system_library_loader_create(
iree_hal_executable_import_provider_t import_provider,
iree_allocator_t host_allocator,
iree_hal_executable_loader_t** out_executable_loader) {
IREE_ASSERT_ARGUMENT(out_executable_loader);
*out_executable_loader = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
iree_hal_system_library_loader_t* executable_loader = NULL;
iree_status_t status = iree_allocator_malloc(
host_allocator, sizeof(*executable_loader), (void**)&executable_loader);
if (iree_status_is_ok(status)) {
iree_hal_executable_loader_initialize(
&iree_hal_system_library_loader_vtable, import_provider,
&executable_loader->base);
executable_loader->host_allocator = host_allocator;
*out_executable_loader = (iree_hal_executable_loader_t*)executable_loader;
}
IREE_TRACE_ZONE_END(z0);
return status;
}
static void iree_hal_system_library_loader_destroy(
iree_hal_executable_loader_t* base_executable_loader) {
iree_hal_system_library_loader_t* executable_loader =
(iree_hal_system_library_loader_t*)base_executable_loader;
iree_allocator_t host_allocator = executable_loader->host_allocator;
IREE_TRACE_ZONE_BEGIN(z0);
iree_allocator_free(host_allocator, executable_loader);
IREE_TRACE_ZONE_END(z0);
}
#if defined(IREE_PLATFORM_APPLE)
#define IREE_PLATFORM_DYLIB_TYPE "dylib"
#elif defined(IREE_PLATFORM_WINDOWS)
#define IREE_PLATFORM_DYLIB_TYPE "dll"
#elif defined(IREE_PLATFORM_EMSCRIPTEN)
#define IREE_PLATFORM_DYLIB_TYPE "wasm"
#else
#define IREE_PLATFORM_DYLIB_TYPE "elf"
#endif // IREE_PLATFORM_*
static bool iree_hal_system_library_loader_query_support(
iree_hal_executable_loader_t* base_executable_loader,
iree_hal_executable_caching_mode_t caching_mode,
iree_string_view_t executable_format) {
return iree_string_view_equal(
executable_format,
iree_make_cstring_view("system-" IREE_PLATFORM_DYLIB_TYPE "-" IREE_ARCH));
}
static iree_status_t iree_hal_system_library_loader_try_load(
iree_hal_executable_loader_t* base_executable_loader,
const iree_hal_executable_params_t* executable_params,
iree_hal_executable_t** out_executable) {
iree_hal_system_library_loader_t* executable_loader =
(iree_hal_system_library_loader_t*)base_executable_loader;
IREE_TRACE_ZONE_BEGIN(z0);
// Perform the load (and requisite disgusting hackery).
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hal_system_executable_create(
executable_params->executable_data,
executable_params->executable_layout_count,
executable_params->executable_layouts,
base_executable_loader->import_provider,
executable_loader->host_allocator, out_executable));
IREE_TRACE_ZONE_END(z0);
return iree_ok_status();
}
static const iree_hal_executable_loader_vtable_t
iree_hal_system_library_loader_vtable = {
.destroy = iree_hal_system_library_loader_destroy,
.query_support = iree_hal_system_library_loader_query_support,
.try_load = iree_hal_system_library_loader_try_load,
};