All right! Doing a bit of poking.
Using this code:
(program entry point)
(hello)
(hello)
Hello world!
(interface (dead alpha))
(dead alpha)
(dead beta)
(dead beta)
This should never happen
Compiling with -vvv returns, along with many other things:
Predicate ---M-- ($ < $) of arity 2
Predicate ---M-- ($ > $) of arity 2
Predicate ---M-- ($ plus $ into $) of arity 3
Predicate ---M-- ($ minus $ into $) of arity 3
[...]
Predicate PN---- (hello) of arity 0
helloworld.dg:4: ---> Hello world!
Predicate ------ (dead alpha) of arity 0 (interface declared at helloworld.dg:7)
helloworld.dg:9: ---> (dead beta)
Predicate ------ (dead beta) of arity 0
helloworld.dg:12: ---> This should never happen
Those flags after the word “Predicate” are the key:
- P = invoked by program
- N = invoked normally (not for words)
- W = invoked for words (within a
(collect words))
- M = invoked in a multi-query
- G/F/D = global variable / fixed flag / dynamic flag
- S = might
(stop)
The first one, “P”, is the key. Any predicate without “P” is considered dead and will be eliminated by the compiler. (Annoyingly, this works differently on Z-machine and Å-machine. But unz confirms that the string “This should never happen” does not appear in the compiled Z-machine file, at least.)
If we include stdlib.dg when compiling, then we see a huge list of library predicates have the “P” flag—it seems like everything involved in parsing and the turn sequence! And unfortunately, I can’t find any way of cutting it down by altering helloworld.dg. It seems like Dialog always considers every rule for (program entry point) to be live, even if an earlier one features (just) or (stop) or the like.
Why does this happen? It comes from trace_invocations in frontend.c. It starts by marking (program entry point), (error $ entry point), and (object $) live, then propagates live-ness from there. And it marks them live for normal queries and multi-queries, which means every rule needs to be compiled, not just the first one—after all, if you (exhaust) *(predicate), every rule might matter! It doesn’t seem to notice (just) or (quit) at this stage.
Predicate PN-M-- (program entry point) of arity 0
helloworld.dg:1: ---> (just) (hello)
stdlib.dg:4909: ---> (exhaust) *(startup) (div @initial-spacer) {} (update environment around player) (stoppable) (intro) *(repeat forever) (read-parse-act) (fail)
So…why do we mark (program entry point) as live for multi-queries? I honestly have no idea! Maybe it has something to do with how (program entry point) is queried to launch the program, which is not documented in the manual? Or maybe it’s simpler than that—it looks like every builtin is automatically marked as “invoked simply” and “invoked multi”, no matter what. And that includes the entry points.
The answer ends up being: dead code usually gets eliminated, but code called from a (program entry point) rule is never marked dead. If you want to use the standard library’s utilities without its main parsing loop, comment out (or just rename) its entry point rule.