Documentation

Runtime Ownership Inventory

Runtime Ownership Inventory

External native packages initially use existing scalar/string extern contracts. An external Arx class may own a checked integer token through __init__ and __del__, but token validation and foreign allocations belong to its C ABI wrapper. This adds no raw pointer ownership, foreign byte-buffer ownership, owned extern string results, or foreign-frame unwind. Generated Arx calls use checked exception propagation and lexical cleanup. See native packages.

Status and purpose

This inventory was checked against the local IRx lowering and native runtime on 2026-11-02. It is the starting point for the Arx resource model, not yet a complete production ownership specification. Scalar dynamic lists, immutable strings/bytes, text/binary files, and acyclic class references have bounded enforced ownership slices. A resource may be promoted to the stable language only after its create, borrow, transfer, escape, release, and failure paths are represented here and enforced by semantic metadata and lowering.

Required vocabulary

The final Arx 1 model must choose which of these operations source programs can observe. LLVM lowering must consume the ownership decision from semantic metadata; it must not infer ownership again from AST shape.

Current inventory

Resource Creation/allocation Current release path Current ownership state Required work
Scalar values and literal aggregate storage LLVM values, globals, or stack allocation Function/stack lifetime Non-owning values Specify overflow and invalid-operation behavior.
Dynamic list backing bytes realloc in irx_list_append Semantic owners call idempotent irx_list_destroy on lexical exits and replacement One scalar-list owner; return transfers ownership Add nested owning elements, generator/object-field lifecycle, and sanitizer/fault proof.
Literal-list runtime view Stack descriptor over stack/literal data Stack lifetime Static/non-owning sidecar Escape and mutation are rejected; retain conformance coverage as literal representation evolves.
Concatenated string Checked malloc in generated strcat_inline Semantic owner calls free on lexical exit/replacement One owner; return moves to caller Add sanitizer/fault-injection and long-running leak proof.
Numeric/string formatting result Checked malloc in _snprintf_heap Consuming print frees immediately; string values follow semantic cleanup Temporary or transferred owner Add injected snprintf failure and allocator-fault execution tests.
String literals, empty strings, type names LLVM global constant No release required Static/non-owning sidecar Retain negative coverage so static pointers never reach free.
Class instance Checked zeroed irx_class_create Last irx_class_release invokes concrete drop then frees payload/token Acyclic reference-managed identities; strings/files/class fields owned Cycles, concurrency, general container/generator/global/external ownership deferred.
Generator frame malloc in generator factory Lexical/temporary owner frees scalar-only frames on every exit Single owner; iterator parameters borrow; bounded materialization does not rewind Resource capture, rebinding, borrowed returns, and explicit close remain unsupported.
Buffer owner Native malloc; optional external release callback irx_buffer_owner_release Reference-counted owner Add sanitizer/fault tests and prove all retain/release/status paths, including interpreter/FFI failures.
Buffer view Native view plus owner handle irx_buffer_view_release Shared owner with borrowed byte range Enforce view lifetime and status checks at every lowering path.
Arrow schema C++ handle owning shared Arrow schema irx_arrow_schema_release Opaque owner Audit every create failure and output slot; keep Python wrappers idempotent.
Arrow array builder C++ unique builder handle finish consumes or builder release Opaque owner with consuming finish Prove exactly-once consume/release on success and failure.
Arrow array C++ handle with shared Arrow array irx_arrow_array_release Opaque shared-data owner Document borrow/transfer for C Data Interface export/import.
Arrow tensor builder C++ unique builder handle finish consumes or builder release Opaque owner with consuming finish Prove cleanup for append/shape/finish failures.
Arrow tensor C++ handle with shared buffer irx_arrow_tensor_release Opaque shared-data owner Specify Arx return/argument/view ownership and shape validation.
Arrow table/DataFrame C++ table handle irx_arrow_table_release Opaque owner Ensure all lowered construction paths release intermediate arrays and table results.
Arrow Series/chunked array C++ chunked-array handle irx_arrow_chunked_array_release Opaque owner Define whether field selection returns an owned handle or borrow; test parent release ordering.
RecordBatch type descriptor C++ allocated descriptor irx_type_release Opaque owner Keep local descriptor cleanup on every nested schema failure.
RecordBatch schema C++ schema handle irx_rb_schema_release Python wrapper owner Partial-construction safety is implemented; add context-manager and explicit closed-state checks.
RecordBatch builder C++ builder handle finish resets column buffers; builder release Python wrapper owner Specify wrapper behavior after finish and reject reuse deterministically.
RecordBatch batch C++ shared RecordBatch handle irx_rb_batch_release Python wrapper owner Retain idempotent release and reject access after release.
RecordBatch stream writer C++ writer and sink close plus irx_rb_stream_writer_release Python wrapper owner Define close failure versus unconditional release; test both file and buffer errors.
RecordBatch stream reader C++ reader irx_rb_stream_reader_close Python wrapper owner Clarify whether close deletes the handle and make repeated close/access rules explicit.
PyArrow interchange capsules Arrow C Data Interface callbacks Arrow release callbacks Transferred/shared by protocol Test both release orders, partial imports, and abandoned capsules.

