Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ disableKinds = ["taxonomy", "term", "RSS", "sitemap", "404"]
[[params.phases]]
name = "Phase 8 — Conformance & performance"
status = "active"
blurb = "Since July the work has been **runtime** conformance, measured against [ruby/spec](https://github.com/ruby/spec)'s `language/` + `core/` suites: **21,982 examples pass** (measured 2026-09-23 on `main`, `d498ddc`, four consecutive runs all identical), gated in CI by a **shrink-only floor of 21,970** — the floor is the level CI refuses to fall below, *not* the score. It sits just under the measured total by design: a margin wide enough for the run-to-run jitter, narrow enough that losing a whole spec file still trips it. It has climbed from 6,000 when the ratchet landed on 2026-08-03, across 27 waves. Two of the recent jumps were fixes to the **measurement** rather than the interpreter — the mspec shim, and a parser upgrade that made 13 previously-unparseable spec files visible — and those are gains in what the measurement can *see*, not in what the VM can *do*. Earlier, a conformance campaign brought the pure-Go **front-end** to ~**100% of all valid Ruby** parsed across Rails + Puppet (99.96% of files; 2 misses are syntax-error fixtures MRI rejects) and **parse + compile** of **Rails 99.82% / Puppet 100%**, with **0 over-permissive** — and RuboCop 99.7%, Sinatra/Jekyll/Thor/Kramdown/dry-struct 100%, RSpec DSL 10/10. On the runtime side, that road is now underway: **the real `puppet apply` CLI runs end-to-end** on a pure-Go CGO=0 `rbgo` — `require \"puppet\"` boots Puppet 8.11.0 (all pure-Ruby gem deps on the path), and the genuine `Puppet::Util::CommandLine` → `Puppet::Application::Apply` path (real `OptionParser`, all types + providers, settings catalog applied to disk, then the user catalog via the transaction/RAL) converges `notify`, `file` and `exec` resources (the `exec` provider runs commands via pure-Go process execution with `onlyif`/`unless`/`creates`/`path` guards), emits real Puppet output (`Notice: hi from rbgo cli`) and exits 0 with the YAML run report round-tripped. Broader resource **providers** are the active next milestone (`user`/`group` provider-ready; `package`/`service` need a host package manager/systemd + root). rbgo is also increasingly composed from the standalone pure-Go **go-ruby-* family** — **178 modules across 176 orgs are now bound into rbgo as native modules** (parser, regexp, erb, marshal, yaml, format, optparse, strscan, json, date, … a selection is on the landing page), each its own 100%-coverage 6-arch org. (`Set` is not among them: it is implemented in-tree, over rbgo's own Hash.) Every one of the six arches is validated on **real hardware**, not just qemu — amd64/arm64 natively, riscv64/ppc64le/loong64 on the GCC Compile Farm (RVV, POWER8E/POWER9, LoongArch), s390x on the IBM LinuxONE Community Cloud — which is what makes the SIMD-accelerated modules and perf numbers measured rather than estimated. On performance, a 6-runtime suite (rbgo/rbgo+AOT vs MRI/YJIT/JRuby/TruffleRuby): **rbgo+AOT beats MRI+YJIT 18–24× on loop/fib**; plus inline method caches, small-integer interning (a small-int loop drops from ~245k allocations to 1) and capture-tracked frame recycling (~14% faster on fib)."
blurb = "Since July the work has been **runtime** conformance, measured against [ruby/spec](https://github.com/ruby/spec)'s `language/` + `core/` suites: **22,488 examples pass** (measured 2026-09-26 on `main`, `3e8e3cc`; three independent runs, identical), gated in CI by a **shrink-only floor of 22,475** — the floor is the level CI refuses to fall below, *not* the score. It has climbed from 6,000 when the ratchet landed on 2026-08-03. **Only `language/` and `core/` are swept** — ruby/spec's `library/`, `optional/` and `security/` trees are not run at all, so this is not a percentage of Ruby. Two of the recent jumps were fixes to the **measurement** rather than the interpreter — the mspec shim, and a parser upgrade that made 13 previously-unparseable spec files visible — and those are gains in what the measurement can *see*, not in what the VM can *do*. Earlier, a conformance campaign brought the pure-Go **front-end** to ~**100% of all valid Ruby** parsed across Rails + Puppet (99.96% of files; 2 misses are syntax-error fixtures MRI rejects) and **parse + compile** of **Rails 99.82% / Puppet 100%**, with **0 over-permissive** — and RuboCop 99.7%, Sinatra/Jekyll/Thor/Kramdown/dry-struct 100%, RSpec DSL 10/10. On the runtime side, that road is now underway: **the real `puppet apply` CLI runs end-to-end** on a pure-Go CGO=0 `rbgo` — `require \"puppet\"` boots Puppet 8.11.0 (all pure-Ruby gem deps on the path), and the genuine `Puppet::Util::CommandLine` → `Puppet::Application::Apply` path (real `OptionParser`, all types + providers, settings catalog applied to disk, then the user catalog via the transaction/RAL) converges `notify`, `file` and `exec` resources (the `exec` provider runs commands via pure-Go process execution with `onlyif`/`unless`/`creates`/`path` guards), emits real Puppet output (`Notice: hi from rbgo cli`) and exits 0 with the YAML run report round-tripped. Broader resource **providers** are the active next milestone (`user`/`group` provider-ready; `package`/`service` need a host package manager/systemd + root). rbgo is also increasingly composed from the standalone pure-Go **go-ruby-* family** — **178 modules across 176 orgs are now bound into rbgo as native modules** (parser, regexp, erb, marshal, yaml, format, optparse, strscan, json, date, … a selection is on the landing page), each its own 100%-coverage 6-arch org. (`Set` is not among them: it is implemented in-tree, over rbgo's own Hash.) Of the six arches, amd64 and arm64 are tested on every pull request; riscv64, loong64, ppc64le and s390x are validated on a **nightly schedule** — under qemu, and natively on the GCC Compile Farm plus an IBM LinuxONE s390x host — not on the pull-request path. On performance, a 6-runtime suite (rbgo/rbgo+AOT vs MRI/YJIT/JRuby/TruffleRuby): **rbgo+AOT beats MRI+YJIT on integer-bound kernels — measured here at 8.7× on the tight-loop benchmark and 2.0× on recursive `fib(35)`** (darwin/arm64, whole-process wall time); plus inline method caches, small-integer interning (a small-int loop drops from ~245k allocations to 1) and capture-tracked frame recycling (~14% faster on fib)."
13 changes: 7 additions & 6 deletions layouts/_default/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ <h2>A · Per-module benchmarks</h2>

