JVM Backend
JVM backend design
Goals
The JVM backend is a backend for Fuzion that create Java bytecode to run on a JVM. There will be two major modes of operation:
-
On-the-fly generation of bytecode that is added to a running JVM using System.defineClass or similar APIs, and
-
Static generation of Java .jar or .jmod files that contain the code for a Fuzion application.
The goal is to avoid overhead as much as possible and map Fuzion code to Java instances as much as possible, to even create Java code that is better than manually written Java code (e.g., by avoiding wrapping value types into heap instances and hence avoiding allocation and increasing performance).
Object model
The object model defines how Fuzion instances are represented in the target code.
Primitive Features
Where possible, Java primitive types (boolean
, byte
, char
, short
, int
,
long
, float
, double
) are used to hold Fuzion instances. Note that some
Java primitives (boolean
, byte
, char
, short
) cannot be used to hold
values on the Java stack, so these will be int
when on the stack.
Fuzion feature | Java type |
---|---|
bool |
boolean / int (on Java stack) |
u8 |
byte / int (on Java stack) |
i8 |
byte / int (on Java stack) |
u16 |
char / int (on Java stack) |
i16 |
short / int (on Java stack) |
u32 |
int |
i32 |
int |
u64 |
long |
i64 |
long |
f32 |
float |
f64 |
double |
Value Types
Value types will be split up into several primitive values, e.g., a Fuzion type
point(x,y f64, col u32).
will be represented by three Java values
double point_x
double point_y
int point_col
Whenever a value type is passed as an argument or assigned to a field this will be performed as several assignments in the Java code.
Reference Features
A reference feature corresponds to a Java class whose fields represent the
corresponding value feature. There should be one Java class generated for every
Fuzion clazz, that class should inherit from an abstract class FuzionInstance
that may define methods required by the JVM backend.
There should be no need for additional type information since different reference features will become different Java classes.
Boxed value types
Boxed value types could be represented just like reference types.
Choice Features
A choice feature in general would be a value type consisting of a tag
of type
int
in combination with all the possible variants of the choice type.
Several variants of reference type could be collapsed into a single Java field.
A choice consisting of only disjoint reference types and disjoint unit type
values could be collapsed into a single reference type with special values used
for the unit types, including the special value null
. Note that types like
choice Any String
, which could be the result of type parameters in choice T
U
, do not consist of disjoint reference types.
Arrays
Arrays should be normal value types. fuzion.sys.internal_array T
, however,
needs special treatment:
Fuzion feature | Java type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
One Java array for each primitive or ref part of |
Mutable value Types
Value types that are immutable can be copied in a call. Mutable ones, however, must not be copied and must be made accessible from outside.
example:
f is
x := mut 0
inc is x <- x.get + 1
show is say x.get
g is
cnt := f
cnt.inc # g.cnt must be heap allocated to be accessible be inc.
Value types or primitive types that are passed to a call by reference since their fields are mutable will need special handling. Since Java does not permit accesses to local variables from outside of a method, these will need to be stored in a heap allocated object, even if their outer feature is not a ref feature.
There are basically two approaches to pass such value fields
-
passing an accessor-instance that contains a ref to the enclosing instance together with getter- and setter- methods to access the fields:
abstract class Access_F { abstract int get_x_mutable_value(FuzionInstance cur); abstract void set_x_mutable_value(FuzionInstance cur, int v); }
void f_inc(FuzionInstance cur, Access_f acc) { // using inlining here to make this example work: acc.set_mutable_value(cur, acc.get_mutable_value(acc) + 1); }
class G extends FuzionInstance { int x_mutable_value; // inlined field mutate.new.mutable_value }
static Access_F access_G_x = new Access_f() { void get_mutable_value(FuzionInstance cur) { return ((G) cur).x_mutable_value; } void set_mutable_value(FuzionInstance cur, int v) { ((G) cur).x_mutable_value = v; } });
static void g() { var cur = new G(); cur.x_mutable_value = f(); f_inc(cur, access_G_x); }
This might turn out difficult since if we would not inline the calls made in
f.inc
we need to wrap the access wrapper into another access wrapper for callingmutate.new.get
andmutate.new.infix <-
. -
Since mutating value fields should happen only for the target of a call, we could specialize the call for the location of the value in the surrounding instance
class G extends FuzionInstance { int x_mutable_value; // inlined field mutate.new.mutable_value }
static void g() { var cur = new G(); f_inc_for_G_cnt(cur); }
static void f_inc_for_G_cnt(G cur) { mutate_new_i32_set_for_G_cnt_x(cur, mutate_new_i32_get_for_G_cnt_x(cur) + 1); }
static void mutate_new_i32_get_for_G_cnt_x(G cur) { cur.x_mutable_value; }
static void mutate_new_i32_set_for_G_cnt_x(G cur, int v) { cur.x_mutable_value = v; }
This seems simpler.
Function results
Function result types that are ref types or primitive types in Java can be returned like Java values.
To return other value types, we have to either
-
wrap these into a heap allocated instance
-
pass an accessor-instance that contains a ref to the enclosing instance that should receive the result
-
use thread local vars to return the result
-
specialize the call for where the result is stored
-
add an extra argument to all methods that refers to a thread instance that contains fields that can be used as thread local vars without the overhead of going through Java’s
ThreadLocal
API.
Calls
Statically Bound Calls
Calls to features whose target is a value type or whose target is a single Fuzion type do not need dynamic lookup. The called features should be static features.
Dynamically Bound Calls
Dynamically bounds calls are performed on a reference target, which means the target instance has a unique corresponding Java class. This means we could leverage the Java class to perform this call, either by
-
adding an
id()
method toFuzionInstance
that is redefined for each ref type to return the corresponding clazz id. Alookupswitch
could then be used to perform the call (in O(log n)!) -
adding interface classes for fuzion features that contain interface methods for inner features that are implemented by all heir features implementing these. Then, an
invokeinterface
could be used, which uses a cached search (also in O(log n)), but this is currently being improved (JEP-8180450). A two-layered lookup could reduce the call overhead to O(1) JVM01/siebert.pdf.
Assignments
In Fuzion, an assignment may require dynamic binding if the assigned field is in an outer instance that is a reference type. So we must handle assignment like dynamically bound calls in this case.
Java interface
The JVM backend should be aware of the Java interfaces created by fzjava
and
perform inline calls to the corresponding Java code. Use of reflection
should be avoided as much as possible.