Current status and output-slot rules

The Arrow and RecordBatch C APIs generally return integer statuses and place new handles or values in caller-owned output slots. The required rule is:

  1. initialize the output slot to null/zero;
  2. invoke the ABI function;
  3. if status is not success, do not read or release the output as a valid value;
  4. translate the native error into a structured diagnostic or API exception;
  5. on success, immediately record the resulting ownership obligation.

Dynamic list append and indexing return checked statuses. Indexing publishes its borrowed element pointer through an output slot only on success; lowering turns failures into typed exceptions before reading that slot. The irx_list_status_message accessor supplies diagnostic text, not error categories.

Enforced scalar dynamic-list contract

IRx semantic analysis attaches a typed ResourceOwnership sidecar to list expressions, declarations, arguments, calls, assignments, and returns. The current contract is deliberately move-only:

Lowering consumes these sidecars. It registers irx_list_destroy for owned locals and non-transferred temporaries, emits cleanup on normal block fallthrough, return, break, and continue, excludes an owner moved through a return, and retains cleanup for the caller that receives the result. Construction registers rollback before evaluating elements, including a pending move or return. Native append copies string elements, frees unpublished copies on allocation failure, and destroy drops every initialized string before freeing the backing bytes. Indexed and iterated strings own independent copies.

ListLength and CollectionLength return Int64, without a truncating legacy path. Mutable list borrows require an identical element domain; scalar numeric widening is not a storage conversion.

Tensor scalar-consumer temporaries

Analysis identifies fresh tensor expressions, including views rooted in a literal. Scalar consumers such as indexing release these temporary owners after use, including failures while evaluating indices. Construction guards every status/output boundary and releases partial builders or tensors on failure. Fixed-size call and literal scratch slots are per invocation, not per loop iteration. This does not establish automatic ownership for named tensor locals, arguments, or returns; explicit IRx retain/release operations are unchanged.

Enforced string contract

IRx uses ResourceKind.STRING sidecars to distinguish immutable global pointers from heap owners even though both lower to the current C-string pointer ABI. Concatenation, numeric-to-string formatting, text indexing/slicing/stripping, and defined Arx string-returning calls are owners; literals, empty defaults, and type names are static; function parameters and identifier expressions borrow.

Owned locals and temporaries call free on all ordinary control-flow exits. Owned replacement frees the old generation first. Returning an owner moves it to the caller, while returning static storage performs a checked copy so the call result has one unambiguous release obligation. Allocation and formatting failure emit structured ARX-RUNTIME-STRING-001 or ARX-RUNTIME-STRING-002 records and terminate before a null pointer is used.

Bindings cannot change between static and owned storage. Borrowed parameters cannot initialize owned locals or escape through returns. Owning struct fields, generator locals, external string results without an ownership ABI, and identity casts that would obscure an owner fail closed with IRX-S013.

The registered text runtime validates UTF-8 before operating on code points. Splitting publishes an owned string list only after all fields succeed; failure drops every initialized field. Output slots are read only after success. Snapshots protect text operands from later argument-side mutations, and newly allocated results are protected if operand cleanup raises. Text helpers borrow C-string inputs: producers of length-delimited data must reject embedded NUL before conversion; the bounded native validator is provided for that boundary. The current pointer ABI cannot discover bytes after its terminating NUL.

Cleanup control flow

IRx has typed cleanup_stack actions used by context managers, list owners, and string owners. Resource cleanup extends this mechanism only after semantic analysis states whether a value is an owner, borrow, move, or escape. Required control-flow cases include:

Cleanup emission must never occur after an LLVM terminator and must never double-release a moved value.

Enforcement and verification backlog

  1. Extend the ownership sidecar model beyond acyclic classes to generators and native handles.
  2. Add sanitizer, allocator-fault, and bounded-growth proof for list and string cleanup.
  3. Decide whether the stable ABI keeps compile-time-classified C-string pointers or adopts a tagged string value before compatibility freeze.
  4. Add class and generator lifecycle operations before stabilizing either ABI.
  5. Make Python wrappers reject use after release and support deterministic context management where useful.
  6. Add allocator fault injection for every create/append/finish path.
  7. Run compiled ownership programs under ASan, LSan, and UBSan.
  8. Add long-running create/use/release loops with bounded memory growth.
  9. Test output slots remain unread on all injected non-success statuses.
  10. Document any intentionally immortal allocation; process-lifetime leakage is not an implicit ownership policy.

Enforced file contract

Immutable bytes reuse ResourceKind.LIST and the byte-width descriptor only for storage, not mutable-list operations. Construction copies checked integers; File reads transfer independently allocated buffers only after successful status. Parameters borrow, returns move, and replacement/exception exits destroy owned storage. Explicit bytes(existing) copies; implicit local aliases and nested ownership remain rejected.

Public File objects use ResourceKind.CLASS reference ownership. Their private native field retains ResourceKind.FILE unique ownership; existing low-level IRx handle locals/fields keep their original no-copy/no-escape contract. ParsedModule.native_file_class is an explicit host capability referring to a validated declaration in that module, not a privilege inferred from its name or origin string. IRx never discovers/parses frontend builtin source.

