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 today★ 6286 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, structs, traits, and function pointers lower to ordinary JVM classes and interfaces rather than opaque native handles (see Interop Model). This enables unusually direct interop:

  • Implement a Rust trait on a JVM class and pass it to Rust as &dyn Trait (test and demo).
  • Pass Java or Kotlin lambdas to Rust Fn closures (test and demo).
  • Await Rust async functions from Kotlin suspend code while retaining Kotlin's coroutine dispatcher (test and demo).
  • Construct, inspect, and compare Rust enums as JVM interfaces and variant classes, including transparent enum subtypes (test and demo).
  • Call JVM constructors and access instance or static fields from Rust without JNI glue (test and demo).

For example, one Rust API can expose an enum and accept both a JVM implementation of a Rust trait and a standard JVM lambda. Its result can then cross the Rust/Kotlin async bridge. The complete example is kept executable in the Kotlin interop test suite:

Rust

pub trait BatchObserver {
    fn accept(&mut self, processed: u32) -> bool;
}

pub enum PipelineResult {
    Success { count: u32, elapsed_ms: u64 },
    Rejected(i32),
}

pub fn process_batch(
    batch_size: u32,
    transform: &dyn Fn(u32) -> u32,
    observer: &mut dyn BatchObserver,
) -> PipelineResult {
    let processed = transform(batch_size);
    if observer.accept(processed) {
        PipelineResult::Success {
            count: processed,
            elapsed_ms: u64::from(batch_size),
        }
    } else {
        PipelineResult::Rejected(-1)
    }
}

pub async fn confirm_batch(result: PipelineResult) -> PipelineResult {
    result
}

Kotlin

import my_crate.BatchObserver
import my_crate.PipelineResult
import org.rustlang.runtime.await

class LimitObserver(private val limit: Int) : BatchObserver {
    override fun accept(processed: Int): Boolean = processed <= limit
}

suspend fun main() {
    val prepared = my_crate.my_crate.process_batch(
        40,
        { value -> value + 2 },
        LimitObserver(100),
    )
    val outcome = my_crate.my_crate.confirm_batch(prepared)
        .await<PipelineResult>()

    when (outcome) {
        is PipelineResult.Success -> {
            val (count, elapsedMs) = outcome
            println("completed: $count in ${elapsedMs}ms")
        }
        is PipelineResult.Rejected -> {
            val (code) = outcome
            println("rejected: $code")
        }
        else -> error("unknown PipelineResult implementation")
    }
}

Single tuple payloads use value, multi-field tuples use _0, _1, and so on, and struct-like variants retain their field names. Kotlin can destructure any payload variant.

Expanded Java/Rust example covering methods, callbacks, enum subtypes, constructors, and fields

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).value;
        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

Add jvm = { package = "rcj", git = "https://github.com/IntegralPilot/rustc_codegen_jvm" } to [dependencies].

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

#[jvm::class("java.time.LocalDate", rename_all = "camelCase")]
impl JavaLocalDate {
    #[jvm::static_method]
    pub fn of(year: i32, month: i32, day: i32) -> *mut Self {}

    #[jvm::method]
    pub fn get_year(&self) -> i32 {}
}

#[jvm::class("Main.JavaCounter", rename_all = "camelCase")]
impl JavaCounter {
    #[jvm::constructor]
    pub fn new(value: i32) -> *mut Self {}

    #[jvm::field]
    pub fn value(&self) -> i32 {}

    #[jvm::field]
    pub fn set_value(&mut self, value: i32) {}

    #[jvm::static_field(class = "Main")]
    pub fn shared_count() -> i32 {}

    #[jvm::static_field(class = "Main")]
    pub fn set_shared_count(value: i32) {}
}

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_codegen::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 {
    JavaLocalDate::of(year, month, day)
}

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

pub fn update_java_counter() -> i32 {
    unsafe {
        let counter = JavaCounter::new(5);
        (&mut *counter).set_value((&*counter).value() + 1);
        JavaCounter::set_shared_count(JavaCounter::shared_count() + 1);
        (&*counter).value() + JavaCounter::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) introduce

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