Skip to content

Stackless VM: heap-allocated frames, continuations, portable coroutines#488

Merged
stevedekorte merged 32 commits intomasterfrom
stackless
Mar 5, 2026
Merged

Stackless VM: heap-allocated frames, continuations, portable coroutines#488
stevedekorte merged 32 commits intomasterfrom
stackless

Conversation

@stevedekorte
Copy link
Member

Summary

  • Fully iterative evaluator: replaces C stack recursion with heap-allocated frames (IoEvalFrame as GC-managed IoObject). No setjmp/longjmp, no ucontext, no platform-specific assembly.
  • First-class continuations: callcc captures frame stack, continuations are serializable via asMap.
  • Portable coroutines: frame-based switching instead of C stack switching. Old platform-specific coroutine library removed.
  • Resumable exceptions: signal/withHandler alongside existing raise/try.
  • Tail call optimization: direct TCO + TCO through if branches with savedCall preservation.
  • Performance optimizations: inline cache for slot lookups, object pooling (frames, blockLocals, Call, Numbers), tight C loops for cached literals, ~1.5-4x faster than master on benchmarks.
  • Io-level frame introspection: Coroutine currentFrame, frame walk via parent.
  • All 249 Io tests + 25 C tests pass.

Test plan

  • _build/binaries/test_iterative_eval — 25/25 C tests pass
  • io ../libs/iovm/tests/correctness/run.io — 249/249 Io tests pass
  • Smoke test: io -e 'if(true, "yes") println; for(i,1,3, i println); list(1,2,3) foreach(v, v println); e := try(1 unknownMethod); e error println; "done" println'

🤖 Generated with Claude Code

stevedekorte and others added 30 commits January 20, 2026 13:27
This commit introduces a C-stack-independent evaluator that enables
first-class continuations, serializable execution state, and
network-portable coroutines.

## New Components

- IoEvalFrame: Frame structure for explicit evaluation stack
  * Replaces C recursion with explicit state machine
  * Contains all evaluation context (target, locals, message, args)
  * Supports parent frame linking for stack unwinding

- IoState_iterative.c: Main iterative evaluation loop
  * State machine with 6 states (START, EVAL_ARGS, LOOKUP_SLOT, etc.)
  * Walks IoMessage AST without C recursion
  * Handles message sends, slot lookups, and block activations
  * Frame pooling optimization (10-15% faster)

- IoState_iterative_fast.c: Experimental fast evaluator
  * Uses computed gotos (GCC extension) for faster dispatch
  * Thread-local frame pooling
  * Currently has issues, kept for future work

## Performance

- Iterative evaluator: ~1.8x overhead vs recursive (with pooling)
- Test results: 9/10 tests passing
- Benchmarks: 100k iterations across 7 operation types

Tested operations:
  - Local access: 22.92 M ops/sec (vs 39.35 M recursive)
  - Simple messages: 7.09 M ops/sec (vs 15.67 M recursive)
  - Block activations: 0.03 M ops/sec (vs 0.07 M recursive)
  - Method calls: 0.04 M ops/sec (~same as recursive!)

## Modified Files

- IoState.h: Added currentFrame, frameDepth, framePool fields
- IoState.c: Initialize frame stack and pool
- IoState_eval.h: Added iterative evaluation API declarations

## Test Suite

- test_iterative_eval.c: Correctness tests (9/10 passing)
- benchmark_iterative.c: Performance comparisons

## Future Work

- Continuation capture/restore APIs
- Inline caching for slot lookups (2-3x improvement)
- Bytecode compilation (5-10x improvement)
- Make primitives non-reentrant

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
All control flow primitives (if, while, loop, for, etc.) currently
re-enter the recursive evaluator via IoMessage_locals_valueArgAt_()
and IoMessage_locals_performOn_(), which breaks continuation support.

This causes execution state to be split between:
- IoEvalFrame stack (our explicit frames)
- C call stack (primitive activation records we cannot capture)

This prevents:
- Serializable/restorable execution state
- First-class continuations
- Eliminating platform-specific coroutine code

Solution: Refactor primitives to use frame-based trampolining instead
of direct evaluation. Primitives should push frames with continuation
info and return to the main loop rather than calling back into the
evaluator.