Both public factory forms share a ResolvedClassConstruction with a typed native_file initialization descriptor containing the bound operands and native field. Creation checks the native status in the caller before publishing the handle and completing the phase, preserving active construction rollback. Native-backed method metadata records the private receiver field so the backend can pin the class receiver, check emptiness, and preserve real call-site I/O locations without rediscovering class rules. Ordinary cleanup releases the raw field once. Public references may alias, cross normal function boundaries, and live in class fields; existing class aggregate/global/generator limits remain.

The optional registered file runtime still uses the opaque irx_file_* C ABI. Every output slot is read only after successful status checking. Explicit close is idempotent; final class release also frees the native wrapper. Typed I/O failures propagate through generated cleanup edges. Unrecoverable native failures do not promise foreign-frame unwinding.

Acyclic Class References

The class slice uses ResourceKind.CLASS, analyzed retain/move decisions, slot-based mutable cleanup, argument/receiver pins, and concrete final-drop callbacks. Three pointer header slots carry lifetime token, field descriptor, and dispatch. The opaque native token stores reference count, phase progress, and callback; LLVM never models its C layout. Each owned reference releases once. Nullable field references allow retain/release of null, but access is checked. Last release always consumes payload/token even on callback error.

Owned string/file/class fields drop in reverse canonical storage phases, after each completed phase's __del__. Partial construction skips incomplete hooks but releases all initialized fields. Native field-release errors preserve the first status and continue other field releases. Source hook bodies cannot escape/resurrect self. Generated calls propagate exceptions through cleanup; implicit destruction preserves an existing primary failure and records secondary failures. Native corruption and exception-machinery allocation failures remain unrecoverable. String-field reads take owned snapshots so later expression effects cannot invalidate an evaluated string pointer. Reference counts are non-atomic, and cyclic schemas (including subtype edges) are rejected. General containers, generator capture, global owners, and external class ownership remain out of scope.

Named Calls And Default Arguments

Named operands receive the same borrow/escape checks and lifetime pins as positional operands. Calls evaluate/pin the receiver, evaluate supplied values in source order, then fill omitted defaults in declaration order using resolved parameter symbols. The completed values are emitted in positional ABI order. Class and string-field snapshots must survive later keyword/default effects. Public File method lowering consumes this same binding plan while retaining caller-side native status checks and locations. Generated calls prepend an exception context and publish result slots only on success. Foreign C ABI contracts, cyclic-ownership restrictions, and raw-handle escape checks remain.

RecordBatch IPC and C sharing

The native builder owns a schema snapshot, so its source schema may be released or modified independently. finish() transfers completed column buffers to an independent batch and leaves the builder reusable. Batch and reader schema snapshots retain the entire Arrow schema, including metadata and timezones.

The stdlib.arrow classes own IrxRbApi* boxes in private opaque fields. Their ordinary class destructors destroy each box exactly once. close() releases the box's contained resource but keeps the box alive for aliases and repeated close. A closed object rejects value access. Writer destruction closes best-effort; call close() explicitly to observe flush/footer failures. The checked facade returns typed statuses and caller-owned diagnostics; generated callers raise nominal exceptions only after checking those statuses. Cleanup preserves the existing stream publication/abandonment contract.

C Data exports and stream outputs retain buffers independently. Move imports consume the supplied C structs; failure can also consume them, so inspect the release pointer before cleanup. irx_rb_stream_reader_export transfers remaining iteration, leaving the old handle exhausted. The host owns outer C struct storage and must keep the producer library loaded. Guarded irx_rb_c_*_release can be repeated on allocated structs, but raw handle releases still free exactly once. These operations are not concurrent/thread-safe.

IPC readers own file/socket resources; closing a reader does not invalidate a previously returned batch. Descriptor endpoints duplicate the host descriptor; the host must not interleave reads/writes on shared offsets. The transport reads one Arrow message/batch at a time. Native errors leave output handles unreadable; callers must check status before loading any output, even if a failure path currently clears it to null.

Borrowed numeric sequences

Semantic adapter plans select exact numeric storage; lowering never infers a conversion or flattens tensors. Descriptors hold borrowed context, i64 length, a checked scalar reader, and configuration; native callbacks publish bits and validity only on success. Lists borrow immutable call storage. Tensor adapters retain their backing owner (or discharge a fresh temporary's obligation), and honor byte offsets and signed strides. Series snapshots allocate only an owning handle sharing immutable chunk buffers, independent of later alias replacement. All pins unwind on return, break, and checked failures. Sequence descriptors are parameter-only and cannot escape into fields, returns, or generator frames.

Bounded materialization owns partial output until publication. Fresh iterator frames are temporary owners; named iterators remain lexical owners and calls borrow them. A failed bound check can consume the extra probe item, never return a truncated prefix, and never roll the input back. Generator frames currently contain only non-owning scalar state: captured resources and nested owning locals are rejected rather than relying on free to run destructors.

Search documentation

Type to search.