<section class="psec" style="padding-top:0"><div class="pwrap">
<h2>B · Runtime micro-benchmarks</h2>
<p class="cap">rbgo's ahead-of-time backend compiles hot methods to unboxed Go kernels. Against MRI with YJIT enabled, on tight loop / <code>fib</code> kernels:</p>
<p class="cap">rbgo's ahead-of-time backend compiles integer-bound methods to unboxed Go kernels. Measured on this project's own micro-benchmarks (<code>bench/loop.rb</code>, <code>bench/fib.rb</code>) on darwin/arm64, best-of-N whole-process wall time, against MRI 4.0.5 with YJIT enabled:</p>
<div class="pcards">
<div class="pcard"><div class="n">18–24×</div><h3>AOT vs MRI+YJIT</h3><p>rbgo+AOT beats MRI with YJIT enabled by <b>18–24×</b> on loop / <code>fib</code> — unboxed int64 kernels, no boxing on the hot path.</p></div>
<div class="pcard"><div class="n">8.7×</div><h3>AOT vs MRI+YJIT</h3><p>On the tight-loop benchmark, rbgo+AOT ran <b>8.7×</b> faster than MRI+YJIT (43&nbsp;ms vs 373&nbsp;ms). On recursive <code>fib(35)</code> the margin is <b>2.0×</b> (56&nbsp;ms vs 110&nbsp;ms) — recursion keeps the call overhead the loop case removes. Only methods the AOT backend can lower benefit at all.</p></div>
<div class="pcard"><div class="n">245k → 1</div><h3>Small-integer interning</h3><p>Interned small integers drop a small-int loop from <b>~245,000 allocations to 1</b> — the hot integers are shared, immutable singletons.</p></div>
<div class="pcard"><div class="n">~14%</div><h3>Frame recycling</h3><p>Capture-tracked frame recycling is <b>≈14% faster on fib</b>; frames are reused unless a closure captures them. Plus inline method caches on every call site.</p></div>
</div>
Expand All @@ -68,11 +68,12 @@ <h2>C · Application conformance &amp; performance</h2>
<div class="applist">
<div class="appcard">
<h3>Runtime conformance — ruby/spec</h3>
<p><b>21,982 examples pass</b> in ruby/spec's <code>language/</code> + <code>core/</code> suites, measured 2026-09-23 on <code>main</code> (<code>d498ddc</code>), four consecutive runs all identical.</p>
<p><b>22,488 examples pass</b> in ruby/spec's <code>language/</code> + <code>core/</code> suites, measured 2026-09-26 on <code>main</code> (<code>3e8e3cc</code>). Three independent runs — two through the ratchet at different timeouts, one through a separate sweep — reported the same total.</p>
<ul>
<li>CI gates on a <b>shrink-only floor of 21,970</b>, raised there after wave 27. The floor is the level a run may not fall below — it is <b>not</b> the score. It sits just under the measured total <b>on purpose</b>: a margin of 12, wide enough to absorb the run-to-run jitter of <a href="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/go-embedded-ruby/ruby/issues/615">#615</a> and no wider, because a floor slack enough to hide the loss of a whole spec file would defeat what the ratchet exists to catch.</li>
<li>Of the examples that ran, <b>91.4%</b> pass (21,983 of 24,044); 473 more are skipped and 15 of 2,206 spec files produce no result.</li>
<li>The floor has climbed from <b>6,000</b> on 2026-08-03 to <b>21,970</b> across 27 waves. Two recent jumps were fixes to the <b>measurement</b> — the mspec shim, and a parser upgrade that made 13 previously-unparseable spec files visible — which are gains in what the measurement can <i>see</i>, not in what the VM can <i>do</i>.</li>
<li>CI gates on a <b>shrink-only floor of 22,475</b>. The floor is the level a run may not fall below — it is <b>not</b> the score: it is what CI guarantees, while the measured total is what one run did.</li>
<li><b>Only <code>language/</code> and <code>core/</code> are swept.</b> ruby/spec's <code>library/</code>, <code>optional/</code> and <code>security/</code> trees are not run at all, so this figure is <b>not</b> a percentage of Ruby, and most of the standard library sits outside it.</li>
<li>Of the examples that ran, <b>93.3%</b> pass (22,488 of 24,116); 477 more are skipped because our shim stubs the matcher they need, and 10 of 2,206 spec files produce no result. Skips and unreadable files sit outside the ratio entirely.</li>
<li>The floor has climbed from <b>6,000</b> on 2026-08-03 to <b>22,475</b>. Two earlier jumps were fixes to the <b>measurement</b> — the mspec shim, and a parser upgrade that made 13 previously-unparseable spec files visible — which are gains in what the measurement can <i>see</i>, not in what the VM can <i>do</i>.</li>
</ul>
</div>
<div class="appcard">
Expand Down
20 changes: 11 additions & 9 deletions layouts/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,25 @@

