10#include "../microfmt.hpp"
11#include "../reloco.hpp"
12#include "../scratch_allocator.hpp"
34 bool null_terminated{};
51RELOCO_BEGIN_UNSAFE_CODE
54 return address +
static_cast<uintptr_t
>(offset);
56 const auto magnitude =
static_cast<uintptr_t
>(-(offset + 1)) + uintptr_t{1};
57 return address - magnitude;
63template <
typename Tag>
65 Tag, std::void_t<decltype(address_space_traits<Tag>::write_bytes(
66 std::declval<value_ref<const typename address_space_traits<Tag>::context_type>>(),
67 std::declval<uintptr_t>(), std::declval<const void *>(), std::declval<size_t>()))>>
68 : std::is_same<decltype(address_space_traits<Tag>::write_bytes(
69 std::declval<value_ref<const typename address_space_traits<Tag>::context_type>>(),
70 std::declval<uintptr_t>(), std::declval<const void *>(), std::declval<size_t>())),
75template <
typename Tag>
77 Tag, std::void_t<decltype(address_space_traits<Tag>::write_bytes(
78 std::declval<uintptr_t>(), std::declval<const void *>(), std::declval<size_t>()))>>
79 : std::is_same<decltype(address_space_traits<Tag>::write_bytes(
80 std::declval<uintptr_t>(), std::declval<const void *>(), std::declval<size_t>())),
122 RELOCO_BEGIN_UNSAFE_CODE
123 static bool read_bytes(uintptr_t addr,
void *dest,
size_t size)
noexcept {
128 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
129 std::memcpy(dest,
reinterpret_cast<const void *
>(addr), size);
130 RELOCO_END_UNSAFE_BUFFER_USAGE;
144 RELOCO_BEGIN_UNSAFE_CODE
145 static bool write_bytes(uintptr_t addr,
const void *src,
size_t size)
noexcept {
150 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
151 std::memcpy(
reinterpret_cast<void *
>(addr), src, size);
152 RELOCO_END_UNSAFE_BUFFER_USAGE;
168 RELOCO_BEGIN_UNSAFE_CODE
169 static bool read_string(uintptr_t addr,
char *dest,
size_t max_len,
size_t &out_len,
bool &null_term)
noexcept {
172 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
174 const auto *src =
reinterpret_cast<const char *
>(addr);
176 while (i < max_len) {
178 if (dest[i] ==
'\0') {
185 RELOCO_END_UNSAFE_BUFFER_USAGE;
213 bool (*read_bytes)(
const void *ctx, uintptr_t addr,
void *dest,
size_t size)
noexcept =
nullptr;
217 bool (*write_bytes)(
const void *ctx, uintptr_t addr,
const void *src,
size_t size)
noexcept =
nullptr;
221 bool (*read_string)(
const void *ctx, uintptr_t addr,
char *dest,
size_t max_len,
size_t &out_len,
222 bool &null_term)
noexcept =
nullptr;
236 std::enable_if_t<std::is_void_v<typename Traits::context_type>,
int> = 0>
247 template <
typename Tag,
typename Context,
typename Traits = address_space_traits<Tag>,
248 std::enable_if_t<!std::is_
void_v<
typename Traits::context_type> &&
249 std::is_convertible_v<const Context *, const
typename Traits::context_type *>,
251 constexpr address_space_ref(Tag,
const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
252 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>) {}
254 template <
typename Tag,
typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>,
int> = 0>
263 template <
typename Tag,
typename Traits = address_space_traits<Tag>,
264 std::enable_if_t<std::is_
void_v<
typename Traits::context_type>,
int> = 0>
277 template <
typename Tag,
typename Context,
typename Traits = address_space_traits<Tag>,
278 std::enable_if_t<!std::is_
void_v<
typename Traits::context_type>,
int> = 0>
283 template <
typename Tag,
typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>,
int> = 0>
294 [[nodiscard]] expected<void, address_space_error>
read_bytes(uintptr_t addr,
void *dest,
size_t size)
const noexcept {
295 if (vtbl_ ==
nullptr)
296 return unexpected(address_space_error::invalid_handle);
298 return unexpected(address_space_error::invalid_address);
299 if (dest ==
nullptr && size != 0)
300 return unexpected(address_space_error::invalid_buffer);
301 if (!vtbl_->read_bytes(ctx_.get(), addr, dest, size))
302 return unexpected(address_space_error::read_failed);
313 template <
typename T>
314 [[nodiscard]] expected<void, address_space_error>
read(uintptr_t addr, T &out_obj)
const noexcept {
315 static_assert(std::is_trivially_copyable_v<T>,
"Target type T must be trivially copyable");
316 return read_bytes(addr, &out_obj,
sizeof(T));
326 template <
typename T> [[nodiscard]] expected<T, address_space_error>
read(uintptr_t addr)
const noexcept {
327 static_assert(std::is_trivially_copyable_v<T>,
"Target type T must be trivially copyable");
328 static_assert(std::is_nothrow_default_constructible_v<T>,
"Target type T must be nothrow default constructible");
329 static_assert(std::is_nothrow_move_constructible_v<T>,
"Target type T must be nothrow move constructible");
331 auto result = read_bytes(addr, &
object,
sizeof(T));
333 return unexpected(result.error());
345 [[nodiscard]] expected<void, address_space_error>
write_bytes(uintptr_t addr,
const void *src,
346 size_t size)
const noexcept {
347 if (vtbl_ ==
nullptr)
348 return unexpected(address_space_error::invalid_handle);
350 return unexpected(address_space_error::invalid_address);
351 if (src ==
nullptr && size != 0)
352 return unexpected(address_space_error::invalid_buffer);
353 if (vtbl_->write_bytes ==
nullptr)
354 return unexpected(address_space_error::write_unsupported);
355 if (!vtbl_->write_bytes(ctx_.get(), addr, src, size))
356 return unexpected(address_space_error::write_failed);
367 template <
typename T>
368 [[nodiscard]] expected<void, address_space_error>
write(uintptr_t addr,
const T &
object)
const noexcept {
369 static_assert(std::is_trivially_copyable_v<T>,
"Target type T must be trivially copyable");
370 return write_bytes(addr, &
object,
sizeof(T));
381 span<char> buffer)
const noexcept {
382 if (vtbl_ ==
nullptr)
383 return unexpected(address_space_error::invalid_handle);
385 return unexpected(address_space_error::invalid_address);
387 return unexpected(address_space_error::empty_buffer);
390 if (!vtbl_->read_string(ctx_.get(), addr, buffer.data(), buffer.size(), chunk.length, chunk.null_terminated)) {
391 return unexpected(address_space_error::read_failed);
400 [[nodiscard]]
constexpr explicit operator bool() const noexcept {
return vtbl_ !=
nullptr; }
405 [[nodiscard]]
constexpr bool can_write() const noexcept {
return vtbl_ !=
nullptr && vtbl_->write_bytes !=
nullptr; }
408 template <
typename Tag> [[nodiscard]]
static constexpr auto write_bytes_entry() noexcept {
410 if constexpr (std::is_void_v<context_type>) {
412 return +[](
const void *, uintptr_t address,
const void *source,
size_t size)
noexcept {
416 return static_cast<bool (*)(
const void *, uintptr_t,
const void *,
size_t) noexcept
>(
nullptr);
418 }
else if constexpr (detail::has_address_space_write_bytes<Tag>::value) {
419 return +[](
const void *context, uintptr_t address,
const void *source,
size_t size)
noexcept {
420 const auto &typed_context = *
static_cast<const context_type *
>(context);
421 return address_space_traits<Tag>::write_bytes(value_ref<const context_type>(typed_context), address, source,
425 return static_cast<bool (*)(
const void *, uintptr_t,
const void *,
size_t) noexcept
>(
nullptr);
429 template <
typename Tag>
430 static bool read_bytes_entry(
const void *context, uintptr_t address,
void *destination,
size_t size)
noexcept {
431 using context_type =
typename address_space_traits<Tag>::context_type;
432 if constexpr (std::is_void_v<context_type>) {
433 return address_space_traits<Tag>::read_bytes(address, destination, size);
435 const auto &typed_context = *
static_cast<const context_type *
>(context);
436 return address_space_traits<Tag>::read_bytes(value_ref<const context_type>(typed_context), address, destination,
441 template <
typename Tag>
442 static bool read_string_entry(
const void *context, uintptr_t address,
char *destination,
size_t capacity,
443 size_t &length,
bool &terminated)
noexcept {
444 using context_type =
typename address_space_traits<Tag>::context_type;
445 if constexpr (std::is_void_v<context_type>) {
446 return address_space_traits<Tag>::read_string(address, destination, capacity, length, terminated);
448 const auto &typed_context = *
static_cast<const context_type *
>(context);
449 return address_space_traits<Tag>::read_string(value_ref<const context_type>(typed_context), address, destination,
450 capacity, length, terminated);
454 template <
typename Tag>
455 static constexpr vtable s_vtbl{&read_bytes_entry<Tag>, write_bytes_entry<Tag>(), &read_string_entry<Tag>};
457 value_ptr<const void> ctx_{};
458 const vtable *vtbl_{
nullptr};
461template <typename Tag, bool Stateless = std::is_void_v<typename address_space_traits<Tag>::context_type>>
471 [[nodiscard]]
constexpr value_ref<context_type>
context() &
noexcept RELOCO_LIFETIMEBOUND {
472 return value_ref<context_type>(context_);
475 [[nodiscard]]
constexpr value_ref<const context_type>
context() const & noexcept RELOCO_LIFETIMEBOUND {
476 return value_ref<const context_type>(context_);
483 value_ref<context_type>
context() && =
delete;
484 value_ref<const context_type>
context() const && = delete;
521 span<
char> scratch RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS,
522 size_t max_limit = 4096) noexcept
523 : addr_(addr), space_(space), scratch_(scratch), max_limit_(max_limit) {}
536 char (&scratch RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS)[N],
537 size_t max_limit = 4096) noexcept
538 : addr_(addr), space_(space), scratch_(scratch, N), max_limit_(max_limit) {}
544 [[nodiscard]]
constexpr uintptr_t
address() const noexcept {
return addr_; }
554 [[nodiscard]]
constexpr span<char>
scratch() const noexcept RELOCO_LIFETIMEBOUND {
return scratch_; }
559 [[nodiscard]]
constexpr size_t max_limit() const noexcept {
return max_limit_; }
565 address_space_ref space_{};
567 span<char> scratch_{};
569 size_t max_limit_{4096};
586 span<std::byte> scratch RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
587 : addr_(addr), space_(space), scratch_(scratch) {}
598 : addr_(addr), space_(space), scratch_(scratch, N) {}
604 [[nodiscard]]
constexpr uintptr_t
address() const noexcept {
return addr_; }
609 [[nodiscard]]
constexpr bool is_null() const noexcept {
return addr_ == 0; }
617 RELOCO_BEGIN_UNSAFE_CODE
618 [[nodiscard]] expected<T *, remote_load_error>
load() const noexcept {
620 return unexpected(remote_load_error::null_address);
621 if (scratch_.size() <
sizeof(T))
622 return unexpected(remote_load_error::scratch_too_small);
623 if (
reinterpret_cast<uintptr_t
>(scratch_.data()) %
alignof(T) != 0)
624 return unexpected(remote_load_error::scratch_misaligned);
626 return unexpected(remote_load_error::invalid_address_space);
628 auto *out_ptr =
static_cast<T *
>(
static_cast<void *
>(scratch_.data()));
629 if (!space_.read_bytes(addr_, out_ptr,
sizeof(T)))
630 return unexpected(remote_load_error::read_failed);
639 address_space_ref space_{};
641 span<std::byte> scratch_{};
666 if (view.address() == 0) {
671 if (!view.space() || view.scratch().empty()) {
676 uintptr_t cur = view.address();
679 while (total < view.max_limit()) {
680 auto chunk = view.space().read_string_chunk(cur, view.scratch());
685 out.write(
"<fault>");
690 const size_t chunk_len = chunk->length;
692 const size_t limit_left = view.max_limit() - total;
693 const size_t to_write = (chunk_len > limit_left) ? limit_left : chunk_len;
694 const auto scratch = view.scratch();
695 out.write(microfmt::string_view(scratch.data(), to_write));
700 if (chunk->null_terminated || total >= view.max_limit()) {
705 if (total >= view.max_limit()) {
720 microfmt::string_view spec_{
""};
734 if (view.is_null()) {
739 auto staged = view.load();
747 elem_fmt.parse(pctx);
748 elem_fmt.format(**staged, out);
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition address_space.hpp:471
constexpr address_space_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition address_space.hpp:479
typename traits_type::context_type context_type
Definition address_space.hpp:467
value_ref< const context_type > context() const &&=delete
value_ref< context_type > context() &&=delete
constexpr address_space(context_type context) noexcept
Definition address_space.hpp:469
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition address_space.hpp:475
static constexpr address_space_ref ref() noexcept
Definition address_space.hpp:493
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
static constexpr address_space_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept
Creates a handle for a stateful address-space tag.
Definition address_space.hpp:279
constexpr bool can_write() const noexcept
Reports whether the bound backend provides write access.
Definition address_space.hpp:405
constexpr address_space_ref() noexcept=default
Constructs an empty (invalid) handle.
constexpr address_space_ref(Tag, Context &&)=delete
expected< void, address_space_error > read(uintptr_t addr, T &out_obj) const noexcept
Reads a trivially-copyable object from the remote address space.
Definition address_space.hpp:314
expected< string_chunk, address_space_error > read_string_chunk(uintptr_t addr, span< char > buffer) const noexcept
Reads a bounded chunk of a remote NUL-terminated string.
Definition address_space.hpp:380
constexpr address_space_ref(Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a handle for a stateful address-space tag.
Definition address_space.hpp:251
expected< void, address_space_error > write(uintptr_t addr, const T &object) const noexcept
Writes a trivially-copyable object into the remote address space.
Definition address_space.hpp:368
expected< void, address_space_error > read_bytes(uintptr_t addr, void *dest, size_t size) const noexcept
Reads raw bytes from the remote address space.
Definition address_space.hpp:294
expected< T, address_space_error > read(uintptr_t addr) const noexcept
Reads and returns a trivially-copyable object.
Definition address_space.hpp:326
static address_space_ref make(Context &&)=delete
expected< void, address_space_error > write_bytes(uintptr_t addr, const void *src, size_t size) const noexcept
Writes raw bytes into the remote address space.
Definition address_space.hpp:345
static constexpr address_space_ref make() noexcept
Creates a handle for a stateless address-space tag.
Definition address_space.hpp:265
Definition address_space.hpp:462
Definition microfmt.hpp:881
Lazily-loaded reference to a trivially-copyable remote object.
Definition address_space.hpp:577
constexpr remote_ref(uintptr_t addr, address_space_ref space, std::byte(&scratch)[N]) noexcept
Constructs a remote object reference over a fixed C array.
Definition address_space.hpp:597
constexpr bool is_null() const noexcept
Reports whether the reference points to address zero.
Definition address_space.hpp:609
constexpr uintptr_t address() const noexcept
Returns the remote object address.
Definition address_space.hpp:604
RELOCO_BEGIN_UNSAFE_CODE expected< T *, remote_load_error > load() const noexcept
Loads the object into the scratch buffer.
Definition address_space.hpp:618
constexpr remote_ref(uintptr_t addr, address_space_ref space, span< std::byte > scratch RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a remote object reference.
Definition address_space.hpp:585
Streaming view of a remote NUL-terminated string.
Definition address_space.hpp:506
constexpr remote_string_view() noexcept=default
Constructs an empty (null) view.
constexpr size_t max_limit() const noexcept
Returns the maximum render length.
Definition address_space.hpp:559
constexpr address_space_ref space() const noexcept
Returns the address space handle.
Definition address_space.hpp:549
constexpr remote_string_view(uintptr_t addr, address_space_ref space, char(&scratch RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS)[N], size_t max_limit=4096) noexcept
Constructs a view over a remote string using a fixed C array as scratch.
Definition address_space.hpp:535
constexpr uintptr_t address() const noexcept
Returns the remote string address.
Definition address_space.hpp:544
constexpr span< char > scratch() const noexcept RELOCO_LIFETIMEBOUND
Returns the scratch chunk buffer.
Definition address_space.hpp:554
#define MICROFMT_API_CLASS
Definition compat.hpp:181
RELOCO_BEGIN_UNSAFE_CODE constexpr uintptr_t add_address_offset(uintptr_t address, ptrdiff_t offset) noexcept
Definition address_space.hpp:52
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
remote_load_error
Definition address_space.hpp:39
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
address_space_error
Definition address_space.hpp:22
Virtual table of address-space operations.
Definition address_space.hpp:209
static RELOCO_BEGIN_UNSAFE_CODE bool read_bytes(uintptr_t addr, void *dest, size_t size) noexcept
Copies raw bytes from addr into dest.
Definition address_space.hpp:123
RELOCO_END_UNSAFE_CODE static RELOCO_BEGIN_UNSAFE_CODE bool write_bytes(uintptr_t addr, const void *src, size_t size) noexcept
Copies raw bytes from src into addr.
Definition address_space.hpp:145
void context_type
Definition address_space.hpp:111
RELOCO_END_UNSAFE_CODE static RELOCO_BEGIN_UNSAFE_CODE bool read_string(uintptr_t addr, char *dest, size_t max_len, size_t &out_len, bool &null_term) noexcept
Reads a string until a null terminator or the length limit.
Definition address_space.hpp:169
Definition address_space.hpp:37
Definition address_space.hpp:61
Definition address_space.hpp:73
Static customization point describing a remote address space.
Definition address_space.hpp:103
Definition address_space.hpp:32