Hello everyone. I want to introduce SubsetJuliaVM, a subset of Julia that works on iOS devices and web browsers.
The following text was generated using https://translate.preferredai.jp/ to rewrite the following Japanese manuscript into English.
In short
Give it a try:
Introduction
Julia is excellent. We want to increase the number of people who can use Julia. The realization of this wish becomes easier when Julia is introduced into educational settings.
Some high schools now offer classes using online content on iPads. In mathematics classes, when students become frustrated with complex problems before completing them by hand, teachers use Google Colab to have students perform numerical computations using Julia or Python, then visualize function graphs to enhance understanding.
There’s growing demand in Japan’s school environment for running Julia on iPads.
There is a growing demand for running Julia on iPads in Japanese schools. While it’s certainly possible to run Julia on platforms like JuliaHub or Google Colab online, stable internet connectivity isn’t always guaranteed in Japanese educational settings. Various constraints may prevent access to environments like JuliaHub or Google Colab. Even if a connection is possible, launching a Julia execution environment instance can take minutes—even for simple calculations like 1 + 1.
I thought it was great that Julia can run on iOS devices without internet connectivity.
Running Julia code within an iOS application
Running Julia code within an iOS application is challenging. Since Julia generates native code at runtime, it doesn’t comply with the App Store’s security policies. Additionally, I’m not sure how to even begin building Julia for iOS devices…
Several applications, such as Pythonista and Carnets, allow running Python on iOS devices without an internet connection. I realized that creating a parser and virtual machine that accepts Julia syntax would be the way to go.
While ideas come to me easily, I lack the technical skills to implement them. I know terms like compilers and LLVM, but I’ve never actually applied this knowledge to create functional applications.
Meanwhile, recent years have seen significant advancements in coding assistance agents like Codex, Cursor, and Claude Code. I realized that by leveraging these tools, I could potentially bring my ideas to life without getting bogged down in implementation details.
Your turn! Rust
I chose Rust as the foundation for building an environment to run Julia. Rust is a statically typed, pre-compiled, modern language with package management capabilities. While Rust is considered challenging, my previous development experience has shown that compared to C/C++, it’s overwhelmingly more productive when working with LLMs. See the following blogs to learn more.
- Farewell
libsparseir, Hellosparse-ir-rsby Satoshi Terasaki - Why I Migrated from C++ to Rust (Development Experience in the Age of Generative AI) by Hiroshi Shinaoka
Within just two weeks, we pushed Claude Code Opus 4.5 to its performance limits and successfully developed SubsetJuliaVM, a subset execution environment for JuliaLang.
How it works: 4-layer pipeline
The SubsetJuliaVM consists of four components: the parser, lowering, compiler, and VM.
The parser is a pure Rust parser that parses all Julia syntax. It produces CST (Concrete Syntax Tree). It is inspired by tree-sitter-julia. We could use the tree-sitter-julia itself. However, we encountered technical barriers when attempting to WebAssembly-ize SubsetJuliaVM, so we rewrote the parser from tree-sitter-julia to Pure Rust from scratch (using LLM, of course).
The component lowering takes an abstract syntax tree (CST) parsed by parser and determines whether its components are supported by the VM. For example, if the function does not support certain macros like eval, @eval, @simd, @inbounds, or Base.Threads.@threads, the lowering will raise an UnsupportedFeature error. If the CST consists of a node that is supported by a VM, it is transformed to Core IR.
In this phase, println("Hello World) in Julia is converted to the following Core IR:
Stmt::Expr {
expr: Expr::Call {
function: "println",
args: [
Expr::Literal(Literal::Str("Hello World"), span)
],
kwargs: [],
span,
},
span,
}
The compiler generates stack-based bytecode from Core IR. In the case of println("Hello World"), it is converted to:
[
Instr::PushStr("Hello World".to_string()), // Generated by compile_expr for string literal
Instr::PrintStrNoNewline, // Generated by compile_builtin_call based on type
Instr::PrintNewline, // Generated by compile_builtin_call
Instr::PushNothing, // Generated by compile_builtin_call (return value)
Instr::ReturnNothing, // Generated by emit_return_for_type for top-level
]
Finally the VM which is a stack-based virtual machine executes bytecode.
The VM fetches the instruction at ip, increments ip += 1, then executes.
// VM state
struct Vm {
code: Vec<Instr>, // Bytecode
ip: usize, // Instruction pointer
stack: Vec<Value>, // Execution stack
output: String, // Output buffer
rng: R, // RNG
}
// Execution flow (conceptual)
loop {
match instr {
Instr::PushStr(s) => {
stack.push(Value::Str(s.clone()));
// Stack: [Str("Hello World")]
}
Instr::PrintStrNoNewline => {
let s = pop_str(&mut stack); // Pop "Hello World"
output.push_str(&s); // Append to output buffer
// Stack: []
// output: "Hello World"
}
Instr::PrintNewline => {
output.push('\n'); // Append newline
// output: "Hello World\n"
}
Instr::PushNothing => {
stack.push(Value::Nothing); // Return value (println returns nothing)
// Stack: [Nothing]
}
Instr::ReturnNothing => {
// Top-level return, end execution
return Value::Nothing;
}
}
}
Supported syntax
The SubsetJuliaVM should work the following samples:
# Mandelbrot escape time algorithm
function mandelbrot_escape(c, maxiter)
z = 0.0 + 0.0im
for k in 1:maxiter
if abs2(z) > 4.0 # |z|^2 > 4
return k
end
z = z^2 + c
end
return maxiter
end
# Compute grid using broadcast (vectorized)
# xs' creates a row vector, ys is a column vector
# Broadcasting creates a 2D complex matrix C
function mandelbrot_grid(width, height, maxiter)
xmin = -2.0; xmax = 1.0
ymin = -1.2; ymax = 1.2
xs = range(xmin, xmax; length=width)
ys = range(ymax, ymin; length=height)
# Create 2D complex grid via broadcasting
C = xs' .+ im .* ys
# Apply escape function to all points at once
# Ref(maxiter) prevents maxiter from being broadcast
mandelbrot_escape.(C, Ref(maxiter))
end
# ASCII visualization
@time grid = mandelbrot_grid(50, 25, 50)
println("Mandelbrot Set (50x25):")
for row in 1:25
for col in 1:50
n = grid[row, col]
if n == 50
print("#")
elseif n > 25
print("+")
elseif n > 10
print(".")
else
print(" ")
end
end
println("")
end
module MyGeometry
using Statistics: mean
export distance
export Point
struct Point{T<:Real}
x::T
y::T
end
Base.:+(p::Point{T}, q::Point{T}) where T <: Real = Point{T}(p.x + q.x, p.y + q.y)
Base.:-(p::Point{T}, q::Point{T}) where T <: Real = Point{T}(p.x - q.x, p.y - q.y)
function distance(p::Point{T}, q::Point{T}) where T <: Real
return sqrt((q.x - p.x)^2 + (q.y - p.y)^2)
end
function centroid(points::Vector{Point{T}}) where T <: Real
x = mean([point.x for point in points])
y = mean([point.y for point in points])
Tnew = promote_type(typeof(x), typeof(y))
return Point{Tnew}(x, y)
end
end #module
using .MyGeometry
p = Point(3, 4)
q = Point(0, 0)
@assert distance(p, q) == 5.0
@assert p isa Point{Int}
@assert typeof(p) === Point{Int}
@assert p + q == Point(3, 4)
@assert p - q == Point(3, 4)
@assert MyGeometry.centroid([Point(1, 2), Point(3, 4), Point(5, 6)]) == Point(3.0, 4.0)
println("All assertions passed!")
println(distance(p, q))
Note that macros like @assert, @show, and @time are parsed internally as Rust built-in functions. User-defined macros are not currently supported.
iOS application
Although still in the development phase, the SubsetJuliaVM was successfully tested on my iPad mini.
Web Assembly
Give it a try:
sjulia with REPL
sjulia is a CLI tool to launch a SubsetJuliaVM process.
Compile Julia code
The SubsetJuliaVM is lightweight, so Julia code containing syntax supported by SubsetJuliaVM can run self-contained by packaging the VM. This serves as the SubsetJuliaVM equivalent of JuliaC.
Is it performant?
Unfortunately, NO. Our SubsetJuliaVM looks like a Julia runtime but walks like slow Python. It is slower than Python.
High-performance operation lies beyond the scope of our current development focus. However, we are greedy. Perhaps our dreams might someday become reality (Really???).
Conclusion
We implemented a Julia interpreter called SubsetJuliaVM that supports a subset of Julia, written in Rust.
Our virtual machine is too slow to replace the official Julia, which uses JIT compilation.
As long as Rust-supported environments remain available, our virtual machine can operate anywhere—particularly in environments where the original Julia has difficulty functioning.
The current code remains unreleased. With sufficient financial support, we would be more than willing to make it publicly available. Are you interested😎?



