Skip to content

Commit 398bf13

Browse files
committed
Add support for providing more than 1 argument to format!, arrays, and floats.
- Introduced new formatting functions in Core.kt for displaying integers, booleans, doubles, and general objects. - Implemented `arguments_new_v1` to format strings with arguments, rather than just a simple string. - Enhanced lower1.rs to map specific Rust types to JVM types, including handling for `core::fmt::Arguments` and arrays. - Improved handling of scalar types in lower1.rs, including float types. - Updated lower2.rs to manage local variable types and ensure type consistency during assignments. - Added support for array creation and storage in lower2.rs. - Introduced new tests for panic scenarios, ensuring proper formatting and output. - Introduced proper linking to Kotlin's stdlib, and ensured these files can be excluded from asm-processor.
1 parent 571bf53 commit 398bf13

File tree

20 files changed

+703
-115
lines changed

20 files changed

+703
-115
lines changed

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ clean-java-linker:
4646
cd java-linker && cargo clean
4747

4848
# === Library shim metadata generator ===
49-
shim-metadata-gen: library
49+
# needs to clean first so unzip doesn't prompt for overwrite
50+
shim-metadata-gen: clean-library library
5051
@echo "$(CYAN)🔧 Generating shim metadata...$(RESET)"
5152
cd shim-metadata-gen && rm -f core.json && cargo run -- ../library/build/libs/library-0.1.0.jar ./core.json
5253

@@ -66,7 +67,7 @@ clean-asm-processor:
6667
# === Standard Library Shim (Gradle) ===
6768
library:
6869
@echo "$(CYAN)📚 Building standard library shim...$(RESET)"
69-
cd library && gradle build
70+
cd library && gradle build && cd build/distributions && unzip library-0.1.0.zip
7071

7172
clean-library:
7273
@echo "$(CYAN)🧹 Cleaning library shim...$(RESET)"

Readme.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,15 @@ To compile *your own* Rust project using this backend:
8484
rustflags = [
8585
"-Z", "codegen-backend=/path/to/rustc_codegen_jvm/target/debug/librustc_codegen_jvm.dylib",
8686
"-C", "linker=/path/to/rustc_codegen_jvm/java-linker/target/debug/java-linker",
87-
"-C", "link-args=--asm-processor /path/to/rustc_codegen_jvm/asm-processor/build/libs/asm-processor-1.0-SNAPSHOT-all.jar"
87+
"-C", "link-args=/path/to/rustc_codegen_jvm/library/build/distributions/library-0.1.0/lib/library-0.1.0.jar /path/to/rustc_codegen_jvm/library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar --asm-processor /path/to/rustc_codegen_jvm/asm-processor/build/libs/asm-processor-1.0-SNAPSHOT-all.jar --known-good kotlin-stdlib"
8888
]
89+
90+
# Throwing a JVM exception will unwind and give a stack trace, no need for rust to handle unwinding.
91+
[profile.debug]
92+
panic = "abort"
93+
94+
[profile.release]
95+
panic = "abort"
8996
```
9097
* **Important:** Replace `/path/to/rustc_codegen_jvm/...` with the path to where you cloned the repository. If you're not on macOS, changed `.dylib` to `.so` for Linux or `.dll` for Windows.
9198

@@ -111,7 +118,7 @@ To compile *your own* Rust project using this backend:
111118
This project includes integration tests managed by a Python script.
112119

113120
1. **Ensure Toolchain is Built:** Build the project using `make all`.
114-
2. **Check Target JSON:** Make sure the `jvm-unknown-unknown.json` file in the *root* of this repository has the **relative paths** starting with `../../../` for the linker and backend, as the tester expects this structure when running tests from subdirectories. If you changed them to absolute paths for external use, change them back temporarily.
121+
2. **Check Target JSON:** Make sure the `jvm-unknown-unknown.json` file in the *root* of this repository has the **relative paths** starting with `/path/to/rustc_codegen_jvm/` for the linker and backend, as the tester expects this structure when running tests from subdirectories. If you changed them to absolute paths for external use, change them back temporarily.
115122
3. **Run the Tester:**
116123
```bash
117124
python3 Tester.py