See CONTINUATIONS_TODO.md for detailed analysis and implementation plan.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit implements non-reentrant 'if' primitive for the iterative
evaluator, making significant progress toward full continuations support.

## Changes

### Frame State Machine Extensions

- Added control flow states: IF_EVAL_CONDITION, IF_CONVERT_BOOLEAN, IF_EVAL_BRANCH
- Added control flow union to IoEvalFrame to store branch info
- Updated IoEvalFrame_mark() to mark control flow continuation data
- Updated IoEvalFrame_reset() to clear control flow union

### Special Form Detection

- Added ifSymbol, whileSymbol, loopSymbol, forSymbol to IoState
- Modified FRAME_STATE_START to detect special forms and skip eager arg evaluation
- Special forms now handle their own lazy argument evaluation

### Non-Reentrant 'if' Primitive

- Refactored IoObject_if() to detect evaluator mode (currentFrame != NULL)
- Iterative mode: Sets up control flow state, returns to eval loop
- Recursive mode: Preserves original reentrant implementation
- Both evaluators coexist without breaking each other

### Eval Loop Control Flow Handling

- Added IF_EVAL_CONDITION: Pushes frame to evaluate condition
- Added IF_CONVERT_BOOLEAN: Pushes frame to call asBoolean on result
- Added IF_EVAL_BRANCH: Determines branch and pushes frame to evaluate it
- Modified RETURN case to handle returning to control flow states
- Added needsControlFlowHandling flag for primitive trampolining

### Debug Support

- Added debug output for special form detection
- Added debug output for IF primitive execution
- Added debug output for block activation tracking

## Status

9/10 tests passing. The lazy argument evaluation test reveals a subtle bug:

**Issue**: Block body (method) is being evaluated twice
- Only one block activation occurs (correct)
- But the block's message chain is processed twice
- Counter increments from 0 → 1 → 2 instead of 0 → 1
- Likely issue: CONTINUE_CHAIN or RETURN logic for block frames

**Root cause under investigation**: The block frame's message ("updateSlot")
appears to be evaluated twice despite no "next" message in the chain.

## Next Steps

1. Fix block body double-evaluation bug
2. Remove debug output once tests pass
3. Refactor 'while', 'loop', 'for' primitives similarly
4. Refactor all primitives that call IoMessage_locals_valueArgAt_()
5. Test nested control flow and complex cases

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ions

Replace C stack recursion with heap-allocated frames to enable first-class
continuations and portable coroutines without platform-specific assembly.

- Iterative eval loop with frame state machine (IoState_iterative.c)
- Control flow primitives (if, while, for, loop) as frame manipulations
- First-class continuations via callcc with frame stack copy/restore
- Frame-based coroutines (no setjmp/longjmp, no ucontext, no fibers)
- Io-level exceptions bridged to eval loop via rawSignalException
- 13/13 tests passing including callcc and exception handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove inRecursiveEval flag — all evaluation now routes through the
iterative eval loop when currentFrame is set. A bootstrap-only recursive
fallback remains for VM initialization (before the first eval loop starts).

Key changes:
- Replace IoState_handleStopStatus_ with IoState_unwindFramesForError_
  which respects isNestedEvalRoot boundaries (prevents stale frame
  pointers when coro switch + nested eval combine)
- Control flow primitives (if, while, for, loop) use iterative path
  exclusively; bootstrap fallback only when !state->currentFrame
- Add error-safety checks (errorRaised return-early) across CFunctions:
  IoList, IoFile, IoMap, IoSeq, IoDate, IoDirectory, IoNumber, IoObject
- Remove libcoroutine entirely (no C stack switching needed)
- Add shouldExit check in eval loop for System exit support
- Track nestedEvalDepth in coroutine eval loop
- Add special forms: repeat, do, lexicalDo, foreachSlot,
  cpuSecondsToRun, sortInPlace, or, and
