Skip to content

Commit 6d3913d

Browse files
authored
implement multi-return support for Java bindings (#339)
* implement multi-return support for Java bindings This also adds a records/wasm.java test and makes flag constants a bit more ergonomic to use. It also tweaks the primitive code formatter to consolodate empty lines. Signed-off-by: Joel Dice <[email protected]> * add tests/runtime/variants/wasm.java Signed-off-by: Joel Dice <[email protected]> Signed-off-by: Joel Dice <[email protected]>
1 parent 362accb commit 6d3913d

File tree

4 files changed

+176
-17
lines changed

4 files changed

+176
-17
lines changed

crates/gen-guest-teavm-java/src/lib.rs

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,18 @@ impl TeaVmJava {
193193
func.results.iter_types().next().unwrap(),
194194
qualifier,
195195
),
196-
_ => todo!("multiple return values"),
196+
count => {
197+
self.tuple_counts.insert(count);
198+
format!(
199+
"{}Tuple{count}<{}>",
200+
qualifier.unwrap_or(""),
201+
func.results
202+
.iter_types()
203+
.map(|ty| self.type_name_boxed(iface, ty, qualifier))
204+
.collect::<Vec<_>>()
205+
.join(", ")
206+
)
207+
}
197208
};
198209

199210
let params = func
@@ -335,13 +346,15 @@ impl Generator for TeaVmJava {
335346
.iter()
336347
.enumerate()
337348
.map(|(i, flag)| {
338-
let name = flag.name.to_shouty_snake_case();
349+
let flag_name = flag.name.to_shouty_snake_case();
339350
let suffix = if matches!(flags.repr(), FlagsRepr::U32(2)) {
340351
"L"
341352
} else {
342353
""
343354
};
344-
format!("public static final {ty} {name} = ({ty}) (1{suffix} << {i});")
355+
format!(
356+
"public static final {name} {flag_name} = new {name}(({ty}) (1{suffix} << {i}));"
357+
)
345358
})
346359
.collect::<Vec<_>>()
347360
.join("\n");
@@ -604,7 +617,7 @@ impl Generator for TeaVmJava {
604617
let result_type = match &sig.results[..] {
605618
[] => "void",
606619
[result] => wasm_type(*result),
607-
_ => todo!("multi-value return"),
620+
_ => unreachable!(),
608621
};
609622

610623
let camel_name = func.name.to_upper_camel_case();
@@ -658,7 +671,7 @@ impl Generator for TeaVmJava {
658671
let result_type = match &sig.results[..] {
659672
[] => "void",
660673
[result] => wasm_type(*result),
661-
_ => todo!("multi-value return"),
674+
_ => unreachable!(),
662675
};
663676

664677
let camel_name = func.name.to_upper_camel_case();
@@ -1606,18 +1619,47 @@ impl Bindgen for FunctionBindgen<'_> {
16061619
}
16071620

16081621
Instruction::CallInterface { module, func } => {
1609-
let assignment = match func.results.len() {
1610-
0 => String::new(),
1622+
let (assignment, destructure) = match func.results.len() {
1623+
0 => (String::new(), String::new()),
16111624
1 => {
16121625
let ty = self
16131626
.gen
16141627
.type_name(iface, func.results.iter_types().next().unwrap());
16151628
let result = self.locals.tmp("result");
16161629
let assignment = format!("{ty} {result} = ");
16171630
results.push(result);
1618-
assignment
1631+
(assignment, String::new())
1632+
}
1633+
count => {
1634+
self.gen.tuple_counts.insert(count);
1635+
let ty = format!(
1636+
"Tuple{count}<{}>",
1637+
func.results
1638+
.iter_types()
1639+
.map(|ty| self.gen.type_name_boxed(iface, ty, None))
1640+
.collect::<Vec<_>>()
1641+
.join(", ")
1642+
);
1643+
1644+
let result = self.locals.tmp("result");
1645+
let assignment = format!("{ty} {result} = ");
1646+
1647+
let destructure = func
1648+
.results
1649+
.iter_types()
1650+
.enumerate()
1651+
.map(|(index, ty)| {
1652+
let ty = self.gen.type_name(iface, ty);
1653+
let my_result = self.locals.tmp("result");
1654+
let assignment = format!("{ty} {my_result} = {result}.f{index};");
1655+
results.push(my_result);
1656+
assignment
1657+
})
1658+
.collect::<Vec<_>>()
1659+
.join("\n");
1660+
1661+
(assignment, destructure)
16191662
}
1620-
_ => todo!("multiple return values"),
16211663
};
16221664

16231665
let module = module.to_upper_camel_case();
@@ -1629,6 +1671,7 @@ impl Bindgen for FunctionBindgen<'_> {
16291671
self.src,
16301672
"
16311673
{assignment}{module}Impl.{name}({args});
1674+
{destructure}
16321675
"
16331676
);
16341677
}
@@ -1654,13 +1697,16 @@ impl Bindgen for FunctionBindgen<'_> {
16541697
Memory.free(Address.fromInt(cleanup.address), cleanup.size, cleanup.align);
16551698
}}
16561699
"
1657-
);
1700+
);
16581701
}
16591702

