blob: 19d0c2553e94cd20d24b6df217da604545d59f45 [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
#ifndef IREE_VM_REF_H_
#define IREE_VM_REF_H_
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "iree/base/api.h"
#include "iree/base/internal/atomics.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
typedef struct iree_vm_instance_t iree_vm_instance_t;
// Enable additional expensive checks to track down ref counting issues.
// #define IREE_VM_REF_PARANOID
// Number of least significant bits available in an iree_vm_ref_type_t value.
// These can be used for any purpose.
#define IREE_VM_REF_TYPE_TAG_BITS 3
#define IREE_VM_REF_TYPE_TAG_BIT_MASK 0b111
// Number of bits available in an iree_vm_ref_type_t value to hold the pointer.
#define IREE_VM_REF_TYPE_PTR_BITS \
(sizeof(uintptr_t) * 8 - IREE_VM_REF_TYPE_TAG_BITS)
#define IREE_VM_REF_TYPE_PTR_BIT_MASK (~IREE_VM_REF_TYPE_TAG_BIT_MASK)
// (1 << IREE_VM_REF_TYPE_TAG_BITS) hardcoded for MSVC which cannot have
// expressions in its alignas in older versions.
#define IREE_VM_REF_TYPE_DESCRIPTOR_ALIGNMENT 8
// Alignment required for the reference counter.
// Used to pack the offset bits to fit in IREE_VM_REF_TYPE_TAG_BITS.
#define IREE_VM_REF_COUNTER_ALIGNMENT sizeof(iree_atomic_ref_count_t)
// Defines the type of the reference-counted pointer.
// This is used to verify that operations dealing with the variant ref struct
// are correct at runtime. We don't allow control over the ref types from the
// VM ops and as such we can use the type specified as a safe way to avoid
// reinterpreting memory incorrectly.
enum iree_vm_ref_type_bits_t {
IREE_VM_REF_TYPE_NULL = 0,
// NOTE: these type values are assigned dynamically right now. Treat them as
// opaque and unstable across process invocations.
// Wildcard type that indicates that a value may be a ref type but of an
// unspecified internal type. Note that we mask off the bottom bits to allow
// for tagging (all ref type values )
IREE_VM_REF_TYPE_ANY = UINTPTR_MAX ^ IREE_VM_REF_TYPE_TAG_BIT_MASK,
};
typedef void(IREE_API_PTR* iree_vm_ref_destroy_t)(void* ptr);
// Describes a type for the VM.
typedef iree_alignas(IREE_VM_REF_TYPE_DESCRIPTOR_ALIGNMENT) struct
iree_vm_ref_type_descriptor_t {
// Function called when references of this type reach 0 and should be
// destroyed.
iree_vm_ref_destroy_t destroy;
// Unretained type name that can be used for debugging.
iree_string_view_t type_name;
// Offset from ptr in units of IREE_VM_REF_COUNTER_ALIGNMENT to the start of
// an iree_atomic_ref_count_t representing the current reference count.
uintptr_t offsetof_counter : IREE_VM_REF_TYPE_TAG_BITS;
uintptr_t reserved : IREE_VM_REF_TYPE_PTR_BITS;
} iree_vm_ref_type_descriptor_t;
// Type-erased reference counted type descriptor.
typedef uintptr_t iree_vm_ref_type_t;
// Makes an opaque reference to a type descriptor.
static inline iree_vm_ref_type_t iree_vm_make_ref_type(
const iree_vm_ref_type_descriptor_t* descriptor) {
return (iree_vm_ref_type_t)descriptor |
(iree_vm_ref_type_t)descriptor->offsetof_counter;
}
// Returns the type name for the given type, if found.
IREE_API_EXPORT iree_string_view_t
iree_vm_ref_type_name(iree_vm_ref_type_t type);
// Returns the type descriptor backing the given |type|.
// The descriptor is an implementation detail.
static inline const iree_vm_ref_type_descriptor_t* iree_vm_ref_type_descriptor(
iree_vm_ref_type_t type) {
return (const iree_vm_ref_type_descriptor_t*)(type &
IREE_VM_REF_TYPE_PTR_BIT_MASK);
}
// Base for iree_vm_ref_t object targets.
//
// Usage (C):
// typedef struct my_type_t {
// iree_vm_ref_object_t ref_object;
// int my_fields;
// } my_type_t;
// void my_type_destroy(void* ptr) {
// free(ptr);
// }
// static iree_vm_ref_type_descriptor_t my_type_descriptor;
// my_type_descriptor.type_name = iree_string_view_t{"my_type", 7};
// my_type_descriptor.destroy = my_type_destroy;
// my_type_descriptor.offsetof_counter =
// offsetof(my_type_t, ref_object.counter) / IREE_VM_REF_COUNTER_ALIGNMENT;
// iree_vm_ref_register_defined_type(&my_type_descriptor);
//
// Usage (C++):
// Prefer using iree::vm::RefObject as a base type.
typedef struct iree_vm_ref_object_t {
iree_atomic_ref_count_t counter;
} iree_vm_ref_object_t;
// A pointer reference to a reference-counted object.
// The counter is stored within the target object itself ala intrusive_ptr.
//
// NOTE: we try to keep this small so that we aren't wasting stack space or
// copying around too much when we pass it to functions by value. This also
// helps make the CPU caches happier as we need no indirections to check the
// type and adjusting the counter happens without needing to query a descriptor.
// Ideally the iree_vm_ref_t is in-cache on the stack and the target ptr is
// either in cache from a previous use or will be used again after manipulating
// its ref count.
typedef struct iree_vm_ref_t {
// Pointer to the object. Type is resolved based on the |type| field.
// Will be NULL if the reference points to nothing.
void* ptr;
// Registered type of the object pointed to by ptr.
iree_vm_ref_type_t type;
} iree_vm_ref_t;
static_assert(
sizeof(iree_vm_ref_t) <= sizeof(void*) * 2,
"iree_vm_ref_t dominates stack space usage and should be kept tiny");
// Directly retains the object with base |ptr| with the given |type|.
//
// Note that this avoids any kind of type checking; for untrusted inputs use
// the iree_vm_ref_t-based methods.
IREE_API_EXPORT void iree_vm_ref_object_retain(void* ptr,
iree_vm_ref_type_t type);
// Directly release the object with base |ptr| with the given |type|,
// possibly destroying it if it is the last reference. Assume that |ptr| is
// invalid after this function returns.
//
// Note that this avoids any kind of type checking; for untrusted inputs use
// the iree_vm_ref_t-based methods.
IREE_API_EXPORT void iree_vm_ref_object_release(void* ptr,
iree_vm_ref_type_t type);
// Returns a NULL ref wrapper.
static inline iree_vm_ref_t iree_vm_ref_null(void) {
iree_vm_ref_t ref = {0};
return ref;
}
// Wraps a raw pointer in a iree_vm_ref_t reference and assigns it to |out_ref|.
// |out_ref| will be released if it already contains a reference. The target
// object will not be retained and must come in with a count >= 1.
//
// Usage (C):
// my_type_t* my_type = (my_type_t*)malloc(sizeof(my_type_t));
// my_type.ref_object.counter = IREE_ATOMIC_VAR_INIT(1);
// iree_vm_ref_t my_ref;
// iree_vm_ref_wrap_assign(my_type, IREE_VM_REF_TYPE_MY_TYPE, &my_ref);
// iree_vm_ref_release(&my_ref);
//
// Usage (C++):
// iree_vm_ref_t my_ref;
// iree_vm_ref_wrap_assign(new MyType(), IREE_VM_REF_TYPE_MY_TYPE, &my_ref);
// iree_vm_ref_release(&my_ref);
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);
// Wraps a raw pointer in a iree_vm_ref_t reference and retains it in |out_ref|.
// |out_ref| will be released if it already contains a reference.
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);
// Checks that the given reference-counted pointer |ref| is of |type|.
static inline iree_status_t iree_vm_ref_check(const iree_vm_ref_t ref,
iree_vm_ref_type_t type) {
return IREE_LIKELY(ref.type == type)
? iree_ok_status()
: iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
ref.type == IREE_VM_REF_TYPE_NULL
? "ref is null"
: "ref type mismatch");
}
// Retains the reference-counted pointer |ref|.
IREE_API_EXPORT void iree_vm_ref_retain_inplace(iree_vm_ref_t* ref);
// Retains the reference-counted pointer |ref|.
// |out_ref| will be released if it already contains a reference.
IREE_API_EXPORT void iree_vm_ref_retain(iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref);
// Retains the reference-counted pointer |ref| and checks that it is of |type|.
// |out_ref| will be released if it already contains a reference.
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);
// Retains or moves |ref| to |out_ref|.
// |out_ref| will be released if it already contains a reference.
IREE_API_EXPORT void iree_vm_ref_retain_or_move(int is_move, iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref);
// Retains or moves |ref| to |out_ref| and checks that |ref| is of |type|.
// |out_ref| will be released if it already contains a reference.
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);
// Releases the reference-counted pointer |ref|, possibly freeing it.
IREE_API_EXPORT void iree_vm_ref_release(iree_vm_ref_t* ref);
// Assigns the reference-counted pointer |ref| without incrementing the count.
// |out_ref| will be released if it already contains a reference.
IREE_API_EXPORT void iree_vm_ref_assign(iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref);
// Moves one reference to another without changing the reference count.
// |out_ref| will be released if it already contains a reference.
IREE_API_EXPORT void iree_vm_ref_move(iree_vm_ref_t* ref,
iree_vm_ref_t* out_ref);
// Returns true if the given |ref| is NULL.
IREE_API_EXPORT bool iree_vm_ref_is_null(const iree_vm_ref_t* ref);
// Returns true if the two references point at the same value (or are both
// null).
IREE_API_EXPORT bool iree_vm_ref_equal(const iree_vm_ref_t* lhs,
const iree_vm_ref_t* rhs);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
//===----------------------------------------------------------------------===//
// Type adapter utilities for interfacing with the VM
//===----------------------------------------------------------------------===//
#if defined(IREE_VM_REF_PARANOID)
#define IREE_VM_REF_ASSERT(expr) IREE_ASSERT(expr)
#else
#define IREE_VM_REF_ASSERT(expr)
#endif // IREE_VM_REF_PARANOID
// TODO(benvanik): make these macros standard/document them.
#define IREE_VM_DECLARE_TYPE_ADAPTERS(name, T) \
IREE_API_EXPORT_VARIABLE iree_vm_ref_type_t name##_registration; \
static inline iree_vm_ref_type_t name##_type(void) { \
IREE_VM_REF_ASSERT(name##_registration); \
return name##_registration; \
} \
static inline bool name##_isa(const iree_vm_ref_t ref) { \
IREE_VM_REF_ASSERT(name##_registration); \
return name##_registration == ref.type; \
} \
IREE_API_EXPORT iree_vm_ref_t name##_retain_ref(T* value); \
IREE_API_EXPORT iree_vm_ref_t name##_move_ref(T* value); \
static inline T* name##_deref(const iree_vm_ref_t ref) { \
IREE_VM_REF_ASSERT(name##_registration); \
return IREE_LIKELY(name##_isa(ref)) ? (T*)ref.ptr : NULL; \
} \
IREE_API_EXPORT iree_status_t name##_check_deref(const iree_vm_ref_t ref, \
T** out_ptr); \
IREE_API_EXPORT iree_status_t name##_check_deref_or_null( \
const iree_vm_ref_t ref, T** out_ptr); \
IREE_VM_DECLARE_CC_TYPE_LOOKUP(name, T)
// TODO(benvanik): make these macros standard/document them.
#define IREE_VM_DEFINE_TYPE_ADAPTERS(name, T) \
iree_vm_ref_type_t name##_registration = 0; \
IREE_API_EXPORT iree_vm_ref_t name##_retain_ref(T* value) { \
IREE_VM_REF_ASSERT(name##_registration); \
iree_vm_ref_t ref = {0}; \
iree_vm_ref_wrap_retain(value, name##_type(), &ref); \
return ref; \
} \
IREE_API_EXPORT iree_vm_ref_t name##_move_ref(T* value) { \
IREE_VM_REF_ASSERT(name##_registration); \
iree_vm_ref_t ref = {0}; \
iree_vm_ref_wrap_assign(value, name##_type(), &ref); \
return ref; \
} \
IREE_API_EXPORT iree_status_t name##_check_deref(const iree_vm_ref_t ref, \
T** out_ptr) { \
IREE_VM_REF_ASSERT(name##_registration); \
IREE_RETURN_IF_ERROR(iree_vm_ref_check(ref, name##_type())); \
*out_ptr = (T*)ref.ptr; \
return iree_ok_status(); \
} \
IREE_API_EXPORT iree_status_t name##_check_deref_or_null( \
const iree_vm_ref_t ref, T** out_ptr) { \
IREE_VM_REF_ASSERT(name##_registration); \
if (ref.type != IREE_VM_REF_TYPE_NULL) { \
IREE_RETURN_IF_ERROR(iree_vm_ref_check(ref, name##_type())); \
*out_ptr = (T*)ref.ptr; \
} else { \
*out_ptr = NULL; \
} \
return iree_ok_status(); \
}
// Optional C++ iree::vm::ref<T> wrapper.
#ifdef __cplusplus
#include "iree/vm/ref_cc.h"
#else
#define IREE_VM_DECLARE_CC_TYPE_LOOKUP(name, T)
#define IREE_VM_DECLARE_CC_TYPE_ADAPTERS(name, T)
#endif // __cplusplus
#endif // IREE_VM_REF_H_