(rontolisp) docs
← The uiop Package

uiop/image

uiop/image is what a program does at its edges: end the process with a status code, report a condition nobody handled, and register work to run when an image is restored or dumped, and read the command line it was started with. All 30 exports are implemented.

Every name is reachable through either spelling: uiop:quit and uiop/image:quit are the same function (The uiop Package).

Three decisions here are rontolisp's own, and each is a decision rather than a gap:

  • uiop:quit is the host's exit on all four backends, and nothing runs after it — see Exiting.
  • Backtraces carry no frames. No backend keeps a Lisp-level call stack, so the honest rendering of "the backtrace for this condition" is the condition and nothing else. Real UIOP's own fallback for an implementation without a backtrace API has the same shape.
  • There is no image to dump, restore or create, so those three signal — but the hooks around them are real, because registering into one is just a list push. See Image hooks.
  • The argument vector has one shape everywhere, (program-name user-arg ...), and an interpreted program's arguments follow a -- separator. See The command line.

Exiting

FunctionWhat it does
uiop:quitend the process with a status code (0 by default), after finishing the standard output streams
uiop:diereport a format message on *error-output*, then quit with the given code
uiop:shell-boolean-exitquit with 0 when the argument is true and 1 when it is nil — a shell's idea of a boolean
$ cat quit.lisp
(print :before)
(uiop:quit 3)
(print :after)
$ rontolisp quit.lisp
:BEFORE
$ echo $?
3

The same program compiled to a class, to a Preview 1 module or to a component prints the same line and exits 3: the primitive underneath is System.exit on the JVM, proc_exit on WASM Preview 1 and wasi:cli/exit's exit-with-code under --component, and the interpreter raises an exit signal the CLI turns into the process code.

Two consequences follow from that being a real host exit, and they hold on every backend:

  • Nothing runs afterwards. An unwind-protect cleanup around a quit does not run — the process ends where the call stands.
  • It is not a condition. handler-case, ignore-errors and a catch tag cannot see it, so a quit inside a library's error handling still quits.

The status code is masked to eight bits, which is what a POSIX host does with it anyway and what wasi:cli/exit's u8 accepts: (uiop:quit 300) exits 44 everywhere rather than 300 on one backend and 44 on another.

A test runner's exit code is the usual reason to reach for this:

$ cat run-tests.lisp
(uiop:quit (if (rove:run :my-app/tests) 0 1))

uiop:quit needs a host process to end, so it is refused at compile time under --no-wasi and --no-gc: those emit a reactor whose entry points are exports a host calls, and a reactor returns from an export rather than exiting.

Fatal conditions

A fatal condition is a serious-condition — the type is a deftype alias, so typep and a handler-bind clause both match it.

NameWhat it does
uiop:fatal-conditionthe type: serious-condition
uiop:fatal-condition-p(typep c 'uiop:fatal-condition)
uiop:handle-fatal-conditionreport the condition on *error-output* and uiop:die with status 99
uiop:call-with-fatal-condition-handlercall a thunk with that handler bound
uiop:with-fatal-condition-handlerthe macro over it: (uiop:with-fatal-condition-handler () body...)
uiop:*lisp-interaction*nil

uiop:*lisp-interaction* is nil here where upstream defaults to t. It asks "is this an interactive Lisp environment, or is it batch processing?", and every rontolisp backend runs a program and ends: there is no debugger to enter and no REPL underneath a compiled artifact. That value is what makes uiop:handle-fatal-condition report and exit rather than call an invoke-debugger that does not exist.

$ cat fatal.lisp
(print :start)
(uiop:with-fatal-condition-handler ()
  (error "the sky is falling"))
(print :unreachable)
$ rontolisp fatal.lisp
:START
Fatal condition:
the sky is falling
the sky is falling
the sky is falling
$ echo $?
99

The condition appears three times because upstream prints it three times — once as the report, once with the backtrace, once as die's message — and the middle one has no frames above it here.

Backtraces

FunctionWhat it prints
uiop:raw-print-backtracethe :condition argument, when there is one
uiop:print-backtracethe same, through uiop:raw-print-backtrace
uiop:print-condition-backtraceits condition argument, on :stream (*error-output* by default)

:count is accepted and ignored: there are no frames to limit. lack-middleware-backtrace is the library that reaches for this — its error report opens with uiop/image:print-condition-backtrace, and here that report is one line long.

Image hooks

NameWhat it does
uiop:register-image-restore-hookpush a function onto uiop:*image-restore-hook*, calling it now unless the second argument is nil
uiop:register-image-dump-hookpush a function onto uiop:*image-dump-hook*, calling it now only if the second argument is true
uiop:call-image-restore-hookcall the restore hooks, in registration order
uiop:call-image-dump-hookcall the dump hooks
uiop:*image-restore-hook* / uiop:*image-dump-hook*the two lists
uiop:*image-prelude* / uiop:*image-entry-point* / uiop:*image-postlude* / uiop:*image-dumped-p*nil

The hooks are real even though nothing here can dump an image: a library may register into one while it loads, and that must not be an error.

uiop:*image-dumped-p* is nil and stays nil — nothing sets it, because nothing dumps.

Dumping an image

FunctionWhat it signals
uiop:dump-imageuiop:not-implemented-error — no backend can save its heap; compile the program instead
uiop:restore-imageuiop:not-implemented-error — a program is started from source, never resumed
uiop:create-imageuiop:not-implemented-error — there are no Lisp object files to link

rontolisp has no image in the SBCL sense. A program is read and run, or read and compiled into one artifact:

rontolisp app.lisp -o App.class      # a JVM class
rontolisp app.lisp -o app.wasm       # a WASM module

which is what uiop:dump-image would have been for.

The command line

NameWhat it answers
uiop:raw-command-line-argumentsthe whole vector: the program name, then the user arguments
uiop:command-line-argumentsthe user arguments — the vector's rest (or the rest of a vector passed explicitly)
uiop:argv0the program name — the vector's first
uiop:*command-line-arguments*the user arguments, seeded before the program runs
uiop:setup-command-line-argumentsseed that variable again from the vector
$ cat args.lisp
(print (uiop:argv0))
(print (uiop:command-line-arguments))
$ rontolisp args.lisp -- alpha beta
"args.lisp"
("alpha" "beta")

-- is where rontolisp's own options end and the program's arguments begin. Nothing else could tell --optimize the compiler flag from --optimize the argument a program wants to be handed, so an interpreted program's arguments always follow the separator.

A compiled artifact has no such ambiguity: it takes its arguments the way its host hands them over.

$ rontolisp args.lisp -o Args.class --class-name Args && java Args alpha beta
"Args"
("alpha" "beta")
$ rontolisp args.lisp -o args.wasm && wasmtime run args.wasm alpha beta
"args.wasm"
("alpha" "beta")

The SHAPE is the same on all four backends, which is what lets uiop:command-line-arguments be the vector's rest and uiop:argv0 its first with no per-backend rule. Only the program NAME differs, because only the host knows it: the input file under the interpreter, the class name on the JVM (what stood on the java command line), and whatever the host put in args[0] on both WASM backends — the module path, for wasmtime run.

Upstream UIOP answers this way only for an image that was dumped as an executable, and otherwise hunts for a -- inside a vector that still carries the implementation's own arguments. rontolisp is always the executable case: the CLI splits at the separator itself, and a compiled artifact never sees a rontolisp option at all.