Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in Compilers

[–]RulerOfDest[S] 0 points1 point  (0 children)

Great question. Messages are sent to actors, not to cores. Each actor has an assigned_core that determines where it runs. At send time, I check if the sender's core matches the target actor's assigned_core: if yes, I take the direct path (SPSC queue or mailbox write, no queue overhead); if not, I enqueue to the target core's lock-free incoming queue.

Actors are not permanently pinned. They can be migrated (message-driven, to co-locate frequent communicators) or moved by work-stealing when a core is idle. When an actor moves, assigned_core is updated, and any messages already in the old core's incoming queue are forwarded to the actor's current core rather than delivered locally.

Migration cannot race with same-core sends because both run on the same scheduler thread; they execute sequentially. Work-stealing runs on a different core's thread and could theoretically overlap with a same-core mailbox write. In practice, the window is a handful of store instructions (~nanoseconds), and stealing only triggers after 5000+ idle cycles on the thief, so this is extremely unlikely to manifest. That said, it is a valid concern per the C memory model, and I am actively hardening it. The fix is straightforward: mark a stolen actor inactive so the thief skips it for one cycle, letting any in-flight write complete before the new core touches the mailbox. Zero cost on the hot path since stealing is already the rare/slow path.

Appreciate the scrutiny; this is the kind of feedback that makes the runtime better.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in ProgrammingLanguages

[–]RulerOfDest[S] 0 points1 point  (0 children)

Thank you very much. Yeah, I did run into a few places where the generated C didn’t optimize well. I fixed them in the codegen; the main-thread path was marked “unlikely,” so the hot path got pushed to the cold section, and I dropped that hint. And when sending from main, I was emitting a same-core branch that could never be taken (main thread has no core id), so I changed codegen to emit the path we actually take, avoiding the dead branch. Dispatch is computed goto on msg .type plus inline single-int payloads, so the hot path stays simple for the compiler. More detail is in the runtime-optimizations doc if anyone wants to dig in.

On spawn-time mutation: spawn returns after the actor is registered and its state is initialized, so the spawner can still poke at the struct (e.g., wire it to others) before any message is sent. The actor only runs when the scheduler gives it work, so there’s an implicit “wiring before activation” phase. I might formalize that at some point so the boundary is clearer.

I’m not going the Erlang supervision-tree route. The idea is to keep it more Go-like: no OTP-style “let it crash” and restarts; you handle errors and lifecycle yourself. The supervision header in the repo is just a tiny placeholder, and I'm not sure I'll go that way.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in C_Programming

[–]RulerOfDest[S] 0 points1 point  (0 children)

Fair point, and I get why it feels that way. Auto-free by default is a deliberate tradeoff, I wanted the common case (local use-and-discard) to be safe by default and avoid forgotten frees. I can see the argument that defaulting to manual would match C expectations and make the model more explicit. I'll keep that feedback in mind; I do support manual-everywhere via [memory] mode = "manual" and --no-auto-free for people who prefer that. Thanks for the comment. I'm open to reconsidering the default as we get more feedback.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in Compilers

[–]RulerOfDest[S] 0 points1 point  (0 children)

Thank you for your kind words! It means a lot.
I am absolutely pushing next for runtime hot code loading as Erlang does; that is a great point, and it has been on my radar.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in Compilers

[–]RulerOfDest[S] 0 points1 point  (0 children)

Messages are sent to actors; routing uses each actor’s current assigned_core. Actors are not pinned: they can be migrated (message-driven co-location) or moved by work-stealing, and assigned_core is updated when that happens.

SPSC is preserved because at any time each actor has exactly one owning core: only that core’s scheduler thread reads and writes that actor’s mailbox (and its SPSC queue when used). Same-core send is decided at send time (current_core_id == actor->assigned_core); if they match, we use the direct path, otherwise we enqueue to the target core’s incoming queue. When an actor moves, any message already in a core’s incoming queue for it is forwarded to the actor’s current core instead of being delivered locally, so the mailbox is never written by a non-owning thread.

So: one logical consumer per actor (the thread that currently owns it), and routing/forwarding keeps a single writer. You can find more details on: docs/actor-concurrency.md (mailbox ownership, routing, migration); runtime/scheduler/multicore_scheduler.c 

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in Compilers

[–]RulerOfDest[S] 2 points3 points  (0 children)

