views — zero-copy windows (L1)¶
In one paragraph
A view_t is a (segment, offset, length) window onto real bytes; copying it is
a refcount clone, not a byte copy. A rope_t is a chain of views, so one logical
message can span several buffers (a static header + a live DMA payload) without
copying. view_as_tlv realizes L1’s load-bearing claim — a TLV is a cast from
a view — by running the M1 decoder over a view’s bytes in place.
What it does¶
L1 sits between real memory (L0) and TLV bytes (L2). Its types — view_t, rope_t,
and segment_ptr_t — live in tr::view. It owns the ownership
semantics, not the bytes. A single-link view_t is the hot path and allocates
nothing; a multi-link rope_t models scatter-gather. rope_t::to_iovec() hands the
chain to writev/sendmsg-style egress with zero copies; rope_t::flatten()
materializes it into one contiguous segment only when a flat-buffer consumer
demands it (the single transport-boundary copy). Assembling a multi-buffer message is
chaining views into a rope_t, never a memcpy — a contiguous copy happens only
when flatten() runs at a substrate boundary that cannot scatter-gather.
view_as_tlv(v) is just decode(v.bytes()) — the decoded tlv_t’s payload spans
point into the view’s segment, and the view’s segment_ptr_t keeps them alive. No
decode-into-a-struct step: the wire bytes are the in-memory value.
Interface¶
struct view_t {
segment_ptr_t owner; std::size_t offset, length;
static view_t over(segment_ptr_t); // whole segment
std::span<const std::byte> bytes() const;
view_t subview(std::size_t off, std::size_t len) const; // narrower, shares owner
};
class rope_t { // ordered chain of views
rope_t(view_t); // a view is a 1-link rope
void append(view_t); rope_t& concat(const rope_t&);
std::size_t total_length() const; template<class F> void walk(F) const;
std::vector<std::span<const std::byte>> to_iovec() const; // zero-copy egress
view_t flatten(mem_backend_t& = mem::heap_backend()) const; // one-copy materialize
};
std::expected<tlv_t, err_t> view_as_tlv(const view_t&); // the L1 -> L2 cast
Rope = one message, many buffers¶
flowchart LR
H["view A · header<br/>static segment"] --> P["view B · payload<br/>DMA segment"] --> T["view C · tail<br/>pool segment"]
H -.-> S1[(seg 1)]
P -.-> S2[(seg 2)]
T -.-> S3[(seg 3)]
R["rope_t.to_iovec() → writev()"]:::e
H --- R
classDef e fill:#dbeafe,stroke:#1e40af;
Benefits¶
A TLV is a view — no parse-into-struct; the decoder returns borrowed spans, so reading a field is a pointer load.
Scatter-gather without copies — compose a message from separate buffers and emit it with one
writev; flatten only when a transport truly needs contiguity.Slicing is free —
subview/concatbuild new view structs that bump the segment refcount; no bytes move.
API reference¶
Generated from core/include/libtracer/view.hpp and rope.hpp by Doxygen.
-
struct view_t¶
A single contiguous window into one segment_t.
Copyable; copy == clone (bumps the segment refcount). The common case is one link; ropes (rope.hpp) chain several.
Note
Invariant:
offset + length <= owner->bytes.size().Public Functions
-
inline bool empty() const noexcept¶
True when the window is empty.
-
inline std::span<const std::byte> bytes() const noexcept¶
The window’s bytes as a read-only span (empty if unowned).
Warning
For a is_device window the span addresses non-CPU memory — do not dereference it on the CPU (docs/adr/0024).
-
inline bool is_host() const noexcept¶
True when this window’s bytes are CPU-addressable (HOST). Unowned ⇒ host.
-
inline bool is_device() const noexcept¶
True when this window’s bytes are non-CPU (DEVICE, e.g. GPU memory).
Public Members
-
segment_ptr_t owner¶
The segment whose bytes this view borrows.
-
std::size_t offset = 0¶
Byte offset of the window within the segment.
-
std::size_t length = 0¶
Window length in bytes.
Public Static Functions
-
static inline view_t over(segment_ptr_t seg) noexcept¶
A view covering the whole of
seg.
-
inline bool empty() const noexcept¶
-
class rope_t¶
An ordered chain of view_t links — one logical byte sequence spread across segments, assembled by chaining and never by copying.
The rope is the transport-agnostic scatter-gather representation: each transport lowers it to its native DMA (
iovec/sendmsg, CAN descriptors, RDMA verbs) via to_iovec. The single contiguous copy is flatten, taken only at a substrate boundary that cannot scatter-gather.Public Functions
-
inline std::size_t link_count() const noexcept¶
Number of links in the chain.
-
inline std::span<const view_t> links() const noexcept¶
The links, in order (inline small-buffer storage or the spilled chain).
-
inline const view_t &only() const noexcept¶
The single contiguous link — the consumer’s explicit “this value is
one segment” (ADR-0053 §6), zero copy.
Note
Precondition:
link_count() == 1(debug-asserted). A consumer that cannot promise contiguity calls materialize instead.
-
inline view_t materialize(mem::mem_backend_t &backend = mem::heap_backend()) const¶
The rope as one contiguous view_t — zero copy when single-link, one flatten copy otherwise.
The visible choice a contiguous-bytes consumer makes (ADR-0053 §6): a single-link rope is returned as its link (a refcount bump, no byte copy); a multi-link rope pays the single flatten copy from
backend. Distinct from flatten, which always copies — this keeps the trivial case free.
-
inline std::size_t total_length() const noexcept¶
Total logical length across all links.
-
inline bool all_host() const noexcept¶
True when every link is CPU-addressable (HOST).
A
falserope is heterogeneous — it has a DEVICE link (e.g. a GPU payload, docs/adr/0024) that the CPU must not dereference, so host-side operations (flatten, CRC) cannot touch it.
-
inline rope_t subrope(std::size_t off, std::size_t len) const¶
The
[off, off + len)sub-range as its own rope (chaining — no copy).Trims the covering links with view_t::subview, so the result shares (refcounts) exactly the segments its window touches and keeps only those alive — the region primitive of the lazy decode tier (ADR-0053 §1): a child TLV, a routed path suffix, or a payload handed onward is a subrope of the inbound frame, never a copy of it.
Note
Precondition:
off + len <= total_length()(debug-asserted via the subview window invariant; a shorter tail yields a shorter rope).
-
template<class Fn>
inline void walk(Fn &&fn) const¶ Visit each link’s contiguous bytes in order (parsers, serializers, CRC).
-
inline std::vector<std::span<const std::byte>> to_iovec() const¶
Scatter-gather egress: spans into the original segments (no copy).
Hand the result to
writev/sendmsg-style I/O for true zero-copy transmit.
-
view_t flatten(mem::mem_backend_t &backend = mem::heap_backend()) const¶
Materialize the rope into one contiguous segment from
backend(one copy).The single bridge-boundary copy — taken only when a flat-buffer consumer demands it. The flattened view can then be cast with
view_as_tlv(frame.hpp,tr::wire).- Return values:
{} – An empty view if the backend cannot allocate, **or if the rope is not all_host** (a DEVICE link cannot be CPU-memcpy’d — docs/adr/0024; lower such a payload via its device transport).
-
inline std::size_t link_count() const noexcept¶
See: segment, frame-codec, graph.