# Compile to JVM Bytecode

Give `rontolisp` an output path ending in `.class` with `-o`, and it compiles the
source straight to JVM bytecode instead of interpreting it -- no ASM or other
library, the bytecode is emitted by hand. The output extension is what selects the
backend (`.class` for JVM, `.wasm` for WASM).

```bash
echo '(print (+ 1 2))' > hello.lisp
rontolisp hello.lisp -o Hello.class
java Hello
```

The generated class is named after the output file, so the name you pass to
`java` is the file's stem: `-o Hello.class` produces a class `Hello` you run with
`java Hello`. Keep the path free of directories (use a plain `Hello.class`, not
`out/Hello.class`), since the class name must match. The program's top-level forms
become the class's entry point and run in order when you launch it.

Example (`hello.lisp`):

```lisp
(print (+ 1 2))
```

```
3
```

## Optimize (Dead-Code Elimination)

Compilation drops every method unreachable from `main`, along with any static field
only they referenced, and compacts the constant pool accordingly. You get that
without asking:

```bash
echo '(defun fact (n) (if (<= n 1) 1 (* n (fact (- n 1)))))
(print (fact 10))' > fact.lisp
rontolisp fact.lisp -o Fact.class
java Fact
```

For a small program like `fact` the class is ~6.5 KB. Pass `--optimize=off` and the
class instead embeds the **entire** runtime (printer, numeric, reader and `eval`
helper methods, plus a first-class wrapper for every built-in) regardless of what the
program actually uses, which for the same `fact` is ~190 KB. The elimination is
behavior-preserving: reachability follows the actual `invoke` instructions
in the bytecode, so anything a first-class function value, `funcall`, or an embedded
`eval`/`load` can dispatch to is kept, and the `java:` interop bridge's reflective
entry point survives as an explicit root. The same levels also tree-shake the
[WASM output](wasm.md).

The dispatch methods `funcall` goes through list only the functions your program
can actually obtain as a value — `#'name`, a quoted `'name` designator, a
`lambda`, or (while the program holds a symbol builder such as `intern` or
`find-symbol`) a string or keyword constant spelling the name — so everything
else becomes ordinary dead code the shaker removes. That listing switches off,
and every function stays reachable, only when the program can name a function
out of data this compile never sees: any use of `eval`, `read`,
`read-from-string`, a runtime `load` or a `~/name/`
[`format`](../reference/macros/format.md) directive — including one inside a
library you loaded — as does `--dynamic`. Compile with
`-Drontolisp.debug.dispatchgate=true` to have the compiler name the operator
responsible.

One carve-out follows from that: a designator assembled at run time out of
**computed** pieces — `(funcall (intern (concatenate 'string "gre" suffix)))` —
is no constant the compiler can read, so the call signals the ordinary
"undefined function" error. `--dynamic` is the way back. `--optimize=off` is
not: the listing is not part of what the level switches, so declining the
optimizer does not bring such a name back.

`--optimize` takes an optional level, shared with the [WASM backend](wasm.md).
`--optimize` and `--optimize=default` both spell what an absent flag already
selects — everything above — for a build script that wants it written down.
`--optimize=off` declines it, and emits what a build before the flag was on by
default emitted. `--optimize=size` asks for the smallest output a backend can
give; this backend accepts it and emits a byte-for-byte identical class, because
what that level declines are the wasm-GC emissions that spend bytes on speed and
there is no counterpart here -- the same program's JVM bytecode is about a third
the size of its WASM to begin with. So one build script can pass
`--optimize=size` for every target.

`--optimize=off` exists for two jobs, and neither of them is making a program
work: comparing an artifact against one built before a compiler change, and
bisecting a suspected shaker bug by asking whether the unshaken class behaves
differently. A program whose functions are reached only through a name the
compiler cannot read needs `--dynamic`, as above.

Independently of the level, compilation always tree-shakes the libraries it
splices in: the bundled Lisp-source ones (`linalg:`, `vec:`, JSON, URL,
`equalp`/`string<`) and every system loaded with
[`asdf:load-system` / `ql:quickload`](../guides/asdf-systems.md). A function,
variable or constant your program never mentions -- by name anywhere in the
source, including quoted symbols and string literals -- is not compiled in. Your
own code is never pruned, and neither is anything a `load`/`require` splices in:
only a library that came from a system is subject to it.

Classes, generic functions, methods, conditions and structures are pruned by
the same rule: a class nothing references leaves together with its methods,
and a method on a generic your program does call is still dropped when no
reachable code can create an instance of the class it specializes on. Methods
on the standard protocol names (`initialize-instance`, `print-object`,
`close`, ...) follow their class alone, since those calls are implicit.

The one consequence: a library function whose name is only assembled at runtime
from computed strings and called through `eval`/`apply` signals the usual
"undefined function" error. Compile with `--no-prune` (or `--dynamic`) to keep
every library definition in that case.

The generated `.class` file targets Java 17 (class version 61), so running it
requires a Java 17 or newer JRE. Beyond `java.lang` and `java.io`, the emitted
runtime helpers reference `java.math` (`BigInteger`/`BigDecimal`/`MathContext`,
for the overflow-promoting integer and exact ratio arithmetic) and `java.util`
(`ArrayList`/`Arrays`, and `HashMap` for hash tables); a program that calls
`rontolisp:fetch` additionally references `java.net`/`java.net.http`, and
`rontolisp:await` / `rontolisp:futurep` represent futures as
`java.util.concurrent` futures -- all of which are part of Java 17, so none of
these raise the requirement. The one exception is a program that uses the
[`java:` interop package](../guides/java-interop.md): the compiler embeds a
reflection bridge (compiled with the project's own Java release) into the
class, so it needs a JRE at least as new as the one rontolisp was built with.