- Unbound block params default to nil (matching master behavior)
- Fix stop-status propagation through CONTINUE_CHAIN states
- Add FRAME_STATE_FOREACH_AFTER_BODY to RETURN handler
- 21/21 C tests pass, 227/230 Io tests pass (3 pre-existing failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ection

Implement tail call optimization through if() branches: when if() is the
last message in a chain, evaluate the selected branch in-place instead of
pushing a child frame. This enables TCO for the common recursive pattern
`if(n <= 0, acc, recurse(n - 1, acc))`.

Add savedCall field to IoEvalFrame to preserve the original Call object's
stop status across in-place if optimization + TCO. Without this, the ?
operator's relayStopStatus would lose its RETURN/CONTINUE status when TCO
replaces frame->call. The RETURN handler now checks both frame->call and
frame->savedCall.

Add continuation introspection methods: frameCount, frameStates,
frameMessages. Add IoEvalFrame_stateName() for human-readable state names.

29/29 C tests, 230 Io tests (3 pre-existing failures only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement Continuation asMap method that produces a structured Map
representation of the captured frame stack. Each frame is serialized
with its state machine state, message code (reparseable), flags,
argument evaluation state, object type info, block locals slots,
Call stop status, and control flow parameters (loop counters, branch
messages, etc.).

Also add IoEvalFrame_stateName() for human-readable state names,
and Continuation introspection methods: frameCount, frameStates,
frameMessages. These are building blocks toward full continuation
serialization/deserialization.

30/30 C tests, 230 Io tests (3 pre-existing failures only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert IoEvalFrame from a manually-allocated C struct to a GC-managed
IoObject. Frames are now collector-tracked, enabling grab-pointer
continuation capture (no deep copy) and simplified coroutine marking.

Phase 0 - Mechanical rename:
- Introduce FRAME_DATA(frame) macro and IoEvalFrameData typedef
- Convert all frame->field to fd->field across 19 source files

Phase 1 - IoEvalFrame as IoObject:
- typedef IoObject IoEvalFrame; data payload behind IoObject_dataPointer
- Proto/tag/mark/free lifecycle following IoCall pattern
- Continuations use grab-pointer capture; copy method for snapshots
- IoCoroutine mark simplified to single IoObject_shouldMarkIfNonNull
- Remove manual frame pool from IoState; remove multiShot from continuations
- Exclude dead IoState_iterative_fast.c from build

Phase 2 - Performance recovery:
- Add 256-entry IoObject frame pool to IoState
- pushFrame_ reuses pooled frames (memset reset); popFrame_ returns to pool
- Pooled frames marked in IoCoroutine_mark to survive GC
- 6x speedup recovered (23.85s -> 3.65s on 1M for-loop benchmark)

30/30 C tests pass, 230 Io tests (3 pre-existing failures only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ction

Fix WeakLinkTest regression: block activation now brackets with a retain
pool (push before IOCLONE, pop on return/error unwind) so blockLocals
and intermediate objects are released from ioStack, allowing GC to
collect unreferenced WeakLink targets. TCO clears the pool top each
iteration to prevent accumulation during deep tail recursion.

Fix IoList atInsert/removeAt: add errorRaised checks after argument
evaluation to prevent operating on invalid state when errors occur.

Add Io-level frame introspection (Phase 3): EvalFrame exposes message,
target, locals, state, parent, result, depth, call, blockLocals, and
description methods. Coroutine gains currentFrame for stack walking.

30/30 C tests, 239/239 Io tests (was 230, +9 EvalFrame tests).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audit and fix CFunctions that call multiple IoMessage_locals_*ArgAt_
without checking errorRaised between calls. If the first argument
evaluation fails, subsequent calls proceed with invalid state.

Fixed sites:
- IoMap: at, atPut, atIfAbsentPut
- IoSeq_mutable: atInsertSeq, insertSeqEvery, removeSlice,
  leaveThenRemove, atPut
- IoSeq_immutable: exclusiveSlice, inclusiveSlice
- IoNumber: asString, between, clip
- IoObject: protoSet_to_ (setSlot), protoSetSlotWithType

Also: guard EvalFrameTest.io for master compatibility, update TODO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… reduced memset

- Skip body frame push/pop for cached literal loop bodies (nil, numbers, strings)
  by running remaining iterations in tight C for(;;) loops inline
- Add goto labels for common message path (START→LOOKUP→ACTIVATE→CONTINUE_CHAIN)
  and loop return fast paths (RETURN→FOR_AFTER_BODY, FOREACH_AFTER_BODY, etc.)
- Replace full memset in pushFrame_/popFrame_ with selective field initialization
- Gate ISEVALFRAME/ISMESSAGE validation behind DEBUG_FRAME_VALIDATION
- Expand number cache from [-10,256] to [-10,1024]
- Add BODY_IS_CACHED_LITERAL/CACHED_LITERAL_RESULT macros to IoEvalFrame.h

Nested for loops 66x faster than master (tight loop bypasses eval loop entirely).
Single for loops ~10% faster. Method calls ~9% faster.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… lookups

- Add IoObject *inlineArgs[4] to IoEvalFrameData: messages with ≤4 args
  use this inline buffer instead of heap-allocating argValues, eliminating
  io_calloc/io_free per message send (~2M calls saved for 1M-iteration loops)
- Remove dead special form scan from FRAME_STATE_START (17 pointer
  comparisons per message that set a variable immediately discarded)
- Cache isSpecialForm flag (0/1/2) on IoMessageData: computed once per
  unique message, subsequent calls are a single byte comparison instead
  of scanning 18 symbol pointers
- Add monomorphic inline cache on IoMessageData: caches last
  (tag, slotValue, slotContext, slotVersion) for proto-chain lookups.
  Repeated sends to same-type targets skip the proto chain walk entirely.
  Global slotVersion counter on IoState invalidates caches on setSlot,
  updateSlot, removeSlot, and proto manipulation (not on local var updates)
- Replace controlFlow memset in popFrame_ with fd->state = FRAME_STATE_START
  (1 int write vs 80-byte memset; mark function uses state to dispatch)
- Mark inline cache entries in IoMessage_mark for GC safety

Benchmarks vs previous commit:
  for(i,1,1M,i+1): 14.2s (was 16.0s, -11%)
  500K method calls: 5.3s (was 5.9s, -10%)
  for(i,1,1M,nil): 3.5s (was 3.5s, same — tight loop dominates)
  nested for: 0.03s (same — already at hardware limit)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable COLLECTOR_RECYCLE_FREED: CollectorMarkers are now recycled
through the freed list instead of being malloc'd/free'd each time.

Combined data+protos allocation: IoObject_justAlloc now allocates
IoObjectData and the 2-pointer protos array in a single io_calloc
call. appendProto/prependProto detect inline protos and allocate
a separate array when growing. IoObject_dealloc checks for inline
protos before freeing.

Inline Number allocation: IoState_numberWithDouble_ bypasses IOCLONE
entirely for uncached numbers. Directly calls Collector_newMarker
and sets up the IoObjectData fields, avoiding pushCollectorPause/
popCollectorPause (which can trigger GC sweeps), tag function
dispatch, and double Collector_addValue_ calls. Data blocks are
recycled via a freelist in IoState (up to 512 blocks).

Benchmarks (vs master):
- for(i,1,1M,nil): 3.38s → 0.05s (68x faster)
- for(i,1,1M,i+1): 14.87s → 0.11s (135x faster)
- 500K method calls: 5.84s → 2.0s (3x faster)
- 500K accumulator: 5.38s → 0.09s (60x faster)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reuse blockLocals objects across block activations instead of cloning
a new localsProto each time. Pool of 1 entry is returned on block
RETURN and reclaimed on next activation (PHash cleaned, slots rebound).

Key fixes for correctness:
- IoState_stackRetain_ pooled blockLocals after taking from pool,
  preventing GC sweep during IoCall_with allocation
- Do NOT pool blockLocals in TCO path — Call object references old
  blockLocals as sender, and relayStopStatus needs its slots alive
- Mark pooled blockLocals in IoCoroutine_mark for GC safety
- Cache Call tag/proto on IoState for inline allocation

Also adds fast path for trivial method bodies (single cached literal
like nil/true/number): skip entire block activation and return the
cached result directly.

Benchmarks (500K iterations, release build):
  Trivial method:     7.5s master → 0.8s stackless (9.4x faster)
  Method with args:  20.0s master → 2.0s stackless (10x faster)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reuse Call objects across block activations instead of IOCLONE per call.
Profiling showed 98% of method call time was in IoCall_with → IOCLONE →
GC sweep. Pool eliminates both the Collector_newMarker allocation and
the cpalloc of IoCallData.

Pool mechanics:
- CALL_POOL_MAX=1 (single pooled Call, covers tight loops)
- RETURN handler returns Call to pool with fields cleared to ioNil
- activateBlock_/activateBlockTCO_ take from pool, set fields directly
- Pooled Call is IoState_stackRetain_'d for GC safety between pool
  removal and frame attachment
- IoCoroutine_mark marks pooled Calls for GC traversal

Benchmarks (Release, vs master):
- 500K method(1+2):     6.34s → 0.19s  (33x faster)
- 500K method(y, y+1):  13.52s → 0.28s (48x faster)
- for(i,1,1M,nil):      4.01s → 0.08s  (50x faster)

30/30 C tests, 239 Io tests (3 pre-existing DirectoryTest failures).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the condition result is already ioTrue, ioFalse, or ioNil,
skip pushing a frame to send asBoolean (which just returns the
same value). Saves a frame push/pop + 4 state transitions per
if/while condition evaluation when the condition uses comparison
operators (<=, ==, !=, etc.) which return boolean singletons.

fib(30): 2.83s -> 2.70s (~5% faster)
while(i < 1M, ...): 9.52s -> 0.61s (15.6x vs master)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pool size 1 only benefits tight loops where the same method is called
repeatedly. Size 8 also covers recursive patterns (like fib) and
nested method calls where multiple block activations are in flight.

Benchmarks:
- fib(30):   2.70s -> 1.83s (1.5x faster)
- Test suite: 9.5s -> 7.1s  (25% faster)
- Tight loops: unchanged (0.19s, 0.28s)

Memory cost: 7 extra pointers per pool = 112 bytes total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use PHash_at_put_ directly for for-loop counter variable (bypass
  IoObject_setSlot_to_ overhead: createSlotsIfNeeded + isDirty flag)
- asBoolean skip now uses goto to batch IF_CONVERT_BOOLEAN ->
  IF_EVAL_BRANCH transition for boolean singletons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The monomorphic inline cache keyed on IoObject_tag(target) only, but
objects sharing the same tag can have different local slots. This caused
false.isTrue to return the cached Object.isTrue=true, breaking and/or
chaining (e.g. `true and true and false` returned true instead of false).

Added an O(1) PHash guard on cache hit to verify no local slot shadows
the cached proto-chain result. Fixes DirectoryTest path traversal
(.. filter), CoroTest failures, and all and/or chaining.

Also includes for-loop counter Stack_pop and RC infrastructure (gated
behind disabled COLLECTOR_USE_REFCOUNT).

239/239 Io tests now pass (was 236/239).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RC layer for the garbage collector, gated behind COLLECTOR_USE_REFCOUNT.
When enabled, promptly reclaims short-lived objects (especially for-loop
counter Numbers) via refcount tracking and drain, keeping the freed list
populated and avoiding calloc. Mark/sweep GC remains as backup for cycles.

GC library changes:
- refCount field on CollectorMarker (conditional on ifdef)
- RC increment in Collector_value_addingRefTo_
- RC decrement and enqueue in Collector_value_removingRefTo_
- Deferred free list (rcEnqueue/rcDrainFreeList) with safety guards
- inSweep flag to suppress RC frees during sweep phase

IoVM integration:
- IOUNREF macro for explicit reference release
- Slot overwrite decrement in IoObject_inlineSetSlot_to_ (no drain)
- Cascading decrement in IoObject_willFree via PHASH_FOREACH
- Bounded memory growth check in Collector_newMarker

Benchmarks with RC enabled: 1.5x faster on for(i,1,1M,i+1), ~7%
regression on method-heavy workloads. Disabled by default; enable
with `#define COLLECTOR_USE_REFCOUNT 1` in CollectorMarker.h.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comprehensive report covering goals, architecture, new language features
(continuations, frame introspection, portable coroutines), performance
optimizations (object pooling, inline cache, TCO, fast paths), optional
hybrid RC, and benchmark comparisons vs master.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The iterative evaluator pre-evaluates message arguments for performance,
but special forms (if, while, foreach, etc.) need lazy evaluation. The
previous approach used a hardcoded list of ~20 symbol names, which failed
for aliases like `false.elseif := Object getSlot("if")` — the CFunction
was `if` but the message name `elseif` wasn't in the list, causing args
to be pre-evaluated (broke REPL launch).

Now each CFunction carries an `isLazyArgs` flag set once at VM init.
Since `getSlot` returns the same CFunction object (not a clone), any
alias automatically inherits the flag. This also fixes `./io` launching
without arguments (the if/then/elseif chain in Z_CLI.io).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
captureFrameStack_ was grabbing a pointer to the live frame chain.
After normal return from callcc, popFrame_ zeroes frame data, making
deferred and multi-shot invocations crash. Now captures a deep copy
at callcc time, so continuations work regardless of when they're invoked.

Updated IoContinuationsExamples.md: removed references to unimplemented
setMultiShot, added deferred invocation and working multi-shot examples
using copy. Updated StacklessReport.md to reflect isLazyArgs and
continuation API changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement resumable exception system coexisting with non-resumable raise.
signal(error) finds a matching handler and calls it as a regular function;
the handler's return value becomes signal's return value at the signal site.
withHandler evaluates the body directly via doMessage (no child coroutine),
avoiding a known interaction between continuation capture and nested C eval
loops. All changes are Io-level (A4_Exception.io), no C modifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
callcc exposes undelimited continuations that deep-copy the frame stack
and can rewind past cleanup code. No Io-level code depends on it, and
the resumable exception system (signal/withHandler) doesn't use it.
Disabled by default; enable with -DIO_CALLCC at build time.

Add docs/Exceptions.md with design notes comparing Io's resumable
exceptions to Common Lisp's condition system and Smalltalk's on:do:,
and outlining possible directions (restarts, handler decline,
interactive restart selection in REPL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renamed IoContinuationsExamples.md to IoStacklessExamples.md. Removed
callcc/continuations section (being removed). Added sections for TCO,
coroutines, scheduler mechanics (yield/pause/resumeLater), async I/O
pattern, actor model, frame introspection, and exception handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
restoreState_ was walking the entire frame chain to recalculate
frameDepth on every coroutine switch. Save it alongside the frame
pointer instead, making switches O(1).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stackless branch replaced C stack switching (assembly, ucontext,
setjmp/longjmp) with heap-allocated frame swapping. This library is
no longer built or referenced.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
stevedekorte and others added 2 commits March 4, 2026 17:18
The CMakeLists.txt IO_SRCS list already controls load order explicitly,
making the A0_/A1_/B_/Y_/Z_ prefixes redundant. Rename for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve CLAUDE.md conflict: keep detailed stackless version over
generic auto-generated version from master.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stevedekorte stevedekorte merged commit 7e98323 into master Mar 5, 2026
@pedro-w
Copy link
Contributor

pedro-w commented Mar 5, 2026

This looks amazing! 🚀

@stevedekorte
Copy link
Member Author

Thanks Peter. Btw, would you like to chat about some plans for the future of Io? I've got Claude working on some major changes.

@pedro-w
Copy link
Contributor

pedro-w commented Mar 6, 2026

Sounds intriguing! Would love to hear what you've got planned.

I found that the current master branch does not work when compiled with Microsoft Visual C on Windows. I've got some fixes for this but I am away this weekend so hopefully on Sunday I can make a PR for you to have a look at.

I also did a bit of an overhaul on the CMake scripts; basically CMake has got better since the build system was originally written, so some of the more complicated things can be done more simply now.

@stevedekorte
Copy link
Member Author

Next step is making WASM the only target platform:
https://github.com/IoLanguage/io/blob/wasm/agents/WASM.md
https://github.com/IoLanguage/io/blob/wasm/agents/WASM_PLAN.md
Is this crazy? lol

@pedro-w
Copy link
Contributor

pedro-w commented Mar 7, 2026

That was not what I was expecting! (I thought maybe some kind of JIT compiler?) I don't know much about WASM, it seems to have been almost ready for the big time for quite a while. What's the plan on GC. I know it's been possible to compile code with emscripten but the garbage collection is not part of the runtime engine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants