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 freesubview/concat build 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).

inline view_t subview(std::size_t sub_offset, std::size_t sub_length) const

A narrower window into the same segment (shares ownership — a refcount bump, no copy).

Note

Precondition: sub_offset + sub_length <= length.

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.

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 rope_t(view_t v)

Implicitly adopt a single view as a one-link rope.

inline void append(view_t v)

Append a link (chaining — no copy).

inline rope_t &concat(const rope_t &other)

Chain other's links onto this rope (no copy).

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 false rope 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).

See: segment, frame-codec, graph.