1660-
match amt {
1703+
match *amt {
16611704
0 => (),
16621705
1 => uwriteln!(self.src, "return {};", operands[0]),
1663-
_ => unreachable!(),
1706+
count => {
1707+
let results = operands.join(", ");
1708+
uwriteln!(self.src, "return new Tuple{count}<>({results});")
1709+
}
16641710
}
16651711
}
16661712

@@ -1931,8 +1977,18 @@ fn list_element_info(ty: &Type) -> (usize, &'static str) {
19311977
fn indent(code: &str) -> String {
19321978
let mut indented = String::with_capacity(code.len());
19331979
let mut indent = 0;
1980+
let mut was_empty = false;
19341981
for line in code.lines() {
19351982
let trimmed = line.trim();
1983+
if trimmed.is_empty() {
1984+
if was_empty {
1985+
continue;
1986+
}
1987+
was_empty = true;
1988+
} else {
1989+
was_empty = false;
1990+
}
1991+
19361992
if trimmed.starts_with('}') {
19371993
indent -= 1;
19381994
}

crates/gen-guest-teavm-java/tests/codegen.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,19 @@ mod imports {
55
test_helpers::codegen_teavm_java_import!(
66
"*.wit"
77

8-
// TODO: implement async, resource, and multi-return support
8+
// TODO: implement async and resource support
99
"!async-functions.wit"
1010
"!resource.wit"
11-
"!multi-return.wit"
1211
);
1312
}
1413

1514
mod exports {
1615
test_helpers::codegen_teavm_java_export!(
1716
"*.wit"
1817

19-
// TODO: implement async, resource, and multi-return support
18+
// TODO: implement async and resource support
2019
"!async-functions.wit"
2120
"!resource.wit"
22-
"!multi-return.wit"
2321
);
2422
}
2523

tests/runtime/numbers/wasm.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import wit_imports.Imports;
44

55
public class ExportsImpl {
6-
76
public static byte roundtripU8(byte a) {
87
return a;
98
}

tests/runtime/records/wasm.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package wit_exports;
2+
3+
import wit_imports.Imports;
4+
5+
public class ExportsImpl {
6+
public static void testImports() {
7+
{
8+
Imports.Tuple2<Byte, Short> results = Imports.multipleResults();
9+
10+
expect(results.f0 == (byte) 4);
11+
expect(results.f1 == (short) 5);
12+
}
13+
14+
{
15+
Imports.Tuple2<Integer, Byte> results = Imports.swapTuple(new Imports.Tuple2<>((byte) 1, 2));
16+
17+
expect(results.f0 == 2);
18+
expect(results.f1 == (byte) 1);
19+
}
20+
21+
expect(Imports.roundtripFlags1(Imports.F1.A).value == Imports.F1.A.value);
22+
expect(Imports.roundtripFlags1(new Imports.F1((byte) 0)).value == (byte) 0);
23+
expect(Imports.roundtripFlags1(Imports.F1.B).value == Imports.F1.B.value);
24+
expect(Imports.roundtripFlags1(new Imports.F1((byte) (Imports.F1.A.value | Imports.F1.B.value))).value
25+
== (byte) (Imports.F1.A.value | Imports.F1.B.value));
26+
27+
expect(Imports.roundtripFlags2(Imports.F2.C).value == Imports.F2.C.value);
28+
expect(Imports.roundtripFlags2(new Imports.F2((byte) 0)).value == (byte) 0);
29+
expect(Imports.roundtripFlags2(Imports.F2.D).value == Imports.F2.D.value);
30+
expect(Imports.roundtripFlags2(new Imports.F2((byte) (Imports.F2.C.value | Imports.F2.E.value))).value
31+
== (byte) (Imports.F2.C.value | Imports.F2.E.value));
32+
33+
{
34+
Imports.Tuple4<Imports.Flag8, Imports.Flag16, Imports.Flag32, Imports.Flag64> results =
35+
Imports.roundtripFlags3(Imports.Flag8.B0, Imports.Flag16.B1, Imports.Flag32.B2, Imports.Flag64.B3);
36+
37+
expect(results.f0.value == Imports.Flag8.B0.value);
38+
expect(results.f1.value == Imports.Flag16.B1.value);
39+
expect(results.f2.value == Imports.Flag32.B2.value);
40+
expect(results.f3.value == Imports.Flag64.B3.value);
41+
}
42+
43+
{
44+
Imports.R1 result = Imports.roundtripRecord1(new Imports.R1((byte) 8, Imports.F1.A));
45+
46+
expect(result.a == (byte) 8);
47+
expect(result.b.value == Imports.F1.A.value);
48+
}
49+
50+
{
51+
Imports.R1 result = Imports.roundtripRecord1
52+
(new Imports.R1((byte) 0, new Imports.F1((byte) (Imports.F1.A.value | Imports.F1.B.value))));
53+
54+
expect(result.a == (byte) 0);
55+
expect(result.b.value == (byte) (Imports.F1.A.value | Imports.F1.B.value));
56+
}
57+
58+
Imports.tuple0(Imports.Tuple0.INSTANCE);
59+
60+
{
61+
Imports.Tuple1<Byte> result = Imports.tuple1(new Imports.Tuple1<>((byte) 1));
62+
63+
expect(result.f0 == 1);
64+
}
65+
}
66+
67+
public static Exports.Tuple2<Byte, Short> multipleResults() {
68+
return new Exports.Tuple2<>((byte) 100, (short) 200);
69+
}
70+
71+
public static Exports.Tuple2<Integer, Byte> swapTuple(Exports.Tuple2<Byte, Integer> tuple) {
72+
return new Exports.Tuple2<>(tuple.f1, tuple.f0);
73+
}
74+
75+
public static Exports.F1 roundtripFlags1(Exports.F1 a) {
76+
return a;
77+
}
78+
79+
public static Exports.F2 roundtripFlags2(Exports.F2 a) {
80+
return a;
81+
}
82+
83+
public static Exports.Tuple4<Exports.F8, Exports.F16, Exports.F32, Exports.F64> roundtripFlags3
84+
(Exports.F8 a, Exports.F16 b, Exports.F32 c, Exports.F64 d)
85+
{
86+
return new Exports.Tuple4<>(a, b, c, d);
87+
}
88+
89+
public static Exports.R1 roundtripRecord1(Exports.R1 a) {
90+
return a;
91+
}
92+
93+
public static Exports.Tuple0 tuple0(Exports.Tuple0 a) {
94+
return a;
95+
}
96+
97+
public static Exports.Tuple1<Byte> tuple1(Exports.Tuple1<Byte> a) {
98+
return a;
99+
}
100+
101+
private static void expect(boolean v) {
102+
if (!v) {
103+
throw new AssertionError();
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)