<div class="hero"><div class="wrap">
<div>
<h1 class="head">A complete Ruby,<br><span class="accent">written in Go.</span></h1>
<h1 class="head">A Ruby you can embed,<br><span class="accent">written in Go.</span></h1>
<p class="sub">go-embedded-ruby (<b>rbgo</b>) is a from-scratch Ruby — lexer, parser, compiler, bytecode VM and an AOT backend — that embeds in any Go program and cross-compiles to a single CGO-free binary. Built in the open as a <b>digital common</b> (<i>commun numérique</i>), free to use, study, modify and share under the BSD-3-Clause licence.</p>
<div class="cta">
<a class="btn btn-primary" href="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/go-embedded-ruby/ruby">Get started →</a>
<a class="btn btn-ghost" href="/performance/">See the benchmarks</a>
</div>
<div class="trust"><span><b>21,982</b> ruby/spec examples passing</span><span><b>~100%</b> of real Ruby parsed</span><span><b>6</b> 64-bit arches</span></div>
<div class="trust"><span><b>22,488</b> ruby/spec <code>language/</code>+<code>core/</code> examples passing</span><span><b>~100%</b> of real Ruby parsed</span><span><b>6</b> 64-bit arches</span></div>
</div>
<div class="term">
<div class="bar"><i style="background:#ff5f57"></i><i style="background:#febc2e"></i><i style="background:#28c840"></i><span class="t">rbgo — zsh</span></div>
<pre><span class="c-com"># a real Ruby, no cgo, no Ruby installed</span>
<span class="c-cmd">$ rbgo</span> -e <span class="c-str">'puts (1..20).reduce(:*)'</span>
<span class="c-out">2432902008176640000</span>

