Project 8: An Upstream Contribution
Get a change merged into a real language runtime.
Effort: unbounded, and that is the point. Value: it is the difference between "I built a toy runtime" and "I contribute to language runtimes" — and after four months you are genuinely qualified, which is unusual for this domain.
Why You Are Ready
Language runtimes are intimidating to contribute to because the entry cost is high: you cannot fix a GC bug without understanding the collector, and you cannot understand the collector by reading a CONTRIBUTING file.
You have already paid that cost. You know what an upvalue is, why it has two states, what a write barrier protects, why the validator exists, and what a stale handle means. That is exactly the knowledge that makes these codebases readable — and it is why this project comes last.
Where to Contribute
| Project | Language | Why it suits you | Where to start |
|---|---|---|---|
mlua | Rust bindings to Lua | You know the C API's shape and the lifetime problems it solves | Docs, UserData ergonomics, async edges |
rune | Rust-native, embeddable | Closest to Ember in design; active and welcoming | Diagnostics, stdlib gaps, the book |
koto | Rust-native, expression-oriented | Small enough to read in a weekend | Stdlib, error messages |
piccolo | Rust Lua with a GC arena | Its GC design is the interesting alternative to yours | GC, Collect derive, stdlib |
rhai | Rust-native, embedding-focused | Excellent docs; strong on the host-boundary problems | Ergonomics, performance, docs |
hematita | Rust Lua VM | Small, incomplete — plenty of gaps | Almost anything |
lua (PUC-Rio) | C | The reference. High bar, mailing-list workflow | Documentation, test cases, bug reports |
| CPython | C | Enormous, well-organized, excellent mentoring | Doc/, dis, error messages |
wasmtime/cranelift | Rust | If Lab 30 caught you | Cranelift docs, IR verification |
Pick one, and pick the smallest. koto and hematita are readable in a weekend; mlua and
rune have active maintainers who review quickly. CPython and Lua are worth aspiring to and are not
where to learn the workflow.
What to Contribute, in Order of Increasing Ambition
1. Documentation
Not a consolation prize. You have just spent four months finding out which explanations are missing, and that is a rare and perishable perspective — in six months you will have forgotten what was confusing.
Look for: an undocumented invariant you had to derive, a design decision with no rationale recorded, an example that does not compile, a term used before it is defined.
2. A Test Case
Every project's test suite has gaps, and you now know where they are because you know which properties are hard:
- A closure capturing a loop variable.
- Upvalue closing on
breakout of a captured block. - A table key that is a table, surviving a collection.
select('#')with trailing nils.- A metamethod that mutates the table it is dispatched from.
- An inconsistent
table.sortcomparator.
Submit a failing test with a clear title before submitting a fix. Maintainers can merge a test immediately; a fix needs review of the fix and the diagnosis.
3. A Bug Report With a Minimal Reproducer
You have a whole runtime to differential-test against. Run their implementation and yours on the same program, and when they disagree, work out which is right — the same four-step procedure from Lab 12, applied across projects rather than across backends.
A minimal reproducer plus a specification citation is a gift. Most bug reports are neither.
4. A Fix
Start where you have the most context:
| If you enjoyed | Look at |
|---|---|
| Section 1 | Parser error recovery, diagnostic spans |
| Section 3 | The compiler, the disassembler, bytecode validation |
| Section 4 | The GC, upvalue handling, table internals |
| Section 5 | The host API, marshaling, userdata ergonomics |
| Section 6 | Fuzz targets, CI, the test matrix |
| Section 7 | Benchmarks, inline caches, specialization |
5. A Feature
Only after 1–4, and only after a maintainer has agreed to the design. An unsolicited large PR is usually a waste of everybody's time, and language runtimes are conservative for good reasons: every feature is forever, and every semantic is a compatibility promise.
The Workflow
- Read
CONTRIBUTING.mdand the last 20 merged PRs. The second is more informative than the first: it shows you what actually gets merged, how big it is, and how the reviewer talks. - Open an issue before writing code, unless the change is trivially small. "I noticed X; is a PR doing Y welcome?" costs you nothing and saves a rejected week.
- One change per PR. A fix plus a refactor plus a rename is three reviews wearing a trenchcoat.
- A test with every behavioral change. You already believe this.
- Match the project's style, including things you disagree with. Their repository, their conventions.
- Respond to review quickly and without defensiveness. A reviewer's time is the scarce resource.
What to Expect
- The first PR takes longer than the change. Setup, CI, conventions, and review latency.
- Review may be slow. Maintainers of small language projects are usually one or two people doing it in evenings. Ping politely after two weeks, then wait.
- You may be wrong. You have deep knowledge of your design; theirs may differ for reasons that are not in the code. Ask before asserting.
- Small merged beats large stalled. A merged docs PR makes you a contributor; a 2,000-line feature PR sitting for six months does not.
Deliverables
- One merged PR to a real language runtime.
-
A
docs/learning/16-upstream.mdentry: what you contributed, what review taught you, what their design does differently from yours and why. - At least one thing you would change in Ember because of what you read in their codebase. That is the actual output of this project.
- Optional and better: a second contribution, from a bug you found with your own runtime as the oracle.
Where to Look First
# The Rust-native ones, in rough order of approachability:
git clone https://github.com/koto-lang/koto
git clone https://github.com/rune-rs/rune
git clone https://github.com/mlua-rs/mlua
git clone https://github.com/kyren/piccolo
git clone https://github.com/rhaiscript/rhai
# Then read, in each: the GC (if any), the value representation, and the host API.
# You now know exactly what to look for, and how each one differs from your choices.
Read piccolo's GC first, whichever you contribute to. Its arena-and-branding approach to
GC-in-Rust is the strongest alternative to Ember's handles, it uses lifetimes to make unrooted access
a compile error rather than a runtime one, and comparing the two designs is the single most
educational hour available after finishing this curriculum.
Validation / Self-check
- Why are you qualified now in a way you were not four months ago?
- Why is documentation a first-class contribution here specifically, and why is the window short?
- Give three test cases you could write for someone else's runtime, from properties you know are hard.
- What do the last 20 merged PRs tell you that
CONTRIBUTING.mddoes not? - Why open an issue before writing code?
- What is the actual output of this project, beyond the merged PR?
- How does
piccolo's GC design differ from yours, and which would you choose now?
Next: The Appendix.