Naïve translation of Lunar programs to machine code seems like it would be very inefficient because of the high-level and mostly untyped nature of the language, and because programs can be modified at run time by the addition of new classes and new methods applicable to existing objects. However, a variety of compiler optimizations are possible and very tight code can be produced when sufficient type information can be inferred.
Given a call_expression with a known function and some type information about the actual parameters, the compiler can examine the known methods for the function that are applicable to those types. If the most specific applicable method is the same for all subtypes and is sealed or dominant, run-time addition of new methods cannot change the behavior of the program. The compiler can use the declared or inferred result type of that method as the type of the result of the call.
Even when the most specific applicable method is not known, if there is an applicable require statement its declared result type can be used by the compiler, since any applicable method must return a result of that type.
Even when the precise type of the actual parameters is not known, the compiler may be able to infer that the result of a call is a more specific type than everything.
In this way, the compiler can propagate type information from the few places where it is explicitly declared (some formal parameter lists, some slot declarations, some variable definitions) throughout the body of a method. In the general case, an undeclared type will be everything, but with just a scattering of type declarations in places where they seem natural the compiler can infer a useful static type. The run-time dynamic type of the result of an expression is guaranteed to be a subtype of the static type of that expression.
When the exact method to be called is known at compile time (because it is sealed) the compiler can generate code to call it directly without dispatching, or inline it when it is small or intrinsic. For example, I expect that uses of the sequence iteration protocol can be fully inlined when the specific sequence type is known.
Note that all slot accessor methods are sealed, so access to a slot of a datum of a known class can always be inlined. The Consistent Slot Order Rule and lack of null pointers mean that a slot access can usually be inlined as a single machine instruction.
The compiler can often infer the possible range of integer-valued expressions, even in loops. If this range lies within the range directly supported by machine instructions, arithmetic operations can be inlined. No overflow checking is required when no overflow is possible. It helps if max_length is defined to fall within the range of machine integers, since many loop variables are easily inferred to be in the range 0..max_length+1.
The compiler can often infer that a value of type XX | false is actually of type XX because false has been ruled out by a prior if_expression.
When the exact method to be called is not known at compile time, it may still be possible to bypass the full generality of method selection and use a dispatcher specialized for a particular number and/or type of actual parameters. It is also possible to use a dispatcher specialized for the particular pattern of methods present in a function, and change to a different dispatcher at run time if a new method is added that does not fit the pattern. For example, in some cases only one parameter affects which method is selected. Dispatching can be simplified when all applicable formal parameter types are simply classes, rather than ranges, unions, or sets.
When the general method dispatcher is required, it may be possible to use caching techniques to decrease the average dispatch time.
Generic methods can be compiled once for the most general case. Or specialized versions can be compiled for specific values of the generic parameters, where there is an advantage to be gained such as additional inlining. This can be based on pre-analysis or can be done just-in-time.
The main point is not to pollute the language with special features intended to allow a simple compiler to generate better code. The programming language should be designed to maximize expressiveness and minimize unnecessary detail, so the intent of programs is clear. It's the compiler's business to minimize the resources needed for execution, in cooperation with the runtime. The programmer should trust, but verify through measurement, that this infrastructure does its job properly, rather than obscuring the program source code with extra material that may or may not be necessary to achieve the desired performance.
One could imagine profiling tools that advise the programmer about possibilities for increased performance through judicious addition of a small number of type declarations, sealed: modifiers, and/or require statements. These could be run on just the performance-critical portion of a program.
Previous page Table of Contents Next page