Fork me on GitHub

Julia, embedded in Go.
An interpreter that runs Julia's own Base.

go-embedded-julia is in development, and its parser and its lowering are released: v0.3.0 completes milestones M0 (the oracle harness, against julia 1.12.6), M1 (a Go port of JuliaSyntax), identical to julia's Meta.parseall on 1191 of 1191 files of julia's own tree, and M2 (lowering: a port of julia 1.12.6's femtolisp front end), identical to Meta.lower on 53 997 of 53 997 forms of that tree. Milestone M3 (the Core runtime) is in progress: v0.9.0 extends the package interp, a port of julia 1.12.6's src/interpreter.c that runs lowered code over Core, to struct and abstract type definitions, with parameters, with julia's default constructors and to methods with where clauses, dispatched with static parameters (85 Core-only programs show the same value as julia; Base is not there yet), on top of Julia's type objects, subtyping and method specificity, identical to julia on 20 000 random pairs, and the dispatch of concrete calls, identical to julia's which on 9 000 calls. It will be a pure-Go, embeddable Julia interpreter: julia's parser and lowering ported to Go, and an interpreter over the lowered IR that runs Julia's Base unchanged, for the julia cells of go-notebook and its WebAssembly playground.

v0.9.0: M0, M1, M2 done, M3 in progress8 milestones, M0 to M7pure Go
status
release   v0.9.0 (2026-10-31)
done      M0 oracle harness (julia 1.12.6)
done      M1 parser: 1191/1191 files
done      M2 lowering: 53997/53997 forms
now       M3 Core: types, dispatch, interp
next      M3 boot.jl, builtins, intrinsics
goal      julia cells in go-notebook
scope     an interpreter, not a JIT
plan      github.com/go-scikit/roadmap
3 repositories — released v0.9.0: the parser and the lowering (M0, M1 and M2 done), the Core runtime (M3) in progress — types, subtyping, specificity, dispatch, an interpreter that runs parametric struct definitions and where methods
What it will be

Julia's semantics, written in Go.

Julia's front end becomes Go, Base stays Julia, and the boundary between them is mapped onto libraries the fleet already has. This is the plan of the roadmap's bibliography, not a result.

Julia's own front end, in Go

JuliaSyntax (the parser, 11 326 lines of Julia) and the femtolisp lowering julia 1.12.6 ships (julia-syntax.scm, macroexpand.scm, ast.scm, match.scm) ported to Go. Until Base is loaded no Julia can run, so the front end has to be native: a Go VM has no femtolisp.

An interpreter over lowered IR

The whole instruction set an interpreter must execute is 12 node types and 30 Expr heads. Julia's own C interpreter over it, src/interpreter.c, is 1 118 lines.

Julia's Base, unchanged

Base (140 705 lines of Julia) is run as is through the interpreter, at build time, and the resulting heap is shipped as an image. Nothing of Base is rewritten natively, so a Base fix upstream is not ported twice.

The runtime boundary on fleet libraries

Intrinsics, ccall targets and the runtime itself map onto Go: math/big and go-bignum for GMP and MPFR, go-ndarray for BLAS and LAPACK, the regexp stack of go-ruby-regexp for PCRE2, go-fft for FFTs.

For go-notebook

The julia cells of go-notebook and of its WebAssembly playground, the way rbgo, the Ruby VM of go-embedded-ruby, backs its ruby cells.

Licences kept clean

Julia is MIT, and its front end, runtime and Base may be translated to Go with the notice kept. The GPL parts of SuiteSparse are avoided, and GMP and MPFR (LGPL) are neither linked nor translated.

The plan

8 milestones, from an oracle to a playground.

Effort is on the plan's scale: S is days, M weeks, L months, XL several months. M3 and M4 dominate. The differential oracles of the front end are built first, because they are stronger than Julia's test suite there.

M0 · Oracle harness

done · S

Done: merged as pull request 1 of go-embedded-julia/julia, against julia 1.12.6. A Go tool that runs the reference julia (a pinned release) on a corpus and stores its outputs: parse trees, Meta.lower, Test summaries, printed values. Exit test: the outputs are checked in as golden files, regenerated by one command.

M1 · Parser

done · L

Done: released as v0.2.0 (2026-10-26), a Go port of JuliaSyntax 29ec5226f7b1 (the version julia 1.12.6 ships). syntax.ParseAll returns the same Expr as Meta.parseall: tokens, green trees and Expr identical on 1191 of 1191 files of julia 1.12.6's share/julia tree (base, Compiler, stdlib, test; 18.5 MB) and on 976 of 976 golden snippets. One core: 0.98 s against 1.39 s for julia (0.70x). Bounded (64 MiB, depth 4000) and fuzzed. Known gap: on a few malformed inputs JuliaSyntax itself throws and julia falls back to flisp, which is not ported.

M2 · Lowering and macro expansion

done · L

Done: released as v0.3.0 (2026-10-28). The lower package ports julia 1.12.6's femtolisp front end (julia-syntax.scm, macroexpand.scm, ast.scm, match.scm): lower.NewState().Lower(ex) is Meta.lower of a macro-expanded top-level form, compared field by field modulo gensym numbering. Identical on 53 997 of 53 997 forms of julia's share/julia tree (base, Compiler, stdlib, test; 1191 of 1191 files), 124 of 124 lowering snippets, 48 of 48 hygienic-scope forms and 477 of 477 built expressions (error paths, rare heads); deparse 119 of 119 and front-end functions 151 of 151 against julia --lisp. 100% statement coverage. Lower refuses trees deeper than lower.MaxDepth (1000) and FuzzLower runs on every pull request. Known gap: a Float64 inside a lowering error message returns lower.ErrUnsupported.

M3 · Core runtime

in development · XL

In progress. Types, subtyping, method tables, dispatch, the builtins (63 in julia 1.12.6), the intrinsics (93 in julia 1.12.6), the interpreter and boot.jl. Released so far: core (v0.3.0, v0.4.0) — Julia's type objects, apply_type, and subtyping (a port of jltypes.c and subtype.c of 1.12.6, without intersection): identical to julia on 20 000 random pairs and 4 902 cases from test/subtype.jl; and method specificity (jl_type_morespecific), identical to Base.morespecific on the same corpus. core/dispatch (v0.5.0): method tables with world ages and the dispatch of concrete calls, identical to julia's which and dispatch environment on 9 000 calls over 1 500 random method tables (4 675 method choices, 87 ambiguities). interp (v0.6.0 to v0.9.0): a port of julia 1.12.6's interpreter.c that runs lowered code over Core, with 24 builtins and the integer and float intrinsics; v0.7.0 runs struct and abstract type definitions with julia's default constructors (jl_ctor_def) and user-defined outer constructors, and calling a type dispatches on its constructors; v0.8.0 dispatches methods with where clauses with static parameters (e.g. f(x::T) where {T<:Integer} = T), through the TypeVar and UnionAll constructors of boot.jl:335-338, built in Go until boot.jl runs; v0.9.0 runs structs and abstract types with parameters (struct Q{T}, abstract type A{T}): Q(1) infers Q{Int64}, Q{Integer}(1) runs the inner constructor, a parameter no field constrains gets no outer constructor (as jl_ctor_def decides), and a type argument dispatches as Type{X}; 85 Core-only programs show the same value in Go and julia 1.12.6, 100% statement coverage, fuzzed. Not yet: type intersection (and with it the interference sets julia 1.12 uses to order matches), Base (and constructor arguments that need convert), macro expansion, redefining a struct that has parameters (it needs Base.getproperty), const and atomic fields, arrays, finally, the remaining builtins and intrinsics; interpreter speed against julia is not measured, and subtyping is 1.49× slower than jl_subtype on one core (open defect). Exit test: test/subtype.jl, specificity.jl, intrinsics.jl and the parts of core.jl that need no Base I/O.

M4 · Base bootstrap and image

planned · XL

Base_compiler.jl and Base.jl evaluated; the jl_*, libc and libuv shim (about 524 + 88 + 78 call sites); the Compiler interpreted for _return_type; an image writer and reader. Exit test: Base loads, and a list of test/ files passes.

M5 · Numeric boundary

planned · L

GMP and MPFR on math/big and go-bignum, a PCRE2 front end on go-ruby-regexp, utf8proc tables, a dSFMT port, Printf, Dates, Random, Statistics and LinearAlgebra on go-ndarray. Exit test: the gmp, mpfr, regex, unicode, rational and complex tests, and the standard-library tests, with seeded Random streams byte-identical.

M6 · Notebook bridge and go-scikit/julia

planned · M

The Julia to Go value table, the names a cell defines and uses, errors and worker limits, and the go-scikit bindings. Exit test: a mixed Go and Julia example notebook transcript in the go-notebook docs.

M7 · WebAssembly playground

planned · M

A lazily loaded Julia core, image loading, and SVG display through the existing sanitizer. Exit test: a first result in less time than the Ruby core on the same machine, with the size recorded.

The runtime boundary

What the interpreter has to provide, counted.

Every number below was counted by a Go program over the Julia sources the plan names, not estimated.

93 intrinsics in julia 1.12.6

The bibliography counted 94 (81 ADD_I, 12 ALIAS and 1 hidden) on julia 1.15-DEV sources; julia 1.12.6, the release the port follows, has 93. All but llvmcall and the pointer group map to Go in a few lines each, on math/bits and math.

63 builtins in julia 1.12.6

The bibliography counted 83 on julia 1.15-DEV sources; julia 1.12.6 has 63. From src/builtin_proto.h: getfield, setfield!, isa, ===, apply_type, invoke and the rest, each one a Go function.

896 ccall sites in base/

856 ccall and 40 @ccall in 202 files. By target: 524 on the Julia runtime and ios_*, 88 on libc, 81 on MPFR (and 29 more generated by loops), 78 on libuv, 40 on GMP, 18 on PCRE2, 15 on utf8proc.

0 libm calls, 0 llvmcall

Base's elementary functions are written in Julia over intrinsics, so they are bit-identical by construction, and Base has no llvmcall( site.

194 sites in LinearAlgebra

182 call BLAS and LAPACK. They are implemented as Go functions over go-ndarray's linalg on the column-major buffer a Julia matrix owns, without copying. Missing kernels are added to go-ndarray, not to the VM.

No FFT in Julia's Base

Neither Base nor the standard library has one: an FFTW.jl-style API over go-fft is a separate package, not part of the VM.

An interpreter, and honest about it.

It keeps Julia's semantics, not its native speed. An interpreter cannot match Julia's JIT on loops, so heavy computation goes through the Go libraries (go-ndarray, go-bignum, go-fft). The comparisons that can hold are start-up and first result, and the kernels routed to those libraries. The plan lists every interpreter speed statement as unverified, except the parser's: on one core, parsing julia 1.12.6's share/julia tree takes 0.98 s against 1.39 s for Meta.parseall (0.70x). No speed is claimed for the lowering.

The plan, the architecture and the licences →
Repositories

What is here.

The organization holds the interpreter's repository, julia, and its landing, documentation and brand.

julia

source

The interpreter. Its M0, the oracle harness against julia 1.12.6, and M1, a Go port of JuliaSyntax identical to Meta.parseall on 1191/1191 files of julia's tree, are released as v0.2.0 (2026-10-26). M2, lowering, a Go port of julia 1.12.6's femtolisp front end identical to Meta.lower on 53 997 of 53 997 forms of julia's tree (1191/1191 files), is released as v0.3.0 (2026-10-28). M3, the Core runtime, is in progress: the core package (v0.3.0, v0.4.0) holds Julia's type objects, apply_type, subtyping and method specificity, core/dispatch (v0.5.0, 2026-10-30) the method tables and the dispatch of concrete calls, and interp (v0.6.0 to v0.9.0, 2026-10-31) an interpreter of lowered code over Core that runs struct and abstract type definitions, with parameters, with julia's default constructors and methods with where clauses.

docs

site

The documentation (Hugo + Hextra): the plan and its milestones, the architecture, the runtime boundary with its counts, and the licences. Source →

brand

assets

Logos and icons for the organization, in SVG / PNG / JPG / ICO / ICNS across colour, white and black variants. The mark is Julia's subtype operator, <:.