DEV Community

COMMENTERTHE9
COMMENTERTHE9

Posted on • Originally published at cx-lang.com

Cx Dev Log — 2026-07-04

Handles in Cx have taken a big step forward with two key commits now on submain. The Handle type has finally become a real machine value processed through Cranelift, marking a major milestone for Phase 8 Round 2.

The packed-i64 Representation

In Cx, a Handle is straightforward: a packed single i64 represented by slot | (gen << 32). Both slot and gen are unsigned u32s and we use a u64 reinterpretation to avoid any sign-extension issues. Specifically, lower_type(SemanticType::Handle(_)) outputs an IrType::I64.

Handling these machine values required placing runtime state in a HandleRegistry<i64> behind a OnceLock> static in host_boundary.rs. This setup is a workaround for Cranelift limitations. The constraint? jit_builder.symbol() relies on raw function pointers, not state-capturing closures. Safety here comes from run_jit_subprocess which isolates states by using fresh OS processes for each fixture, preventing cross-test state carryover.

A helper, widen_handle_payload_to_i64, converts inner expressions' lowered types to i64 but only for {I8, I16, I32, I64, Bool} types. Notably excluded are:

  • Ptr: We'd face the Handle<str> issue, where a raw scalar or a string descriptor pointer could confuse operations.
  • F64: This would lead to silent truncation, not suitable for types requiring round-trip fidelity. Consideration here is crucial, reflecting intentional design rather than future work placeholders.

Handle.val and the Out-parameter Approach

Reading the value (commit D2.5b) posed more challenges than expected. Our initial "uniform-i64" hypothesis didn't hold water due to strict type equality required downstream. Instead, HandleVal's semantic design uses I128, not i64, so handling it involves widening the i64 payload via sign-extending Cast to output a legitimate IrType::I128.

For validity checks, an out-parameter is used instead of sentinels, given that the slot+gen packed space leaves no room for reserved bit patterns without conflicts. _cx_handle_val(handle: i64, out_valid: *mut i8) -> i64_ performs the out-parameter check and defaults to IrTerminator::Trap if invalid. The reuse of the Alloca/Store/Load pattern, similar to Results, adapts well, though a single-byte slot suffices here.

Though the Trap path is hardwired, we can't yet test invalid handles through current Cx code; since there’s neither HandleDrop nor uninitialized reads. The real testing awaits D2.5c alongside generational reuse that will naturally generate stale handles.

Handle+Array Composition Success

A third commit by early July 3rd confirmed the correctness of Handle+array composition. An earlier syntax error audit, using arr[i] instead of the correct arr:[i], had been resolved. Now, Handles in array literals process and return expected results. Notably, the t_handle_array_no_drop fixture confirms this improvement.

Test Matrix Progress

The matrix on submain now stands at 297/0 after rising from 292. Parity scores are 237 PASS / 60 SKIP / 0 PARITY_FAIL across this set. Recent commits introduced several new fixtures, such as:

  • t_handle_construct_cross_fn (from D2.5a, provisional)
  • t_handle_val_positive (standard round-trip case)
  • t_handle_val_negative (tests for sign-extension)
  • t_handle_val_bool (validates discrimination)

Currently, all updates remain within submain, with main unchanged.

What's Next

Up next is the HandleDrop (D2.5c), set to handle generational reuse and empirically prove the stale-handle Trap path. An integration point approaches with submain now four commits ahead of v0.3.0 and a merge to main beckons. Still in the wings are DotAccess updates in compound forms, alongside uncommitted tutorial rewrites—280 lines ready to fit into the broader narrative.


Follow the Cx language project:

Originally published at https://cx-lang.com/blog/2026-07-04

Top comments (0)