<span class="c-com"># AOT-compile the hot path to native Go</span>
<span class="c-com"># AOT-compile the hot path and drop the front-end</span>
<span class="c-cmd">$ rbgo</span> build --closed app.rb -o app
<span class="c-out">→ app (CGO=0, 1 file, runs anywhere)</span>
<span class="c-out">rbgo build: app (1 method(s) AOT-compiled: Object#fib)</span>
<span class="c-out">rbgo build: closed-world — front-end dropped</span>
<span class="c-out">rbgo build: binary size 172.5 MiB</span>

<span class="c-com"># the real Puppet CLI, end to end</span>
<span class="c-cmd">$ rbgo</span> run puppet_apply.rb
Expand All @@ -54,19 +56,19 @@ <h1 class="head">A complete Ruby,<br><span class="accent">written in Go.</span><
<h2>Everything a Ruby runtime gives you — as a Go library.</h2>
<p class="lead">No FFI, no libruby, no system Ruby. Drop a genuine, monkey-patchable Ruby object model into your Go binary and ship it anywhere.</p>
<div class="grid">
<div class="card"><div class="ico">🧩</div><h3>Truly embeddable</h3><p>A real object model — classes, mixins, <code>method_missing</code>, metaprogramming, Fiber — callable straight from Go.</p></div>
<div class="card"><div class="ico">🧩</div><h3>Embeddable</h3><p>A real object model — classes, mixins, <code>method_missing</code>, metaprogramming, Fiber. The Go API is deliberately small today: <code>ruby.Run(src, out)</code> runs a program on a fresh VM and writes its output to one writer. Calling a Ruby method from Go is not exposed yet.</p></div>
<div class="card"><div class="ico">📦</div><h3>One static binary</h3><p>CGO=0 by default. Cross-compiles to amd64, arm64, riscv64, loong64, ppc64le and s390x.</p></div>
<div class="card"><div class="ico">⚡</div><h3>AOT that flies</h3><p>Ahead-of-time method compilation to unboxed Go kernels — <b>18–24× over MRI+YJIT</b> on loop/fib.</p></div>
<div class="card"><div class="ico">✅</div><h3>Conformance-first</h3><p><b>21,982</b> ruby/spec <code>language/</code> + <code>core/</code> examples pass, gated in CI by a shrink-only floor of <b>21,970</b> — the floor is what CI refuses to fall below, not the score. It sits just under the measured total by design: a margin wide enough for run-to-run jitter, narrow enough that losing a whole spec file still trips it. Front-end: ~100% of valid Ruby parsed across Rails + Puppet (2026-06).</p></div>
<div class="card"><div class="ico">⚡</div><h3>AOT for integer kernels</h3><p>Ahead-of-time compilation of integer-bound methods to unboxed Go. Measured on this project’s own micro-benchmarks (darwin/arm64, whole-process wall time): <b>8.7×</b> MRI+YJIT on the tight-loop case, <b>2.0×</b> on recursive <code>fib(35)</code>. Only methods the compiler can lower benefit.</p></div>
<div class="card"><div class="ico">✅</div><h3>Conformance, measured</h3><p><b>22,488</b> ruby/spec <code>language/</code> + <code>core/</code> examples pass, gated in CI by a shrink-only floor of <b>22,475</b> — the floor is what CI refuses to fall below, not the score. <b>Only <code>language/</code> and <code>core/</code> are swept:</b> <code>library/</code>, <code>optional/</code> and <code>security/</code> are not run, so this is not a percentage of Ruby. Front-end: ~100% of valid Ruby parsed across Rails + Puppet (2026-06).</p></div>
<div class="card"><div class="ico">🔒</div><h3>Closed-world builds</h3><p><code>rbgo build --closed</code> bakes the program to bytecode and drops the front-end for a smaller binary.</p></div>
<div class="card"><div class="ico">🌿</div><h3>A real ecosystem</h3><p>Built from the <b>go-ruby-*</b> family — each a 100%-coverage, 6-arch stdlib module, bound natively.</p></div>
</div>
</div></section>

<section id="perf" style="padding-top:10px"><div class="wrap">
<div class="stats">
<div><div class="n">18–24×</div><div class="l">rbgo+AOT vs MRI+YJIT (loop/fib)</div></div>
<div><div class="n">21,982</div><div class="l">ruby/spec examples passing · CI floor 21,970</div></div>
<div><div class="n">8.7×</div><div class="l">rbgo+AOT vs MRI+YJIT, tight-loop benchmark (2.0× on <code>fib(35)</code>)</div></div>
<div><div class="n">22,488</div><div class="l">ruby/spec <code>language/</code>+<code>core/</code> examples passing · CI floor 22,475</div></div>
<div><div class="n">178</div><div class="l">go-ruby-* modules bound into rbgo</div></div>
<div><div class="n">CGO=0</div><div class="l">across 6 arches · 3 OSes</div></div>
</div>
Expand Down
Loading