blob: 2ef1608b8881419ef60dbf90b0b7aabb78b593b7 [file] [log] [blame]
// Copyright 2019 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/vm/ref.h"
#include <string.h>
#include "iree/base/internal/atomics.h"
#ifdef IREE_VM_REF_LOG
#ifdef IREE_VM_REF_LOG_INCLUDE_BACKTRACES
#define IREE_DEBUG_PRINT_BACKTRACE_HERE(msg) \
{ \
iree_status_t __status = iree_make_status(IREE_STATUS_INTERNAL, msg); \
iree_status_fprint(stderr, __status); \
iree_status_ignore(__status); \
}
#else
#define IREE_DEBUG_PRINT_BACKTRACE_HERE(msg)
#endif // IREE_VM_REF_LOG_INCLUDE_BACKTRACES
static inline iree_atomic_ref_count_t* iree_vm_get_raw_counter_ptr(
void* ptr, iree_vm_ref_type_t type);
static inline iree_atomic_ref_count_t* iree_vm_get_ref_counter_ptr(
iree_vm_ref_t* ref);
static void iree_vm_ref_trace(const char* msg, iree_vm_ref_t* ref) {
if (!ref->ptr) return;
iree_atomic_ref_count_t* counter = iree_vm_get_ref_counter_ptr(ref);
iree_string_view_t name = iree_vm_ref_type_name(ref->type);
fprintf(stderr, "%s %.*s %p %d\n", msg, (int)name.size, name.data, ref->ptr,
iree_atomic_ref_count_load(counter));
IREE_DEBUG_PRINT_BACKTRACE_HERE(msg);
}
static void iree_vm_ref_ptr_trace(const char* msg, void* ptr,
iree_vm_ref_type_t type) {
if (!ptr) return;
iree_atomic_ref_count_t* counter = iree_vm_get_raw_counter_ptr(ptr, type);
iree_string_view_t name = iree_vm_ref_type_name(type);
fprintf(stderr, "%s %.*s %p %d\n", msg, (int)name.size, name.data, ptr,
iree_atomic_ref_count_load(counter));
IREE_DEBUG_PRINT_BACKTRACE_HERE(msg);
}
#else
#define iree_vm_ref_trace(...)
#define iree_vm_ref_ptr_trace(...)
#endif // IREE_VM_REF_LOG
IREE_API_EXPORT iree_string_view_t
iree_vm_ref_type_name(iree_vm_ref_type_t type) {
IREE_VM_REF_ASSERT(type);
return iree_vm_ref_type_descriptor(type)->type_name;
}
static inline iree_atomic_ref_count_t* iree_vm_get_raw_counter_ptr(
void* ptr, iree_vm_ref_type_t type) {
IREE_VM_REF_ASSERT(ptr);
IREE_VM_REF_ASSERT(type);
return (iree_atomic_ref_count_t*)ptr + (type & IREE_VM_REF_TYPE_TAG_BIT_MASK);
}
static inline iree_atomic_ref_count_t* iree_vm_get_ref_counter_ptr(
iree_vm_ref_t* ref) {
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(ref->ptr);
return (iree_atomic_ref_count_t*)ref->ptr +
(ref->type & IREE_VM_REF_TYPE_TAG_BIT_MASK);
}
IREE_API_EXPORT void iree_vm_ref_object_retain(void* ptr,
iree_vm_ref_type_t type) {
if (!ptr) return;
IREE_VM_REF_ASSERT(type);
iree_atomic_ref_count_t* counter = iree_vm_get_raw_counter_ptr(ptr, type);
iree_atomic_ref_count_inc(counter);
iree_vm_ref_ptr_trace("RETAIN", ptr, type);
}
IREE_API_EXPORT void iree_vm_ref_object_release(void* ptr,
iree_vm_ref_type_t type) {
if (!ptr) return;
IREE_VM_REF_ASSERT(type);
iree_vm_ref_ptr_trace("RELEASE", ptr, type);
iree_atomic_ref_count_t* counter = iree_vm_get_raw_counter_ptr(ptr, type);
if (iree_atomic_ref_count_dec(counter) == 1) {
const iree_vm_ref_type_descriptor_t* descriptor =
iree_vm_ref_type_descriptor(type);
if (descriptor->destroy) {
// NOTE: this makes us not re-entrant, but I think that's OK.
iree_vm_ref_ptr_trace("DESTROY", ptr, type);
descriptor->destroy(ptr);
}
}
}
IREE_API_EXPORT iree_status_t iree_vm_ref_wrap_assign(void* ptr,
iree_vm_ref_type_t type,
iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ptr);
IREE_VM_REF_ASSERT(type);
IREE_VM_REF_ASSERT(out_ref);
IREE_VM_REF_ASSERT(iree_vm_ref_type_descriptor(type));
if (out_ref->ptr != NULL && out_ref->ptr != ptr) {
// Release existing value.
iree_vm_ref_release(out_ref);
}
// NOTE: we do not manipulate the counter here as we assume it starts at 1
// or it's already coming in with some references.
out_ref->ptr = ptr;
out_ref->type = type;
iree_vm_ref_trace("WRAP ASSIGN", out_ref);
return iree_ok_status();
}
IREE_API_EXPORT iree_status_t iree_vm_ref_wrap_retain(void* ptr,
iree_vm_ref_type_t type,
iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ptr);
IREE_VM_REF_ASSERT(type);
IREE_VM_REF_ASSERT(out_ref);
IREE_VM_REF_ASSERT(iree_vm_ref_type_descriptor(type));
if (out_ref->ptr == ptr) {
// No-op - effectively a retain+release of the same value.
return iree_ok_status();
} else if (out_ref->ptr != NULL) {
// Release existing value.
iree_vm_ref_release(out_ref);
}
out_ref->ptr = ptr;
out_ref->type = type;
if (out_ref->ptr) {
iree_atomic_ref_count_t* counter = iree_vm_get_ref_counter_ptr(out_ref);
iree_atomic_ref_count_inc(counter);
iree_vm_ref_trace("WRAP RETAIN", out_ref);
}
return iree_ok_status();
}
IREE_API_EXPORT void iree_vm_ref_retain_inplace(iree_vm_ref_t* ref) {
IREE_VM_REF_ASSERT(ref);
if (ref->ptr) {
iree_atomic_ref_count_t* counter = iree_vm_get_ref_counter_ptr(ref);
iree_atomic_ref_count_inc(counter);
iree_vm_ref_trace("RETAIN", ref);
}
}
IREE_API_EXPORT void iree_vm_ref_retain(iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref) {
// NOTE: ref and out_ref may alias or be nested so we retain before we
// potentially release.
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(out_ref);
iree_vm_ref_t src_ref = *ref;
if (src_ref.ptr) {
iree_atomic_ref_count_t* counter = iree_vm_get_ref_counter_ptr(&src_ref);
iree_atomic_ref_count_inc(counter);
iree_vm_ref_trace("RETAIN", ref);
}
iree_vm_ref_t dst_ref = *out_ref;
if (dst_ref.ptr) {
// Output ref contains a value that should be released first.
// Note that we check above for it being the same as the new value so we
// don't do extra work unless we have to.
iree_vm_ref_release(&dst_ref);
}
*out_ref = src_ref;
}
IREE_API_EXPORT iree_status_t iree_vm_ref_retain_checked(
iree_vm_ref_t* ref, iree_vm_ref_type_t type, iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(type);
IREE_VM_REF_ASSERT(out_ref);
iree_vm_ref_t src_ref = *ref;
if (src_ref.type != IREE_VM_REF_TYPE_NULL && src_ref.type != type &&
type != IREE_VM_REF_TYPE_ANY) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"source ref type mismatch");
}
iree_vm_ref_retain(ref, out_ref);
return iree_ok_status();
}
IREE_API_EXPORT void iree_vm_ref_retain_or_move(int is_move, iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(out_ref);
if (is_move) {
iree_vm_ref_move(ref, out_ref);
} else {
iree_vm_ref_retain(ref, out_ref);
}
}
IREE_API_EXPORT iree_status_t iree_vm_ref_retain_or_move_checked(
int is_move, iree_vm_ref_t* ref, iree_vm_ref_type_t type,
iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(type);
IREE_VM_REF_ASSERT(out_ref);
iree_vm_ref_t src_ref = *ref;
if (src_ref.type != IREE_VM_REF_TYPE_NULL && src_ref.type != type &&
type != IREE_VM_REF_TYPE_ANY) {
// Make no changes on failure.
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"source ref type mismatch");
}
iree_vm_ref_retain_or_move(is_move, ref, out_ref);
return iree_ok_status();
}
IREE_API_EXPORT void iree_vm_ref_release(iree_vm_ref_t* ref) {
IREE_VM_REF_ASSERT(ref);
iree_vm_ref_t temp_ref = *ref;
if (temp_ref.type == IREE_VM_REF_TYPE_NULL || temp_ref.ptr == NULL) return;
#if defined(IREE_VM_REF_DEBUG_MOVE)
// In debug move mode, poisoned refs from moved-from registers are cleaned up
// during frame cleanup. Just clear them without trying to release.
if (temp_ref.type == IREE_VM_REF_TYPE_POISONED) {
memset(ref, 0, sizeof(*ref));
return;
}
#endif // IREE_VM_REF_DEBUG_MOVE
iree_vm_ref_trace("RELEASE", ref);
iree_atomic_ref_count_t* counter = iree_vm_get_ref_counter_ptr(&temp_ref);
if (iree_atomic_ref_count_dec(counter) == 1) {
const iree_vm_ref_type_descriptor_t* descriptor =
iree_vm_ref_type_descriptor(temp_ref.type);
if (descriptor->destroy) {
// NOTE: this makes us not re-entrant, but I think that's OK.
iree_vm_ref_trace("DESTROY", ref);
descriptor->destroy(temp_ref.ptr);
}
}
// Reset ref to point at nothing.
memset(ref, 0, sizeof(*ref));
}
IREE_API_EXPORT void iree_vm_ref_assign(iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(out_ref);
// NOTE: ref and out_ref may alias.
iree_vm_ref_t src_ref = *ref;
if (ref == out_ref) {
// Source == target; ignore entirely.
return;
}
iree_vm_ref_t dst_ref = *out_ref;
if (dst_ref.ptr != NULL) {
// Release existing value.
iree_vm_ref_release(&dst_ref);
}
// Assign ref to out_ref (without incrementing counter).
*out_ref = src_ref;
}
IREE_API_EXPORT void iree_vm_ref_move(iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref) {
IREE_VM_REF_ASSERT(ref);
IREE_VM_REF_ASSERT(out_ref);
// NOTE: ref and out_ref may alias.
if (ref == out_ref) {
// Source == target; ignore entirely.
return;
}
// Reset input ref so it points at nothing.
iree_vm_ref_t src_ref = *ref;
#if defined(IREE_VM_REF_DEBUG_MOVE)
// Poison the source ref with a sentinel value to detect use-after-move.
// If the compiler incorrectly marked this as a last-use when it's actually
// used again, the next access will see this poison value and assert.
ref->ptr = (void*)(uintptr_t)IREE_VM_REF_TYPE_POISONED;
ref->type = IREE_VM_REF_TYPE_POISONED;
#else
memset(ref, 0, sizeof(*ref));
#endif // IREE_VM_REF_DEBUG_MOVE
iree_vm_ref_t dst_ref = *out_ref;
if (dst_ref.ptr != NULL) {
// Release existing value.
iree_vm_ref_release(&dst_ref);
}
// Assign ref to out_ref (without incrementing counter).
*out_ref = src_ref;
}
IREE_API_EXPORT bool iree_vm_ref_is_null(const iree_vm_ref_t* ref) {
IREE_VM_REF_ASSERT(ref);
iree_vm_ref_type_t null_type = IREE_VM_REF_TYPE_NULL;
return memcmp(&ref->type, &null_type, sizeof(null_type)) == 0;
}
IREE_API_EXPORT bool iree_vm_ref_equal(const iree_vm_ref_t* lhs,
const iree_vm_ref_t* rhs) {
return lhs == rhs || memcmp(lhs, rhs, sizeof(*lhs)) == 0;
}