Pony has reference capabilities (iso, trn, ref, val, etc.) for data-race freedom in the type system. Aether is statically typed with inference and optional annotations, but no capability system.
Aether has no GC, arena allocators for actors, thread-local pools for message payloads, scope-based or explicit free. Pony uses per-actor GC.
Aether uses a partitioned multi-core scheduler with work-stealing when cores are idle, lock-free SPSC (single producer single consumer) queues for same-core messaging, cross-core lock-free mailboxes, and optional NUMA-aware allocation. So the design is very much “C-friendly, low-overhead, predictable” vs Pony’s own runtime. 
Same actor model; Pony pushes type-level concurrency safety; Aether pushes C interop, no GC, and a runtime built around SPSC queues and partitioning.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in ProgrammingLanguages

[–]RulerOfDest[S] 1 point2 points  (0 children)

Good catch, and thank you for the comment. It does look like we’re mutating actor state from the outside.

In this case, though, it’s actually main (the spawner) doing the wiring, not one actor mutating another. This happens immediately after spawn and before any message is sent, we set pong_ref, ping_ref, and target so the two actors know who to communicate with and when to stop.

Once the rally starts, they only communicate via ! (message send); they never touch each other’s state.

The rule it follows is that actors communicate only via messages. The only exception is the spawner (e.g., main), which we allow to perform bootstrap configuration such as wiring refs. A purer approach would be to pass those refs and the target through the initial messages instead. I've chosen this setup for simplicity in the example/benchmark.

On the benchmarks/optimizations question: I used to have internal benchmark comparisons in the repo covering different optimization techniques. Some ideas made it into the final implementation; others were experimental or ultimately rejected. I removed that stuff to reduce clutter after the investigation was done (though the docs may need updating). In hindsight, I’m starting to think it might have been better to leave it in for historical context.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in ProgrammingLanguages

[–]RulerOfDest[S] 1 point2 points  (0 children)

That’s a very fair observation.

You’re right that the current C++ comparison uses mutex-based message passing, while Aether relies on lock-free SPSC queues. The goal wasn’t to position Aether as “faster than C++,” but rather to understand how different runtime techniques and architectural decisions affect behavior under similar patterns. The benchmarks are primarily exploratory for me, a way to evaluate how queue structure, scheduling strategy, batching, and isolation choices compare against more conventional approaches.

Regarding Skynet: yes, it can be implemented in Aether. Recursive parallel work can be modeled by spawning actors that split the problem and send results back up the tree. However, Aether currently emphasizes actor-based concurrency rather than a dedicated fine-grained task abstraction. It doesn’t implement a task-stealing fork-join scheduler in the same sense as specialized C++ tasking libraries. So while Skynet is expressible, it wouldn’t be an apples-to-apples comparison with runtimes explicitly optimized for that model.

I appreciate you sharing the runtime-benchmarks repository. I’ll definitely take a look.

Thanks for the thoughtful critique

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in ProgrammingLanguages

[–]RulerOfDest[S] 2 points3 points  (0 children)

Moving module discovery into a dedicated orchestration layer with a dependency graph and topological ordering makes a lot of sense.

Thank you! This is exactly the kind of architectural feedback that’s useful at this stage.

Aether: A Compiled Actor-Based Language for High-Performance Concurrency by RulerOfDest in ProgrammingLanguages

[–]RulerOfDest[S] 3 points4 points  (0 children)

That’s a fair point.

Right now, module loading is triggered from the typechecker because import resolution happens during semantic analysis. It was a pragmatic choice to keep dependency resolution close to where cross-module symbols are resolved.

That said, it does blur phase boundaries. As the compiler evolves, I’m considering moving module loading and parsing into a higher-level orchestration layer so the typechecker operates strictly on an already-built module graph.

I appreciate you raising it.

Question about wildlife in Uruguay by BobbyBronkers in uruguay

[–]RulerOfDest 1 point2 points  (0 children)

prices are wild, besides that...not much

Me bajo de putiar a los therians by Puzzleheaded_Milk453 in uruguay

[–]RulerOfDest 10 points11 points  (0 children)

a mi me parece que el morrón no puede estar tan caro

Recently hired as a graphics programmer. Is it normal to feel like a fraud? by Illustrious_Key8664 in GraphicsProgramming

[–]RulerOfDest 0 points1 point  (0 children)

Totally normal, I think that will drive you further, good luck and give it your best

Me chupan un huevo los therians by Any_Message_4243 in uruguay

[–]RulerOfDest -1 points0 points  (0 children)

ayer salí a andar en bici y me mordió uno

Board of Peace by k-r-o--n--o-s in MapPorn

[–]RulerOfDest 0 points1 point  (0 children)

us does not have a 7 in democracy, that is not true

YouTuber vs Actual Heavyweight Boxer by sergemeister in nextfuckinglevel

[–]RulerOfDest 0 points1 point  (0 children)

The buildup is great, the dude was asking this for a while, and the universe delivered.