I’m a bit curious about optimization. Is it true that if I include a predicate in my source that is not referenced (say a
(debug my thing) predicate that I use only from the debugger, and not tied to an action) then the compiler will generate the same code as if the predicate was not present?
Further, I would assume this is transitive; that an entire tree of predicates could be excised if there is no route to calling them?
Yes, unreachable code is removed. When there is no chain from
(program entry point) or
(error $ entry point) via rule bodies to a given predicate, in any number of steps, then you can be certain that the predicate is eliminated.
However, the compiler isn’t smart enough (at least yet) to track runtime information such as dynamic predicates. So if a predicate is reachable if flags and variables are set in a particular way, then the predicate is included in the build, regardless of whether the flags and variables can ever end up in that state.
The same caveat applies if the runtime logic is too complex to analyze. For instance,
(if) (a) (then) (b) (elseif) (a) (then) (c) (endif) will be treated as though
(c) is reachable—which it might be, actually, if
(a) has a side-effect that makes it fail the first time and succeed the second time. But if, on the other hand, the definition of
(a) is very simple (i.e. it always fails, or always succeeds) then the compiler can often take that into account. Global feature flags like
(fungibility enabled) have an impact on code size because of this.