Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
28efafc
fix bytecode sizes and metadata remapping
IntegralPilot Sep 29, 2026
d5da1fd
share codec adapters and array codecs
IntegralPilot Sep 29, 2026
154b769
bound weak metadata caches
IntegralPilot Sep 29, 2026
146ef8e
cache field projections
IntegralPilot Sep 29, 2026
202bc00
use component storage and shared carriers
IntegralPilot Sep 29, 2026
6dcd80e
load typed views directly
IntegralPilot Sep 29, 2026
1f8fd0a
observe component addresses directly
IntegralPilot Sep 29, 2026
27e2304
use component addresses for atomics
IntegralPilot Sep 29, 2026
2d1ca2a
avoid slice address wrappers
IntegralPilot Sep 30, 2026
a0c6819
copy and fill storage directly
IntegralPilot Sep 30, 2026
de7972c
share function call adapters
IntegralPilot Sep 30, 2026
6766f72
load compact constant byte blocks
IntegralPilot Sep 30, 2026
07fc163
track exact classfile dependencies
IntegralPilot Sep 30, 2026
1afb380
lower component storage operations
IntegralPilot Sep 30, 2026
125beac
simplify components and unreachable edges
IntegralPilot Sep 30, 2026
e02f9e8
decompose borrowed views
IntegralPilot Sep 30, 2026
339d739
flatten borrowed address ABI
IntegralPilot Sep 30, 2026
7454ca0
access aggregate fields directly
IntegralPilot Sep 30, 2026
993c755
preserve storage owners and borrows
IntegralPilot Sep 30, 2026
ac0d325
retain exact address layouts
IntegralPilot Sep 30, 2026
2f3214a
promote private aggregate values
IntegralPilot Oct 1, 2026
355b8ad
lower intrinsics to component addresses
IntegralPilot Oct 1, 2026
16e140a
make Java exports explicit
IntegralPilot Oct 1, 2026
7d7d014
model typed storage in OOMIR
IntegralPilot Oct 1, 2026
7b8fab1
lower memory operations through components
IntegralPilot Oct 1, 2026
f69ad11
lower borrows as storage locations
IntegralPilot Oct 1, 2026
c247ac0
flatten borrowed fields and calls
IntegralPilot Oct 1, 2026
1bde37e
erase transparent and nullable wrappers
IntegralPilot Oct 1, 2026
de26f1f
pack small values and scalar enum tags
IntegralPilot Oct 1, 2026
472b433
share codecs with exact Rust layouts
IntegralPilot Oct 1, 2026
d64c4a2
share private storage class shapes
IntegralPilot Oct 1, 2026
874e376
use compiled Rust drop glue and dispatch
IntegralPilot Oct 1, 2026
354a7a9
share function and closure adapters
IntegralPilot Oct 1, 2026
a30ee78
enable MIR inlining with exact pointer metadata
IntegralPilot Oct 2, 2026
57a5781
emit large constants as binary resources
IntegralPilot Oct 2, 2026
5e81a90
reuse upstream definitions and emit private owners
IntegralPilot Oct 2, 2026
05deaa4
prune and compact private classes
IntegralPilot Oct 2, 2026
13d8643
preserve reflective lookup for split codecs
IntegralPilot Oct 2, 2026
a6a6716
compress jars with zlib-rs
IntegralPilot Oct 2, 2026
29c9589
preserve scalar wrapper bitcasts
IntegralPilot Oct 3, 2026
9b59119
fold nested managed field reads
IntegralPilot Oct 3, 2026
3de3dce
promote nested private field cells
IntegralPilot Oct 3, 2026
90f5b3b
simplify managed value copies
IntegralPilot Oct 3, 2026
b205195
fuse owned slice reads and copies
IntegralPilot Oct 3, 2026
79a7acd
specialize owned element decoders
IntegralPilot Oct 3, 2026
48f2727
pass private records as scalar arguments
IntegralPilot Oct 3, 2026
42e21f6
fix linker test line endings on windows
IntegralPilot Oct 4, 2026
85f4efe
fix pointer fields in mixed-profile builds
IntegralPilot Oct 4, 2026
b1e6da8
lock mixed-profile iterator fixture
IntegralPilot Oct 4, 2026
da14838
simplify comparison branches
IntegralPilot Oct 4, 2026
153ee82
reuse field borrows and streamline memory views
IntegralPilot Oct 4, 2026
8388ae6
lower private array locations directly
IntegralPilot Oct 4, 2026
c56e96f
inline and scalarize private iterator state
IntegralPilot Oct 4, 2026
0d3f040
streamline memory view tracking
IntegralPilot Oct 4, 2026
4f549fb
speed up byte storage and fixed array access
IntegralPilot Oct 4, 2026
316aa8c
reuse pointer decode layouts and provenance
IntegralPilot Oct 4, 2026
e318908
link field projection metadata once
IntegralPilot Oct 4, 2026
bac35af
fold nested scalar address projections
IntegralPilot Oct 4, 2026
a86c20b
reuse private owned snapshots
IntegralPilot Oct 4, 2026
35fbc9a
remove redundant unwind and branch paths
IntegralPilot Oct 4, 2026
99ff1bd
simplify bounded slice address conversions
IntegralPilot Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 21 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ classes and interfaces rather than opaque native handles (see
without JNI glue
([test and demo](tests/integration/jvm_link_names/src/lib.rs)).

Java exposure is explicit: `pub` controls Rust visibility, while
`#[jvm_codegen::export]` on a public function, type, or module preserves its
Java-facing API. Marking a module includes its public descendants; marking a type
includes its public inherent methods. Register the tool with
`#![feature(register_tool)]` and `#![register_tool(jvm_codegen)]`.
For a library intended entirely for Java, add `#![feature(custom_inner_attributes)]`
and `#![jvm_codegen::export]` at the crate root, as in the examples below.
Mark the types Java constructs or inspects as well as the functions it calls.
Use concrete, nongeneric exports as Java entry points for generic Rust APIs.
Ordinary Rust dependencies remain internal: the compiler can flatten their
representations, merge their classes, and remove unused methods. Rebuild Rust
dependencies with the same backend; generated internal classes are not a stable
Java API. Importing existing Java classes with `jvm::class` or `jvm::interface`
does not require an export marker.

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
Expand All @@ -96,6 +111,10 @@ cross the Rust/Kotlin async bridge. The complete example is kept executable in
**Rust**

```rust
#![feature(register_tool, custom_inner_attributes)]
#![register_tool(jvm_codegen)]
#![jvm_codegen::export]

pub trait BatchObserver {
fn accept(&mut self, processed: u32) -> bool;
}
Expand Down Expand Up @@ -247,8 +266,9 @@ Add `jvm = { package = "rcj", git = "https://github.com/IntegralPilot/rustc_code
to `[dependencies]`.

```rust
#![feature(extern_types, register_tool)]
#![feature(extern_types, register_tool, custom_inner_attributes)]
#![register_tool(jvm_codegen)]
#![jvm_codegen::export]

#[jvm::class("java.time.LocalDate", rename_all = "camelCase")]
impl JavaLocalDate {
Expand Down
53 changes: 48 additions & 5 deletions Tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import shutil
import subprocess
import sys
import zipfile
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

Expand All @@ -31,6 +32,13 @@ def read(path: Path) -> str:
return path.read_text(encoding="utf-8")


def normalize_internal_symbols(text: str) -> str:
"""Private owner placement and Rust identity hashes are not debug semantics."""
text = re.sub(r"\b[\w.$]+\.mono\.Mono(?:Bucket)?(?:_[0-9a-f]+)?\b", "private.Mono", text)
text = re.sub(r"\bMono(?:Bucket)?_[0-9a-f]+\b", "Mono", text)
return re.sub(r"(?:\$[0-9a-f]{16}|\$m\$*[0-9a-f]+)(?=\()", "", text)


def write_failure(path: Path, proc) -> None:
path.write_text(
f"STDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}",
Expand Down Expand Up @@ -69,8 +77,10 @@ def check_results(proc, test: TestCase, release: bool, logs: list[str]) -> bool:
return True

# so it works on Windows
expected = "".join(read(expected_path).strip().split()).replace("\\", "/")
actual = "".join(f"STDOUT:{proc.stdout.strip()}STDERR:{proc.stderr.strip()}".split()).replace("\\", "/")
expected = normalize_internal_symbols(read(expected_path))
actual = normalize_internal_symbols(f"STDOUT:{proc.stdout.strip()}STDERR:{proc.stderr.strip()}")
expected = "".join(expected.strip().split()).replace("\\", "/")
actual = "".join(actual.split()).replace("\\", "/")

if actual == expected:
logs.append("|--- ✅ Output matches expected output!")
Expand Down Expand Up @@ -350,6 +360,7 @@ def javap_debug_info(output: str, included_methods: set[str] | None = None) -> s
source copies and reordering branches changes the executable line sequence.
"""
source: str | None = None
current_source: str | None = None
methods: dict[str, list[str]] = {}
local_rows: dict[str, set[str]] = {}
current_method: str | None = None
Expand All @@ -358,19 +369,22 @@ def javap_debug_info(output: str, included_methods: set[str] | None = None) -> s
for line in output.splitlines():
stripped = line.strip()
if stripped.startswith("Compiled from "):
source = stripped
current_source = stripped
continue
if (
line.startswith(" ")
and not line.startswith(" ")
and "(" in stripped
and stripped.endswith(";")
):
stripped = normalize_internal_symbols(stripped)
current_method = (
stripped
if included_methods is None or stripped in included_methods
else None
)
if current_method is not None:
source = current_source
table = None
continue
if stripped in {"LineNumberTable:", "LocalVariableTable:"}:
Expand Down Expand Up @@ -440,9 +454,16 @@ def check_javap_debug_info(
if not expected_path.exists():
return True

class_name = f"{test.artifact_name}.{test.artifact_name}"
class_names = [f"{test.artifact_name}.{test.artifact_name}"]
# Private Rust methods can move between classes. Debug checks must include their current owners.
with zipfile.ZipFile(jar) as archive:
class_names.extend(sorted(
name.removesuffix(".class").replace("/", ".")
for name in archive.namelist()
if "/mono/Mono" in name and name.endswith(".class")
))
logs.append("|--- 🔎 Checking JVM debug metadata with javap...")
proc = run_command(["javap", "-classpath", str(jar), "-c", "-l", "-p", class_name])
proc = run_command(["javap", "-classpath", str(jar), "-c", "-l", "-p", *class_names])
if proc.returncode != 0:
write_failure(test.directory / "javap-fail.generated", proc)
logs.append(f"|---- ❌ javap exited with code {proc.returncode}")
Expand All @@ -466,6 +487,26 @@ def check_javap_debug_info(
return False


def check_scalar_paths(test: TestCase, jar: Path, release: bool, logs: list[str]) -> bool:
expected = test.directory / "scalar-paths.expected"
if not release or not expected.exists():
return True
from test_codegen import scalar_path_errors
proc = run_command(["javap", "-classpath", str(jar), "-c", "-p", f"{test.artifact_name}.{test.artifact_name}"])
if proc.returncode:
write_failure(test.directory / "javap-fail.generated", proc)
logs.append("|---- ❌ Could not inspect generated bytecode")
return False
errors = scalar_path_errors(proc.stdout, set(read(expected).split()))
if errors:
(test.directory / "scalar-paths-fail.generated").write_text("\n".join(errors) + "\n", encoding="utf-8")
logs.append("|---- ❌ Runtime work remains on a normal return path")
ci_diagnostic(logs, "\n".join(errors))
return False
logs.append("|--- ✅ Scalar paths contain no runtime calls or object allocations")
return True


def run_test(
test: TestCase,
release: bool,
Expand Down Expand Up @@ -494,6 +535,8 @@ def run_test(
return False, logs
if not check_javap_debug_info(test, jar, release, logs):
return False, logs
if not check_scalar_paths(test, jar, release, logs):
return False, logs
logs.append("|--- ✅ Test passed!")
return True, logs

Expand Down
Loading
Loading