MCPcopy Create free account
hub / github.com/IntegralPilot/rustc_codegen_jvm

github.com/IntegralPilot/rustc_codegen_jvm @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
746 symbols 1,958 edges 69 files ⚖ MIT 156 documented · 21% updated 5d ago★ 6097 open issues

Browse by type

Functions 655 Types & classes 91
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

rustc_codegen_jvm

License: MIT/Apache-2.0 CI Rust: Pinned Nightly Java: 8+

A custom Rust compiler backend that compiles Rust directly to Java Virtual Machine (JVM) bytecode, enabling you to compile crates into a runnable .jar compatible with Java 8+.

Demo

This backend transparently compiles Rust constructs to Java classes and interfaces, enabling rich interop between JVM and Rust code at a level mostly unreachable by traditional FFI solutions.

It also enables modern Rust code to run on older platforms outside the reach of current native targets, and has integrated upstream changes into OpenJDK's C2 JIT compiler which can make the JVM faster for everyone, including ~1.85x faster 128-bit multiplication on x86.

By leveraging a "virtual MMU" translation layer, it supports raw pointers with complex pointer arithmetic, transmute, and unions. It also supports key parts of the Rust standard library, including networking, async/await, threading, unwinding, allocation, as well as file system operations, STDIO, and more.

Every selected official Rust coretests and alloctests test passes in CI in both debug and release mode. CI currently verifies 2,812 of 2,817 coretests and 1,474 of 1,477 alloctests, which is roughly 99.8% of the upstream test suites.

[!NOTE] This project is in an active mid-stage of development. While it supports the vast majority of the Rust language, edge-case bugs are continually being ironed out. The ultimate goal is potential upstreaming into main rustc.

Stars, contributions, and feedback are highly welcome and appreciated!

Quickstart

Clone the repo and install cargo-jvm

If on Windows, please use PowerShell so $PWD will work.

git clone https://github.com/IntegralPilot/rustc_codegen_jvm
cd rustc_codegen_jvm
cargo install --path cargo-jvm
cargo jvm setup "$PWD"

Make a new hello_world project

cargo-jvm installs and selects the backend's pinned Rust nightly automatically; your default toolchain can remain stable.

cargo new hello_world --bin
cd hello_world

Build and run on JVM

cargo jvm run

You should see "Hello, world!" printed to the console.

Then, head down to Usage to learn how to integrate it into your project.

Table of Contents

  1. Why is this useful?
  2. Demos
  3. Features & Standard Library Support
  4. Current Limitations
  5. How It Works
  6. Interop Model
  7. Prerequisites
  8. Usage
  9. Running Tests
  10. Project Structure
  11. Contributing
  12. License

Why is this useful?

Interop is deeper and more ergonomic than FFI or bridge solutions

Rust enums, generics, function pointers, unions, and traits map directly onto JVM classes and interfaces (see Interop Model). Because of this, rustc_codegen_jvm achieves a level of ergonomic interop with Java that native FFI solutions cannot easily match. For example, you can implement a Rust trait directly on a Java class and pass it as &dyn Trait to Rust (test and demo), or pass a standard Java lambda directly to a Rust function expecting a Fn closure (test and demo).

Java

import org.rustlang.runtime.Utf8View;
import my_crate.NamedCounter;
import my_crate.Accumulator;
import my_crate.Calculation;
import my_crate.NetworkEvent;
import my_crate.AppEvent;
import java.time.LocalDate;
import static my_crate.my_crate.*;

public class Main {
    // Implement a Rust trait directly on any Java class
    private static class JavaAccumulator implements Accumulator {
        private int sum = 0;

        @Override
        public int add(int amount) {
            this.sum += amount;
            return this.sum;
        }
    }

    // Ordinary Java fields and constructors can be imported by Rust.
    public static int sharedCount = 10;

    public static final class JavaCounter {
        public int value;

