Repository navigation
Releases: dj-lumiere/RazorForge
Release list
RazorForge v0.4.0
RazorForge v0.4.0
The realm release. v0.2.0 shipped the execution model, v0.3.0 the communication layer. v0.4.0 is a
language-maturity release built around one idea that turned out to connect everything else:
realms. A realm is the namespace a name lives in — RF:: for RazorForge, C:: for foreign C,
LLVM:: for intrinsics, and SF:: for the new sister language Suflae. Foreign calls, compiler
intrinsics, and cross-language interop all became one mechanism instead of three ad-hoc ones.
On top of that foundation this release lands a reworked ownership & copy model, a compile-time
reflection surface, a matured C FFI (struct-by-value including float structs, platform-width C
types, conditional compilation), and native SIMD vectors. Suflae ships as its own v0.1 preview
release alongside this one — see the Suflae release notes.
🌐 Realms
Foreign and intrinsic calls are now realm-qualified rather than string-tagged.
routine C::malloc(...)/routine LLVM::...replace the oldexternal("C"|"llvm")form. A
realm is part of a name's identity, not a comment on it — so a foreign name resolves, mangles, and
type-checks like any other.- Realm-qualified references in expression and type position —
RF::Core.List,C::size_t— so a
program can name a type or routine from a specific world when it matters (mixed RF/SF, FFI boundaries). - Strict-realm checking at foreign call sites (RF-S460): crossing a realm boundary is deliberate,
never accidental. - Realms are the substrate the whole Suflae "world-line" model rides on: SF's
ListisRF::Core.List,
wrapped per-instance so it obeys Suflae's sharing semantics without forking the standard library.
🔗 C FFI
The FFI surface matured enough to bind real C libraries.
- Struct-by-value, including float structs. Aggregates pass and return by value following each
platform's ABI — per-eightbyte SSE/INTEGER classification (SysV x86-64), HFA (AArch64/AAPCS64), and
by-size GP-register packing (Windows x64). Struct layout matches the C default, so aHijacked[record]
hits the right field offsets (flat and nested). - Platform-width C types:
CLong/CULong/CWChar(per-target via@target) andCWStr
(wchar_t*), plus the existingCStrforchar*. - Conditional compilation:
@target(os: "linux", "macos")gates whole files and declarations
(Go-style build constraints), so per-platform bindings live side by side. - Library linking from the manifest:
[target] c_libraries/library_paths(clang-l/-L), and a
@link("lib")annotation onC::externs, threaded into the link step. - Callbacks (bare routine name → C function pointer), opaque pointers, typed pointers (
Hijacked[T]),
andchoice → int32all work and are exercised end-to-end.
🧬 Ownership & copy model
The value/copy rules were reworked into one coherent model.
CopyableandStorableare orthogonal. Being copyable (duplicable) and being storable (movable
into a slot) are separate capabilities on separate axes —recordtypes auto-deriveCopyablethrough
aneeds P everywherestructural gate, while reference-counted handles are storable but not copyable
(a duplicate is an explicit.share(), never an implicit copy).- Three-rules parameter model: a
recordparameter borrows; a value moved to a destination
(constructor field, store primitive) transfers; the caller tears down rvalue arguments. Container and
aggregate element reads copy on keep (var x = a[i]copies), and taking a bare entity out of a
container without a copy is a compile error rather than a silent alias/double-free. - Construction verbs are gone. The three entity→wrapper "constructors" that masqueraded as methods
(.retain(),.share[P](),.roam()) are abolished — you writeRetained(from: steal n)/
Roamed(from: n), and the definition reads as the type. - One canonical RC vocabulary:
share(mint a co-owner),hold/unhold(strong count),
observe/unobserve(weak count),access(read-only coercion),control(deref),destroy
(which now folds in the oldrelease). Aggregate-steal holes (steal l[i],steal o.field) are
rejected (RF-S622).
🪞 Buildtime reflection
A first buildtime reflection surface, for serialization, FFI layout, and GPU-vertex-style codegen.
- Member listings:
openmemvarof(T)/allmemvarof(T)enumerate a type's members at build time. - Metadata intrinsics:
nameof/orderof/typeof/placeof(offset) /sizeof/valueof
of a member, with full repr-C offset and size folding. - The
$primarysplice injects a reflected member as code (me.$nameof(m)→me.x), andexpand m in openmemvarof(T)unrolls a body per member at monomorphization. - SoA collections:
SplitList/SplitArraystore a record's fields as parallel arrays, gated on
needs T is SplittableType. - Buildtime-value const generics (
${...}) for sizes and counts. represent/diagnose/serializeare now universal built-ins (every type has them) rather
than opt-in protocols — theRepresentable/Diagnosable/Serializableprotocols are removed.
🔢 Numerics & SIMD
- Native SIMD vectors:
Vector2D/Vector3D/Vector4D(F32lanes) with elementwise
arithmetic, reductions, and geometry helpers, lowered to hardware vector ops. - Rational types
Q64/Q32for exact fractional arithmetic.
✍️ Naming & ceremony
Wide renames that make the surface read as intended. Same programs, clearer words.
- A generic parameter's identity is its slot, not its name. A user type whose name collides with a
stdlib generic's parameter (record TvsList[T]) no longer breaks that generic — the whole
record T/record N/record Mcollision class is fixed. @migratable→@reshaping(the container-mutation marker driving the each-loop reshaping ban).Field→MemberVariable,Method→MemberRoutine,Copy→Store,Blank→None.- Routine names are canonically bare: failability (
!) and type arguments ([...]) are structured
attributes, not part of the name string.
🧹 Under the hood
Internal-only, but load-bearing for correctness and future speed.
- Carriers lowered to records.
try_/check_/lookup_results (Maybe/Result/Lookup)
are now ordinary record constructions produced by a lowering pass; the codegen special-cases for
carrier construction and payload layout are deleted (#carrieris 0 across the whole program). Wide
payloads (U128/D128/ big records) are stored at full width instead of being pointer-truncated. - Monomorphization completes before codegen. Type substitution, RC retain bumps, Roamed locks, and
roam/free hook wiring all moved out of the code generator into explicit passes — codegen no longer
substitutes types or encroaches on stdlib layout, and errors instead of silently falling back. - Scope teardown is emitted before the terminator (spilling the returned value to a temp), fixing a
class of temporary-destroy leaks, and trivial destroys are skipped.
✅ Tests
Full suite green — 1,511 unit + analyzer tests and the single-build stdlib harness (188
fixtures, including the C-FFI, ABI struct-by-value, buildtime-reflection, realm, and Suflae-equivalence
fixtures). CI green on Linux, macOS, and Windows.
🔮 Suflae arrives
Suflae — the sharing-first sister language that hides RazorForge's memory-management surface behind a
biased-refcount Roamed runtime — ships its first preview as a separate sf-v0.1.0 release. The
same compiler binary runs it (suflae hello.sf, or any .sf file). See the Suflae v0.1.0 release notes
for scope.
⚠️ Not yet
Async networking remains unimplemented (async file I/O and subprocess orchestration are the async
I/O surface). Custom FFI calling conventions beyond ccc, @repr(C) / @packed / @align, and
callback-in-struct-field are not yet supported. The persistent-daemon / JIT fast-rebuild path is in
progress and not part of this release.
⬆️ Upgrading from v0.3.0
- Foreign routines: replace
external("C")/external("llvm")withroutine C::name(...)/
routine LLVM::name(...). - Construction: replace
n.retain()/n.share[P]()/n.roam()withRetained(from: steal n)/
Shared[T, P](from: steal n)/Roamed(from: n). - RC verbs:
retain/release→hold/unhold, weaktrack/watch→observe, the wrapper copy
verb →share,refer()→access(); a wrapper'srelease()is now justdestroy(). - Annotations & keywords:
@migratable→@reshaping; in your own compiler-adjacent code/prose,
Field/Method/Copy/Blank→MemberVariable/MemberRoutine/Store/None. - Protocols removed: drop
obeys Representable/Diagnosable/Serializable—represent/
diagnose/serializeare universal now. FastSet/FastDictare removed. UseSet/Dict(secure-by-default hashing); they are the
only hash collections now.
v0.3.0
RazorForge v0.3.0
The channels release. v0.2.0 shipped the execution model — coroutines, threads, and a single owned
Agent[T] handle over both. v0.3.0 adds the communication layer that was promised next: typed
channels for streaming values between concurrent work, a SignalCaster condition-variable monitor
for hand-built coordination, and an M:N work-stealing scheduler so coroutines actually run in
parallel across cores.
It also carries a ceremony pass over the language. A marker earns its keystrokes only when the
danger it guards is silent — so ownership transfer stays marked (steal, or your variable vanishes
under you) and memory-unsafety stays marked (dangerous, narrowed this release to only the ops that can
actually corrupt memory or race). Markers that merely restated something already loud or derivable
are gone: the failable ! is now optional (a failure crashes with a message on its own), the wired $
sigil is removed, and the redundant isonly flags operator is dropped.
📡 Channels
Typed streaming conduits between producers and consumers — the piece v0.2.0 explicitly deferred.
Sender[T]— the producer end; cloneable (sender.clone()), so many producers can feed one
channel (fan-in viasteal sender.clone()). Send withsend!(item:);close()/is_closed()
manage the lifecycle.Receiver[T]— a single-consumer receiver, directly iterable (obeys Iterable[T]).SharedReceiver[T]— a multi-consumer (MPMC) receiver for worker-pool patterns, also iterable
and cloneable.ChannelDrain[T]— the emittable iterator (emit) that iteration drains a receiver through.- Factories:
make_channel[T](capacity:) -> (Sender, Receiver)and
make_shared_channel[T](capacity:) -> (Sender, SharedReceiver). send!is failable — a closed channel is a marked failure you handle withwhen/try_, never
a silent drop. Bounded capacity gives natural backpressure: a full channel parks the producing
coroutine (or blocks the producing thread) until a consumer makes room.
📶 SignalCaster
A condition-variable monitor with its own internal mutex, for coordination patterns channels don't cover.
lock/unlock/wait/wait_within(deadline)/cast_one/cast_all/clone.waitis uncolored — a coroutine parks, a thread blocks, same call site (the same contract as
retrieve!andwaitfor).wait_withinadds a timed variant.- Predicate-style waits and timeouts are covered end-to-end.
⚙️ M:N work-stealing scheduler
The v0.2.0 scheduler was a per-thread, caller-driven event loop — a coroutine only advanced while its
owning thread was inside an await. v0.3.0 replaces it with a process-global pool of N daemon worker
threads (N = host cores by default) with per-worker work-stealing, so independent coroutines make
progress on multiple cores at once.
- Per-worker local deques (owner pushes/pops one end, idle workers steal from the other) plus a
shared injector queue for off-pool spawns and wakes. RF_WORKERSenvironment knob pins the worker count (RF_WORKERS=1for deterministic,
single-worker execution); otherwise it tracks host cores.- A worker-safe park/wake state machine keeps a wake that races a park from ever resuming a
coroutine on two workers at once, and the deadlock detector is N-aware — it only flags a genuine
stall once every worker is idle with work outstanding. - Coroutines may migrate between workers across a park; single-thread-only access tokens
(Viewing/Modifying) and bare entities are held to their thread, while the thread-shareable set
(Atomic/Shared/Watched/Inspecting/Claiming) may cross.
🛡️ Thread-crossing soundness
Passing a value into a threaded or suspended routine now checks at compile time that it is safe
to share across the boundary (RF-S632). A bare, single-owner entity may cross only when moved with
steal (the move is provably exclusive); shared-ownership handles must cross as Shared/Watched.
Single-thread tokens are rejected at the boundary rather than racing at runtime.
✍️ Less ceremony
The language cleanup that ships alongside channels — same programs, fewer required sigils.
- Constructors read as the type.
routine T(from: X)replacesroutine T.create(...); you write
Point(x: 1, y: 2)/S64(from: n)and the definition site matches the call site. The internal
createname is gone from the surface. - The failable
!is optional. A routine's failability is inferred from its body (throw/
absent), and a bare call to a failable routine propagates the crash on its own — no more threading
!through every declaration and call site just to restate that a failure is possible.!still
reads fine where you want it explicit (send!,retrieve!), and recovery is unchanged: the
generatedtry_→Maybe[T],check_→Result[T], andlookup_→Lookup[T]variants plus
whenare how you handle a failure instead of crashing. - The wired
$sigil is removed. Operator hooks and lifecycle methods are plain names (add,eq,
iter,destroy, …); a method's wired-ness is inferred from the protocol its owner obeys, and
a + bnow requires the type to actually implement the operator's protocol (a missing hook is a
compile error, not a codegen surprise). dangerousnarrowed. The unsafe gate now sits only on operations that genuinely deref raw
pointers, hand-manage memory (destroy), or touch concurrency-fatal primitives — not on every
token-passing container routine.dangeris also a plain keyword now (wasdanger!).flags isonly X→flags == X. One redundant keyword removed; the codegen was already identical.
📦 Prefix / package import
import A/Bnow pulls in every submodule underA/B. A single import brings in every module
whose declared path is a strict descendant (A/B/Sub,A/B/Sub/Deep, …), instead of oneimport
line per module. Resolution keys on the declaredmodulepath, not the directory layout.- Each submodule's leaf stays callable leaf-qualified (
XxxApi.start()); a cross-module leaf clash
surfaces as a compile error (RF-S513), disambiguated by importing the specific module. (Multi-segment
call-site qualification likeFoo/Alpha.greet()is not spellable —/is division in expression
position.)
🩹 Runtime stability
- Per-thread coroutine context on the M:N pool. The stackful-coroutine backend's active-context
pointer is now correctly thread-local under the multi-worker scheduler, fixing an intermittent
crash that surfaced only once coroutines ran on more than one worker thread.
✅ Tests
Full stdlib end-to-end suite green — 163 fixtures, including the channel_* (backpressure,
fan-in, rendezvous, try-feed, worker-pool, introspection), signalcaster_* (predicate, timeout), and
coro_* scheduler fixtures (migration, parallel, work-steal) — alongside the analyzer and unit suites
(1,475 tests total). CI green on Linux, macOS, and Windows.
⚠️ Not yet
Async networking is still not implemented (async file I/O and subprocess orchestration from v0.2.0
remain the async I/O surface). The Suflae sister language is in progress and not part of this
release.
v0.2.0
RazorForge v0.2.0
The concurrency release. RazorForge gains a full asynchronous execution model — stackful coroutines, a
cooperative scheduler, OS threads, and a single owned handle that unifies both — plus structured
concurrency, subprocess orchestration, and async file I/O. The design goal throughout: concurrency you
write as straight-line code, with ownership and failure stayed explicit.
🧵 Concurrency model
suspended routineandthreaded routine. Calling one starts the work and hands back an
ownedAgent[T]— nospawnkeyword, the call is the spawn. Asuspendedroutine runs as a
stackful coroutine on this thread's implicit scheduler; athreadedroutine runs on an OS thread.
One handle type backs both, so a mixed set can be awaited together.agent.retrieve!()— uncolored await. Drives the work and returns its value. Inside a
scheduler-driven coroutine it parks (siblings keep running); on a plain thread it blocks. No
function coloring: the same call site works in either context.waitfor(duration)— uncolored timed wait (parks under the scheduler, sleeps on a thread), and
agent.waitfor(d).retrieve!()for a per-agent timeout.- Drop = abandon. An un-retrieved
Agentthat goes out of scope is cleanly torn down: a parked
coroutine unwinds its cancellation shadow stack; a running worker thread is joined then discarded.
🪢 Structured concurrency
A List[Agent[T]] is the scope — its ownership already guarantees no child outlives it. The scope
operations are member routines on that list:
agents.gather!()— drive all concurrently, return every result in input order, fail-fast.agents.race!()— drive all, return the first finisher's value; losers are abandoned.agents.cancel_all!()— request cooperative cancellation of every agent, then wait out the
wind-down. Cancellation is request-only and never frees (teardown stays at scope exit); an agent
observes it viacancellation_requested()(the only way to halt a worker thread, which cannot be
killed) or via an interruptiblewaitfor.
⚙️ Runtime
- Stackful coroutines backed by native fibers on Windows (
CreateFiberEx) and libco elsewhere, so
a coroutine can suspend from any call depth — including deep C-runtime calls likefopen. - Cooperative scheduler (ready FIFO + timer list) with cross-thread wake, the bridge that lets
a coroutine await a worker thread without blocking and lets parked work resume from any thread. - Demand-committed coroutine stacks — reserve large, commit on touch — so very many coroutines
coexist (≈14 KB resident per live coroutine, not 1 MiB); allocation failure raises a diagnosed
OutOfMemoryErrorinstead of crashing.
🔌 Subprocess & async I/O
run_process(command) -> ProcessResult— run an external program (shell), capturing stdout,
stderr, exit code, and signal, while parking the calling coroutine. Orchestrate programs
concurrently withgather!.- Uncolored file I/O —
read_text(path)andwrite_text(path, content)carry no_async
variant and no function color. Inside asuspended routinethe calling coroutine parks while a
vendored libuv loop on its own thread does the blocking transfer (siblings keep progressing);
outside one it runs inline. Same call site, either context — the same contract asretrieve!and
waitfor. For whole-file work prefer these to opening aFileHandleand calling the blocking
read_allinside a coroutine.
🛠️ Language & compiler
- Member routines on specialized generic receivers — e.g.
routine List[Agent[V]].gather!(),
wheremeis typed as the specialized receiver. This is what lets the structured-concurrency
operations live directly onList[Agent[T]]. - Named-argument evaluation order fixed — named arguments are bound to parameters by name (not
source order) across every call path; order-independent named calls now evaluate and bind correctly.
⚠️ Not yet
Honesty about scope: channels (typed streaming conduits) are designed but land in v0.3.0, and
async networking is not yet implemented. v0.2.0 is the execution model; streaming/communication
come next.
✅ Tests
Full stdlib end-to-end suite green — 146 fixtures across coroutines, threads, Agent/race!/
gather!/cancel_all!, subprocess, and async I/O — alongside the analyzer and unit suites (1615
tests total). CI green on Linux, macOS, and Windows.
v0.1.1
RazorForge v0.1.1
An optimization-and-cleanup release: faster decimal transcendentals (results unchanged, bit-for-bit)
plus a new DPD decimal interchange surface and dead-code removal.
⚡ Faster decimal transcendentals
Every speedup below is byte-identical to the previous result — validated by exact-match sweeps
against the full-precision path plus round-trip checks, and locked in by committed fixtures. The wins
come from sizing each decimal type's work to the precision it actually needs rather than the shared
softfloat engine's full width: D64 needs ~54 bits but runs on the 113-bit F128 engine; D128 needs 113
bits but runs on the 237-bit F256 engine.
Measured on an Intel Core i9-14900HX (release-time / LLVM -O3, 20k-iteration microbench).
Numbers are machine-specific and not comparable to the collection benchmarks in
internal-wiki/bench_results.md (different machine).
| routine | before | after | speedup |
|---|---|---|---|
D64 pow |
2614 ns | 1324 ns | ~2.0× |
D64 erf |
10198 ns | 5175 ns | ~2.0× |
D64 erfc |
9943 ns | 5199 ns | ~1.9× |
D64 tgamma |
6841 ns | 4451 ns | ~1.5× |
D64 atan |
3191 ns | 2427 ns | ~1.3× |
D64 asin |
3850 ns | 3157 ns | ~1.2× |
D128 erf |
16308 ns | 11176 ns | ~1.5× |
D128 erfc |
16385 ns | 11014 ns | ~1.5× |
acos/atan2 improve alongside asin/atan (shared core). pow now evaluates exp(you·ln me) at
F128 width instead of octuple; erf/erfc run the continued fraction at F128 width with the level
count sized to the target; tgamma, asin/atan reduce their Stirling / half-angle / Horner degrees
to the target precision. D32 already delegates to native libm and is unchanged.
✨ DPD decimal interchange
to_dpd()/from_dpd(bits:)onD32/D64/D128(→U32/U64/U128). RazorForge stores
decimals in BID (to_bits/from_bits); these convert to and from the IEEE 754-2008
densely-packed-decimal interchange encoding, for interoperating with DPD-based systems (IBM
POWER hardware decimals,decNumber, DPD wire/file formats).- Validated against the canonical IEEE/Speleotrove encodings (e.g. decimal64 −7.50 =
0xA2300000000003D0) and full round-trip across cohorts, signs, exponents, and Inf/NaN. - Codec tables live in
Standard/RazorForge/Core/Numerics/DPDTables.rf, generated by
scripts/gen_dpd.pydirectly from the canonical Cowlishaw BCD↔DPD equations — self-contained, no
external dependency.
🧹 Cleanup
- Removed dead references to the
rf_d32_to_f64/rf_f64_to_d32FFI (adecNumber-layout shim) now
thatdecNumberis no longer in the tree.
✅ Tests
- Added D64/D128 transcendental correctness fixtures (
tcheck_d64_transcendentalsextended,
tcheck_d128_transcendentalsnew) and a DPD encode/round-trip fixture (dpd_api). Full stdlib
end-to-end suite green (130 fixtures).
v0.1.0
RazorForge v0.1.0
The first release with threaded routines and a ground-up overhaul of the numeric stack. This is
the largest release since the initial alphas (134 commits since v0.0.4-alpha).
✨ Highlights
- Threaded routines (Phase 1–3). Concurrency with safety enforced at compile time: a
readers-writers conflict checker (RF-S630) that tracks controller identity across.share()
aliases, a thread-argument shareability gate (RF-S632), and a re-entrant exclusive-lock guard that
fails fast instead of deadlocking. - Numerics rebuilt. Dropped the
decNumber/TLFloatdependencies in favor of an in-house
softfloat engine pluslibbf. Checked arithmetic is failable by default — overflow, divide-by-zero,
and domain errors are now typed and must be handled, so numeric failure is explicit at the call
site rather than silent. - IEEE 754 surface.
total_order/total_order_mag, signaling vs quiet NaN,nextUp/nextDown,
decimalnormalize, and domain-vs-overflow separation in division across the floating-point and
decimal types. - Wider numeric API.
Decimal(full IEEE surface + 70-digit text parse),Integer
(modpow/divmod/bit ops),Real(rounding + selection vialibbf), andComplex/Cxx
(log2/reciprocal/to_polar). - Faster softfloat (measured, x86-64): F128 multiply 14.9 → 10.1 ns, D64 true-division
119.6 → 39 ns (~3×), andsqrt~3.3× faster — speedups that carry into the transcendentals built
on them. F16 now works correctly on x86-64.
🛡️ Correctness & robustness
- Fixed several double-free and memory-leak paths in record-copy lowering, synthesized destructors,
and owned-temporary teardown. - Codegen now emits only reachable routines, backed by an over-prune tripwire and a declare/define
signature-match invariant. - Deterministic, OS-independent method resolution (fixes Linux/macOS-only resolution differences).
- Bare member access (
x.name) now reads a field and no longer silently auto-calls a zero-arg method
— writex.name()to call.
📦 Packaging
- Tests moved to a dedicated project; the shipped compiler no longer carries test code or
test/tooling assemblies, slimming the distribution.
⚠️ Upgrading from v0.0.4-alpha (source-breaking)
Code written against v0.0.4-alpha may need these adjustments:
-
Checked numeric ops are now failable (
!). Checked fixed-width arithmetic, division, and the
overflow/domain-prone methods (e.g.divmod,modpow,logb/quantize/scaleb,tgamma) now
throw typed errors (NumericOverflow/DivisionByZero/NumericDomain). Callers must be
failable themselves, handle withwhen, or call the generatedtry_/check_variants.
Arbitrary-precisionIntegeradd/sub/mul stay non-failable (they cannot overflow); their division
family does not.# before var q = a.divmod(other: b) # after — handle the failure var q = a.divmod!(other: b) # in a failable routine var q = a.try_divmod(other: b) # or recover explicitly -
Bare member access no longer auto-calls a zero-arg method (RF-S450).
x.namereads a member
variable; call methods explicitly with().var p = cstr.ptr # before (silently called ptr()) var p = cstr.ptr() # after -
Error type rename:
ValueError/ParseError→InvalidValueError. Updatewhenarms and
any explicit references. -
Decimal.$subis now$sub!(subtraction can overflow). Decimal subtraction in non-failable
contexts must be handled. -
New concurrency static checks may reject previously-accepted code: RXW conflicts (RF-S630),
unshareable thread arguments (RF-S632). These reject data-racing patterns that compiled before but
were unsafe; re-entrant exclusive-lock acquisition now crashes (ReentrantLockError) rather than
deadlocking. The fix is usually to share throughAtomic/Shared/Watchedor restructure the
usingscopes.
✅ Platforms
Verified in CI on Linux x86-64, Windows x86-64, and macOS arm64 (Apple Silicon).
v0.0.4-alpha
What's new in v0.0.4-alpha
This release makes value types mutate in place. A method on a storage-backed
record now receives its me receiver by reference, so it can change the
caller's storage instead of a throwaway copy. It's a small surface change with a
large consequence: it's the foundation the upcoming concurrency tier (lock-free
atomics) and C FFI out-parameters are built on.
Language
-
Storage-backed record methods now take
meby reference. A method on a
value-type record can mutate its fields and the change is visible to the caller.
Previously the receiver arrived as a hidden copy, some.field = …(and
me.field.hijack()) operated on a temporary that was discarded the moment the
method returned — the mutation silently vanished. Now those operations reach the
caller's real storage, so in-place mutation works. This applies to every
storage-backed record:- struct records (records with no
@llvmbackend type), and - inline aggregate records —
Array[T, N]andBitArray[N], whose backend
is an inline[N x T]buffer.
The rule is purely type-level — there is no per-method or per-name special case.
Array.$setitem!is by-reference becauseArrayis storage-backed, exactly like
every otherArraymethod.- Behavioral note: any code that relied on the old "mutation is a no-op"
behavior will now see the mutation take effect. In practice that pattern was a
latent bug (a value-type method that appeared to mutate but didn't).
- struct records (records with no
-
Nested and indexed in-place mutation now works. Assigning through a field or
element chain mutates the target directly:me.inner.field = …andme.inner.field += 1(nested struct fields)arr[i] = xanda.b[i] = x(index assignment, including through a field)
Array/BitArrayindex assignment writes the single element in place — it no
longer rebuilds the whole buffer on every write. -
@llvm-primitive (scalar) records are unchanged. The numeric types
(S8…U128,F16…F128, the decimals and complexes),Bool,Hijacked,
Retained, and friends keep value-passing semantics — their value is the
machine register their operators feed to intrinsics, so they were never affected
by the copy problem and are untouched here. (Such types are pure values and never
mutatemein place, so "needs by-value" and "mutates in place" never overlap.)
Usewithwhen you want a fresh, independent copy of a value record.
Compiler fixes
- Address-of an rvalue receiver now works. Taking the address of (or calling a
by-reference method on) a temporary — a call result, a constructor expression, a
literal — now spills the value to a temporary and uses its address, instead of
failing with "cannot take address of expression". - Address-of through a crashable's fields is now supported (it previously
rejected crashable parents), so returning or copying aText/record field of a
crashable fromcrash_message/$diagnoseworks. - Address-of on inline aggregate records (
Array/BitArray) now generates
valid IR for the universalget_address/hijackmachinery, where it previously
emitted an illegal[N x T] → ptrcast.
Internal
- The dead
set_element_atintrinsic — superseded by the new in-place element
store path — was removed. - Storing an owned value through the in-place element store is now correctly
treated as an ownership move, so the stored value is no longer torn down twice.
Groundwork
- This is the prerequisite for two v0.1.0 features: value-representation atomics
(AtomicS64is one machine word likeS64, differing only in its atomic-only
API) and C FFI out-parameters (passing a struct's address to a C function).
Both require a method to reach its receiver's real storage, which is exactly what
by-referencemeprovides.
The change is locked by the full end-to-end fixture suite and the analyzer test
suite (1382 tests), all green, with every generated module verified by LLVM.
v0.0.3-alpha
What's new in v0.0.3-alpha
A bugfix release that closes three correctness gaps: a parser misread of
subjectless when, a memory leak on entity-variable reassignment, and protocol
conformance that ignored category membership.
Compiler fixes
- Subjectless
whenarms no longer misparse as lambdas. Awhenwith no
subject whose arm condition started with an identifier (e.g.x > 0) could
be mistaken for a lambda parameter list, producing a spurious syntax error.
Arm conditions now parse correctly. - Reassigning a plain entity variable no longer leaks. Assigning a new
value to an entity-typedvarnow destroys the previous contents first,
matching the teardown that already ran at end of scope. Branch- and
loop-driven reassignment no longer leaks the old value.
Language
- Records satisfy category protocols by membership. A record now conforms
to category protocols such asRecordTypepurely by being a record — no
explicitobeysclause required. Conformance is decided by category
membership rather than only by declared protocol lists.
All three fixes are locked by end-to-end fixtures that run green on every
commit.
RazorForge is a natively compiled, statically typed language built around
single-ownership memory management (no borrow checker, no GC) and
compiler-generated error handling. This is an early alpha: the compiler,
runtime, and standard library work — 1,400+ tests and 90+ end-to-end fixtures
run green on every commit — but APIs will change and you will find bugs.
Install & hello world
Each package is self-contained: compiler, standard library, native runtime,
and the LLVM 22 toolchain (clang/opt/lld) are all inside — no separate LLVM
install needed.
-
Download the package for your platform below and extract it.
-
Run the installer to put
razorforge(andrf) on your PATH:- Windows:
install.cmd - Linux / macOS:
./install.sh
- Windows:
-
Write
hello.rf:module Hello import IO/Console routine start() show("Hello from RazorForge!") return -
razorforge buildandrun hello.rf
See QUICKSTART.md inside the package for projects, razorforge.toml, and the
full CLI reference.
Platform notes
| Package | Needs from the system |
|---|---|
win-x64 |
Nothing — fully self-contained (mingw-based linking) |
linux-x64 |
glibc development files for linking (apt install libc6-dev / dnf install glibc-devel) |
osx-arm64 |
Apple Command Line Tools for linker stubs (xcode-select --install) |
macOS Gatekeeper: this alpha is not notarized by Apple, so browser
downloads are quarantined and macOS will refuse to load the bundled
libraries ("libhostfxr.dylib cannot be opened because the developer cannot
be verified")../install.shfixes this automatically — it clears the
quarantine attribute and ad-hoc re-signs the bundled binaries (quarantine
removal alone isn't always enough: Apple Silicon requires valid signatures
and newer macOS caches Gatekeeper verdicts). Manual equivalent, run once
inside the extracted folder:
xattr -dr com.apple.quarantine . && find . -type f \( -name '*.dylib' -o -perm -u+x \) -exec codesign --force --sign - {} \;
Downloading withcurl -LOavoids the quarantine flag entirely.
Checksums for every artifact are attached as checksums-<platform>.txt.
Docs & feedback
Language guide: https://razorforge.lumi-dev.xyz/ ·
Issues and design feedback: https://github.com/dj-lumiere/razorforge-suflae/issues
v0.0.2-alpha
What's new in v0.0.2-alpha
This release is about decimal precision you can trust — every decimal type
now has a complete, correctly-rounded transcendental surface — plus a rework of
deferred initialization.
Numerics
F128is now backed by TLFloat (correctly rounded quad-precision
software float). LLVM'sfp128type is gone from emitted IR entirely, which
removes the platform-specific compiler-rt shims (notably on macOS).
Behavioral fixes that come with it: subnormals are computed correctly (they
were flushed before),F128→F64conversion rounds (it truncated), and
large-integer→F128conversion rounds to nearest.D32/D64/D128gain the full transcendental surface —sincos
tanasinacosatanatan2, the hyperbolics and their inverses,
expexp2expm1loglog2log10log1p,pow/$pow!,cbrt,
hypot. Results are correctly rounded to the type's last digit and
byte-identical on every platform, via tiered routing through the
next-size-up TLFloat binary format (D32 → binary64, D64 → quad, D128 →
octuple).- Arbitrary-precision
Decimaltrig now actually exists.sin…tanh,
atan2,Decimal.pi(precision), andDecimal.e(precision)were declared
in the stdlib but had no implementation — any call failed at link. They are
now LibBF-backed with working precision that scales with the request
(default 50 significant digits, up to 1000), so a 1000-digit result is as
trustworthy as a 50-digit one. - Canonical special values everywhere: every float and decimal type now
printsinf,-inf, andNaN(NaN is always unsigned). Previously the
spellings differed by family (-nan,Inf,Infinity).
Language
-
Breaking: the
uninitkeyword is removed. Uselateinit varinstead. -
lateinitreworked — eager allocation, late initialization. Storage is
allocated at the declaration, so alateinitentity is valid and borrowable
immediately (the out-parameter pattern now works instead of crashing), and
reassignment destroys the previous contents, so branch-initialization no
longer leaks. -
Typed suffixes now work on integer-form literals.
1f64,1d32,1dn,
1j,1jn, … all tokenize — a literal no longer needs a decimal point just
to carry a float/decimal/imaginary suffix. -
Complex values print as
a+bj, matching the imaginary literal suffixes,
so output round-trips as literal syntax. The fixed-widthC32/C64/C128
gain this representation too (they previously printed the memberwise debug
form), and the arbitrary-precisionComplexswitches from1+1ito1+1j.
Compiler fixes
- Default arguments on method calls were silently broken. Calling any
method without an argument that has a declared default (e.g.d.sin(),
Decimal.pi()) emitted a call with the argument missing — the callee read
whatever happened to be in the register, producing nondeterministic results
that varied by platform. Defaults are now materialized at every call site.
All numeric results above are locked by end-to-end fixtures whose expected
outputs were generated from the runtime itself and cross-checked against
reference constants.
RazorForge is a natively compiled, statically typed language built around
single-ownership memory management (no borrow checker, no GC) and
compiler-generated error handling. This is an early alpha: the compiler,
runtime, and standard library work — 1,400+ tests and 90+ end-to-end fixtures
run green on every commit — but APIs will change and you will find bugs.
Install & hello world
Each package is self-contained: compiler, standard library, native runtime,
and the LLVM 22 toolchain (clang/opt/lld) are all inside — no separate LLVM
install needed.
-
Download the package for your platform below and extract it.
-
Run the installer to put
razorforge(andrf) on your PATH:- Windows:
install.cmd - Linux / macOS:
./install.sh
- Windows:
-
Write
hello.rf:module Hello import IO/Console routine start() show("Hello from RazorForge!") return -
razorforge buildandrun hello.rf
See QUICKSTART.md inside the package for projects, razorforge.toml, and the
full CLI reference.
Platform notes
| Package | Needs from the system |
|---|---|
win-x64 |
Nothing — fully self-contained (mingw-based linking) |
linux-x64 |
glibc development files for linking (apt install libc6-dev / dnf install glibc-devel) |
osx-arm64 |
Apple Command Line Tools for linker stubs (xcode-select --install) |
macOS Gatekeeper: this alpha is not notarized by Apple, so browser
downloads are quarantined and macOS will refuse to load the bundled
libraries ("libhostfxr.dylib cannot be opened because the developer cannot
be verified")../install.shfixes this automatically — it clears the
quarantine attribute and ad-hoc re-signs the bundled binaries (quarantine
removal alone isn't always enough: Apple Silicon requires valid signatures
and newer macOS caches Gatekeeper verdicts). Manual equivalent, run once
inside the extracted folder:
xattr -dr com.apple.quarantine . && find . -type f \( -name '*.dylib' -o -perm -u+x \) -exec codesign --force --sign - {} \;
Downloading withcurl -LOavoids the quarantine flag entirely.
Checksums for every artifact are attached as checksums-<platform>.txt.
Docs & feedback
Language guide: https://razorforge.lumi-dev.xyz/ ·
Issues and design feedback: https://github.com/dj-lumiere/razorforge-suflae/issues
v0.0.1-alpha
RazorForge is a natively compiled, statically typed language built around
single-ownership memory management (no borrow checker, no GC) and
compiler-generated error handling. This is an early alpha: the compiler,
runtime, and standard library work — 1,400+ tests and 90+ end-to-end fixtures
run green on every commit — but APIs will change and you will find bugs.
Install & hello world
Each package is self-contained: compiler, standard library, native runtime,
and the LLVM 22 toolchain (clang/opt/lld) are all inside — no separate LLVM
install needed.
-
Download the package for your platform below and extract it.
-
Run the installer to put
razorforge(andrf) on your PATH:- Windows:
install.cmd - Linux / macOS:
./install.sh
- Windows:
-
Write
hello.rf:module Hello import IO/Console routine start() show("Hello from RazorForge!") return -
razorforge buildandrun hello.rf
See QUICKSTART.md inside the package for projects, razorforge.toml, and the
full CLI reference.
Platform notes
| Package | Needs from the system |
|---|---|
win-x64 |
Nothing — fully self-contained (mingw-based linking) |
linux-x64 |
glibc development files for linking (apt install libc6-dev / dnf install glibc-devel) |
osx-arm64 |
Apple Command Line Tools for linker stubs (xcode-select --install) |
Checksums for every artifact are attached as checksums-<platform>.txt.
Docs & feedback
Language guide: https://razorforge.lumi-dev.xyz/ ·
Issues and design feedback: https://github.com/dj-lumiere/razorforge-suflae/issues