Tester.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ def process_test(test_dir: str, release_mode: bool):
3939
print("|--- ⚒️ Building with Cargo...")
4040
build_cmd = ["cargo", "build", "--release"] if release_mode else ["cargo", "build"]
4141
no_jvm_target = os.path.join(test_dir, "no_jvm_target.flag")
42-
if os.path.exists(no_jvm_target):
43-
print("|---- ⚠️ Skipping JVM target build due to no_jvm_target.flag")
44-
else:
42+
if not os.path.exists(no_jvm_target):
4543
print("|---- 🛠️ Building with JVM target...")
4644
build_cmd.extend(["--target", "../../../jvm-unknown-unknown.json"])
4745
proc = run_command(build_cmd, cwd=test_dir)
@@ -56,7 +54,6 @@ def process_test(test_dir: str, release_mode: bool):
5654
print("|--- 🤖 Running with Java...")
5755
target_dir = "release" if release_mode else "debug"
5856
if os.path.exists(no_jvm_target):
59-
print("|---- ⚠️ Doing some needed moving around due to no_jvm_target.flag")
6057
jar_path = os.path.join(test_dir, "target", target_dir, "deps", f"{test_name}-*.jar")
6158
jar_file = None
6259
for file in os.listdir(os.path.join(test_dir, "target", target_dir, "deps")):
@@ -70,22 +67,40 @@ def process_test(test_dir: str, release_mode: bool):
7067
os.rename(os.path.join(test_dir, "target", target_dir, "deps", jar_file), os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir, f"{test_name}.jar"))
7168
jar_path = os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir, f"{test_name}.jar")
7269
proc = run_command(["java", "-jar", jar_path])
73-
if proc.returncode != 0:
74-
fail_path = os.path.join(test_dir, "java-fail.generated")
75-
output = f"STDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}"
76-
write_to_file(fail_path, output)
77-
print(f"|---- ❌ java exited with code {proc.returncode}")
78-
return False
70+
71+
# Check for java-returncode.expected
72+
expected_returncode_file = os.path.join(test_dir, "java-returncode.expected")
73+
if os.path.exists(expected_returncode_file):
74+
expected_returncode = int(read_from_file(expected_returncode_file).strip())
75+
if proc.returncode != expected_returncode:
76+
fail_path = os.path.join(test_dir, "java-returncode-fail.generated")
77+
output = f"Expected return code: {expected_returncode}\nActual return code: {proc.returncode}\n\nSTDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}"
78+
write_to_file(fail_path, output)
79+
print(f"|---- ❌ java exited with code {proc.returncode}, expected {expected_returncode}")
80+
return False
81+
else:
82+
if proc.returncode != 0:
83+
fail_path = os.path.join(test_dir, "java-fail.generated")
84+
output = f"STDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}"
85+
write_to_file(fail_path, output)
86+
print(f"|---- ❌ java exited with code {proc.returncode}")
87+
return False
7988

80-
# Compare the STDOUT to {test_dir}/java_output.expected
81-
expected_file = os.path.join(test_dir, "java_output.expected")
89+
# Compare the STDOUT and STDERR to {test_dir}/java-output.expected
90+
expected_file = os.path.join(test_dir, "java-output.expected")
8291
if os.path.exists(expected_file):
8392
expected_output = read_from_file(expected_file)
84-
actual_output = proc.stdout.strip()
93+
# special case: blank file = no STDOUT or STDERR expected
94+
if expected_output.strip() == "":
95+
expected_output = "STDOUT:STDERR:"
96+
else:
97+
expected_output = expected_output.replace("\n", "")
98+
actual_output = f"STDOUT:{proc.stdout.strip()}STDERR:{proc.stderr.strip()}"
99+
# remove all remaining newlines
100+
actual_output = actual_output.replace("\n", "")
85101
if actual_output != expected_output.strip():
86102
diff_path = os.path.join(test_dir, "output-diff.generated")
87-
diff_output = f"Expected:\n{expected_output}\n\nGot:\n{actual_output}"
88-
write_to_file(diff_path, diff_output)
103+
write_to_file(diff_path, actual_output)
89104
print("|---- ❌ java output did not match expected output")
90105
return False
91106
else:

java-linker/src/main.rs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ use tempfile::NamedTempFile;
1111
fn main() -> Result<(), i32> {
1212
let args: Vec<String> = env::args().collect();
1313
if args.len() < 3 {
14-
eprintln!("Usage: java-linker <input_files...> -o <output_jar_file> [--asm-processor <processor_jar>]");
14+
eprintln!("Usage: java-linker <input_files...> -o <output_jar_file> [--asm-processor <processor_jar>] [--known-good <identifier>]");
1515
return Err(1);
1616
}
1717

1818
let mut input_files: Vec<String> = Vec::new();
1919
let mut output_file: Option<String> = None;
2020
let mut processor_jar_path: Option<PathBuf> = None;
21+
let mut known_good_identifier: Option<String> = None;
2122

2223
let mut i = 1;
2324
while i < args.len() {
@@ -38,11 +39,19 @@ fn main() -> Result<(), i32> {
3839
eprintln!("Error: --asm-processor flag requires a path");
3940
return Err(1);
4041
}
42+
} else if arg == "--known-good" {
43+
if i + 1 < args.len() {
44+
known_good_identifier = Some(args[i + 1].clone());
45+
i += 2;
46+
} else {
47+
eprintln!("Error: --known-good flag requires an identifier string");
48+
return Err(1);
49+
}
4150
} else if !arg.starts_with("-") && (arg.ends_with(".class") || arg.ends_with(".jar")) {
4251
input_files.push(arg.clone());
4352
i += 1;
4453
} else {
45-
i += 1; // Ignore flags
54+
i += 1;
4655
}
4756
}
4857