        public JavaCounter(int value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        // 1. Interact with Rust types and methods
        NamedCounter counter = NamedCounter.new(Utf8View.fromJavaString("JVM-Counter"));
        counter.increment();
        System.out.println("Counter: " + counter.count);

        // 2. Construct, inspect, compare, and call methods on Rust enums
        Calculation calculation = new Calculation.Success(42);
        Calculation sameCalculation = new Calculation.Success(42);
        int payload = ((Calculation.Success) calculation).field0;
        System.out.println("Enum payload: " + payload);
        System.out.println("Enum method: " + calculation.value_or(-1));
        System.out.println("Enum equality: " + Calculation.eq(calculation, sameCalculation));

        // A transparent enum subtype needs no AppEvent.Network wrapper.
        NetworkEvent network = new NetworkEvent.Connected(8080);
        AppEvent event = network;
        System.out.println("Outer variant: " + AppEvent.variantIndex(event));
        System.out.println("Trait method: " + event.code());
        System.out.println("Rust match: " + inspect_event(event));

        // 3. Pass a standard Java lambda directly to a Rust Fn closure
        int result = apply_twice(val -> val * 3, 2);
        System.out.println("Lambda output: " + result);

        // 4. Pass a Java trait implementation to Rust dynamic dispatch
        JavaAccumulator acc = new JavaAccumulator();
        int finalSum = run_accumulation(acc);
        System.out.println("Accumulator sum: " + finalSum);

        // 5. Construct and call a standard Java API object in Rust
        LocalDate leapDay = make_java_date(2024, 2, 29);
        System.out.println("Leap day: " + leapDay);
        System.out.println("Leap year: " + java_date_year(leapDay));

        // 6. Construct a Java object and access its fields from Rust
        System.out.println("Java field result: " + update_java_counter());
    }
}

Rust

#![feature(extern_types, register_tool)]
#![register_tool(jvm)]

unsafe extern "C" {
    #[link_name = "java/time/LocalDate"]
    pub type JavaLocalDate;

    #[link_name = "jvm:static:java/time/LocalDate:of"]
    fn java_local_date_of(year: i32, month: i32, day: i32) -> *const JavaLocalDate;

    #[link_name = "jvm:virtual:getYear"]
    fn java_local_date_get_year(date: &JavaLocalDate) -> i32;

    #[link_name = "Main$JavaCounter"]
    pub type JavaCounter;

    #[link_name = "jvm:new:Main$JavaCounter"]
    fn java_counter_new(value: i32) -> *mut JavaCounter;

    // A return value makes this an instance-field getter.
    #[link_name = "jvm:field:value"]
    fn java_counter_value(counter: &JavaCounter) -> i32;

    // A value parameter and () return make this an instance-field setter.
    #[link_name = "jvm:field:value"]
    fn java_counter_set_value(counter: &mut JavaCounter, value: i32);

    #[link_name = "jvm:static-field:Main:sharedCount"]
    fn shared_count() -> i32;

    #[link_name = "jvm:static-field:Main:sharedCount"]
    fn set_shared_count(value: i32);
}

impl JavaLocalDate {
    pub fn year(&self) -> i32 {
        unsafe { java_local_date_get_year(self) }
    }
}

pub struct NamedCounter {
    pub name: &'static str,
    pub count: u32,
}

impl NamedCounter {
    pub fn new(name: &'static str) -> Self {
        NamedCounter { name, count: 0 }
    }
    pub fn increment(&mut self) {
        self.count += 1;
    }
}

pub enum Calculation {
    Success(i32),
    Failure(i32),
}

impl Calculation {
    pub fn value_or(&self, fallback: i32) -> i32 {
        match self {
            Calculation::Success(value) => *value,
            Calculation::Failure(_) => fallback,
        }
    }
}

pub enum NetworkEvent {
    Connected(i32),
    Disconnected,
}

pub enum AppEvent {
    // NetworkEvent extends AppEvent on the JVM; AppEvent$Network is omitted.
    #[jvm::subtype]
    Network(NetworkEvent),
    Calculation(Calculation),
}

pub trait EventCode {
    fn code(&self) -> i32;
}

impl EventCode for AppEvent {
    fn code(&self) -> i32 {
        match self {
            AppEvent::Network(NetworkEvent::Connected(port)) => *port,
            AppEvent::Network(NetworkEvent::Disconnected) => -1,
            AppEvent::Calculation(value) => value.value_or(-1),
        }
    }
}

pub fn inspect_event(event: AppEvent) -> i32 {
    event.code()
}

pub fn apply_twice(callback: &dyn Fn(i32) -> i32, value: i32) -> i32 {
    callback(callback(value))
}

pub trait Accumulator {
    fn add(&mut self, value: i32) -> i32;
}

pub fn run_accumulation(acc: &mut dyn Accumulator) -> i32 {
    acc.add(10) + acc.add(5)
}

pub fn make_java_date(year: i32, month: i32, day: i32) -> *const JavaLocalDate {
    unsafe { java_local_date_of(year, month, day) }
}

pub fn java_date_year(date: &JavaLocalDate) -> i32 {
    date.year()
}

pub fn update_java_counter() -> i32 {
    unsafe {
        let counter = java_counter_new(5);
        java_counter_set_value(&mut *counter, java_counter_value(&*counter) + 1);
        set_shared_count(shared_count() + 1);
        java_counter_value(&*counter) + shared_count()
    }
}

Runs everywhere a JVM does, even on legacy systems

Because the compiler targets standard JVM bytecode rather than native machine code, compiled output can run on platforms far outside the reach of modern native Rust targets. It supports any environment with JVM 8+ compatibility.

Operating System Native Rust Minimum JVM 8 (rustc_codegen_jvm)
Windows Windows 10 Windows Vista SP2 / 7 SP1
macOS 10.12 Sierra 10.8.3 Mountain Lion
Linux Kernel 3.2, glibc 2.17 Kernel 2.6.28, glibc 2.9+
Solaris Solaris 11.4 Solaris 10

Compiling directly to JVM bytecode also avoids the deployment friction of native shared libraries in restricted environments. This makes compiled JARs highly portable across sandboxed environments (such as Minecraft mod loaders) and Android platforms (via DEX conversion).

Benefits the wider JVM ecosystem through JIT compiler improvements

Developing this backend helps inspire me to find opportunities to optimise OpenJDK's upstream HotSpot C2 compiler. Contributions benefit the entire JVM ecosystem (including Java and Kotlin).

One merged optimisation (OpenJDK PR #30174) sped up 128-bit multiplication by ~1.85x on x86 targets. Another contribution under review (OpenJDK PR #30485) introduces internal range-check elimination in loops for common compiled patterns.

Facilitates gradual migration of JVM codebases to Rust

Transitioning a large production JVM codebase to native Rust is rarely feasible in a single step. rustc_codegen_jvm enables an incremental migration path where new or refactored components are written in Rust while remaining fully compatible with the existing JVM application. Once a rewrite is complete, the Rust code can either be target-switched to native or kept on the JVM target for fast iteration and cross-platform consistency.

Rapid debugging and hot reloading iteration

Once shared standard-library artifacts are cached, incremental compilation for crates is fast. Leveraging the JVM's mature debugging, hot-reloading, and tracing ecosystem (such as JFR and IDE debuggers) opens up rapid iteration workflows that are traditionally difficult with native Rust targets.

Additionally, the virtual MMU layer can catch raw pointer Undefined Behavior (UB) early, throwing structured Java exceptions with accurate stack traces and LineNumberTable information.

Demos

The following example programs live in tests/, are compiled with the standard library to JVM bytecode, and are verified in CI on every commit:

Standard Library Demonstrations

Example Demonstrates
Alloc Complex allocations: binary trees, heaps, linked lists, vectors, strings, Arc/atomics, and drop/cleanup semantics.
Threads Multi-threading, scoped threads, mutexes (with poisoning), RWLocks, barriers, condition variables, and TLS.
Panic Unwinding, catching static/dynamic panic payloads, resuming unwinds, and custom panic hooks.
Async / Await Multi-poll futures, nested and recursive async work, async closures and trait methods, dyn Future, cross-thread execution, cancellation, and unwinding across suspension points.
STD File system operations, command-line arguments, environment variables, standard I/O, and runtime context.
**[

Extension points exported contracts — how you extend this code

browse all types & interfaces →

Core symbols most depended-on inside this repo

browse all functions →

Shape

Function 451
Method 204
Class 69
Enum 20
Interface 2

Languages

Rust85%
Python8%
Java7%

Modules by API surface

src/lower2/optimise2.rs59 symbols
src/lower2/stackmaps.rs57 symbols
src/oomir.rs48 symbols
src/lower2/translator.rs45 symbols
src/oomir/interpret.rs39 symbols
src/lower1/types.rs33 symbols
tests/binary/modules/src/main.rs29 symbols
src/lower2/constant_pool.rs25 symbols
tests/binary/fn_pointers/src/main.rs23 symbols
Instrument.py22 symbols
src/lib.rs21 symbols
src/optimise1/copyprop.rs20 symbols

For agents

$ claude mcp add rustc_codegen_jvm \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page