@@ -51,7 +60,6 @@ fn main() -> Result<(), i32> {
5160
return Err(1);
5261
}
5362

54-
// if output_file doesn't end in .jar, add .jar
5563
if let Some(ref path) = output_file {
5664
if !path.ends_with(".jar") {
5765
eprintln!("Warning: Output file should end with .jar. Adding .jar extension.");
@@ -74,21 +82,18 @@ fn main() -> Result<(), i32> {
7482
return Err(1);
7583
}
7684

77-
// Prepare the regex for sanitizing the main class file name.
7885
let re = Regex::new(r"^(.*?)-[0-9a-f]+(\.class)$").unwrap();
7986
let main_class_name = main_classes.first().map(|class_path| {
8087
let file_name = Path::new(class_path)
8188
.file_name()
8289
.unwrap()
8390
.to_str()
8491
.unwrap();
85-
// Sanitize the file name if it matches the pattern.
8692
let cleaned_name = if let Some(caps) = re.captures(file_name) {
8793
format!("{}{}", &caps[1], &caps[2])
8894
} else {
8995
file_name.to_string()
9096
};
91-
// Remove the ".class" extension and replace "/" with "." to get the fully qualified name.
9297
cleaned_name.trim_end_matches(".class").replace("/", ".")
9398
});
9499

@@ -100,7 +105,13 @@ fn main() -> Result<(), i32> {
100105
}
101106
}
102107

103-
if let Err(err) = create_jar(&input_files, &output_file_path, main_class_name.as_deref(), processor_jar_path) {
108+
if let Err(err) = create_jar(
109+
&input_files,
110+
&output_file_path,
111+
main_class_name.as_deref(),
112+
processor_jar_path,
113+
known_good_identifier.as_deref(),
114+
) {
104115
eprintln!("Error creating JAR: {}", err);
105116
return Err(1);
106117
}
@@ -115,7 +126,6 @@ fn find_main_classes(class_files: &[String]) -> Vec<String> {
115126
let main_descriptor = b"([Ljava/lang/String;)V";
116127

117128
for file in class_files {
118-
// Only check .class files for main method bytes
119129
if file.ends_with(".class") {
120130
if let Ok(data) = fs::read(file) {
121131
let has_main_name = data.windows(main_name.len()).any(|w| w == main_name);
@@ -151,30 +161,38 @@ fn create_jar(
151161
output_jar_path: &str,
152162
main_class_name: Option<&str>,
153163
processor_jar_path: Option<&Path>,
164+
known_good_identifier: Option<&str>,
154165
) -> io::Result<()> {
155166
let output_file = fs::File::create(output_jar_path)?;
156167
let mut zip_writer = ZipWriter::new(output_file);
157168
let options = SimpleFileOptions::default()
158169
.compression_method(CompressionMethod::DEFLATE)
159170
.unix_permissions(0o644);
160171

161-
// Create META-INF/MANIFEST.MF
162172
let manifest_content = create_manifest_content(main_class_name);
163173
zip_writer.start_file("META-INF/MANIFEST.MF", options)?;
164174
zip_writer.write_all(manifest_content.as_bytes())?;
165175

166176
let re = Regex::new(r"^(.*?)-[0-9a-f]+(\.class)$").unwrap();
167-
168-
// Track all class files we've added to avoid duplicates
169177
let mut added_class_files = std::collections::HashSet::new();
170178

171179
for input_file_path_str in input_files {
172180
if input_file_path_str.ends_with(".jar") {
173181
println!("Processing JAR file: {}", input_file_path_str);
174182
let class_files = process_jar_file(input_file_path_str)?;
175-
183+
176184
for (class_path, class_data) in class_files {
177185
if !added_class_files.contains(&class_path) {
186+
if let Some(known_str) = known_good_identifier {
187+
if input_file_path_str.contains(known_str) {
188+
println!("Skipping ASM processing for known-good JAR file: {}", class_path);
189+
zip_writer.start_file(&class_path, options)?;
190+
zip_writer.write_all(&class_data)?;
191+
added_class_files.insert(class_path.clone());
192+
continue;
193+
}
194+
}
195+
178196
let processed_data = if let Some(processor_path) = processor_jar_path {
179197
let temp_out_file = NamedTempFile::new()?;
180198
let temp_in_file = NamedTempFile::new()?;
@@ -224,8 +242,18 @@ fn create_jar(
224242
};
225243

226244
if !added_class_files.contains(&jar_entry_name) {
227-
println!("Processing class file: {}", input_file_path_str);
245+
if let Some(known_str) = known_good_identifier {
246+
if input_file_path_str.contains(known_str) {
247+
println!("Skipping ASM processing for known-good class: {}", input_file_path_str);
248+
let file_data = fs::read(input_file_path_str)?;
249+
zip_writer.start_file(&jar_entry_name, options)?;
250+
zip_writer.write_all(&file_data)?;
251+
added_class_files.insert(jar_entry_name);
252+
continue;
253+
}
254+
}
228255

256+
println!("Processing class file: {}", input_file_path_str);
229257
let file_data = if let Some(processor_path) = processor_jar_path {
230258
let temp_out_file = NamedTempFile::new()?;
231259
let temp_out_path = temp_out_file.path().to_path_buf();

library/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ repositories {
1010
mavenCentral()
1111
}
1212

13+
dependencies {
14+
implementation(kotlin("stdlib"))
15+
}
16+
1317
kotlin {
1418
jvmToolchain(21) // latest LTS. note to self: update when they release a new LTS.
1519
}

library/src/main/kotlin/org/rustlang/core/Core.kt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ package org.rustlang.core
44
* Core functions needed by the Rust JVM backend stdlib shim.
55
*/
66
public object Core {
7-
8-
@JvmStatic // Ensures static JVM methods are generated
7+
@JvmStatic
98
public fun panic_fmt(message: String?) {
109
// note to future self: consider using a more specific Rust exception type if needed
1110
throw RuntimeException("Rust panic: " + (message ?: "<no message>"))
@@ -18,4 +17,43 @@ public object Core {
1817
// If the array is empty, it correctly returns an empty string.
1918
return pieces.joinToString("")
2019
}
20+
21+
@JvmStatic
22+
public fun core_fmt_rt_argument_new_display_i32(value: Int): String {
23+
// Convert the integer to a string.
24+
return value.toString()
25+
}
26+
27+
@JvmStatic
28+
public fun core_fmt_rt_argument_new_display(value: Any?): String {
29+
// Convert the value to a string.
30+
return value?.toString() ?: "null"
31+
}
32+
33+
@JvmStatic
34+
public fun core_fmt_rt_argument_new_display_bool(value: Boolean): String {
35+
// Convert the boolean to a string.
36+
return value.toString()
37+
}
38+
39+
@JvmStatic
40+
public fun core_fmt_rt_argument_new_display_f64(value: Double): String {
41+
// Convert the double to a string.
42+
return value.toString()
43+
}
44+
45+
@JvmStatic
46+
public fun arguments_new_v1(pieces: Array<String>, args: Array<Any?>): String {
47+
val sb = StringBuilder()
48+
var argIndex = 0
49+
for (i in pieces.indices) {
50+
sb.append(pieces[i]) // Append static piece
51+
if (argIndex < args.size) {
52+
// Append the corresponding argument (already formatted or convertible via toString)
53+
sb.append(args[argIndex]?.toString() ?: "null")
54+
argIndex++
55+
}
56+
}
57+
return sb.toString()
58+
}
2159
}

shim-metadata-gen/core.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@
33
"descriptor": "([Ljava/lang/String;)Ljava/lang/String;",
44
"is_static": true
55
},
6+
"core_fmt_rt_argument_new_display_i32": {
7+
"descriptor": "(I)Ljava/lang/String;",
8+
"is_static": true
9+
},
10+
"core_fmt_rt_argument_new_display": {
11+
"descriptor": "(Ljava/lang/Object;)Ljava/lang/String;",
12+
"is_static": true
13+
},
14+
"core_fmt_rt_argument_new_display_f64": {
15+
"descriptor": "(D)Ljava/lang/String;",
16+
"is_static": true
17+
},
18+
"core_fmt_rt_argument_new_display_bool": {
19+
"descriptor": "(Z)Ljava/lang/String;",
20+
"is_static": true
21+
},
22+
"arguments_new_v1": {
23+
"descriptor": "([Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;",
24+
"is_static": true
25+
},
626
"panic_fmt": {
727
"descriptor": "(Ljava/lang/String;)V",
828
"is_static": true

0 commit comments

Comments
 (0)