[1] Of course, compiled functions as well as interpreted functions are compound (nonprimitive). For compatibility with the terminology used in the explicit-control evaluator, in this section we will use compound to mean interpreted (as opposed to compiled).
[2] Now that the evaluator machine starts with a branch, we must always initialize the flag register before starting the evaluator machine. To start the machine at its ordinary read-eval-print loop, we could use
function start_eceval() {
the_global_environment = setup_environment();
set_register_contents(eceval, "flag", false);
return start(eceval);
}


[3] Since a compiled function is an object that the system may try to print, we also modify the system print operation user_print (from section 4.1.4) so that it will not attempt to print the components of a compiled function:
function user_print(object) {
if (compound_procedure(object)) {
display(list(
"compound_procedure",
procedure_parameters(object),
procedure_body(object),
""));
} else if (compiled_procedure(object)) {
display("");
} else {
display(object);
}
}


[4] We can do even better by extending the compiler to allow compiled code to call interpreted functions. See exercise 5.46.
[5] Independent of the strategy of execution, we incur significant overhead if we insist that errors encountered in execution of a user program be detected and signaled, rather than being allowed to kill the system or produce wrong answers. For example, an out-of-bounds array reference can be detected by checking the validity of the reference before performing it. The overhead of checking, however, can be many times the cost of the array reference itself, and a programmer should weigh speed against safety in determining whether such a check is desirable. A good compiler should be able to produce code with such checks, should avoid redundant checks, and should allow programmers to control the extent and type of error checking in the compiled code.
[6] Of course, with either the interpretation or the compilation strategy we must also implement for the new machine storage allocation, input and output, and all the various operations that we took as primitive in our discussion of the evaluator and compiler. One strategy for minimizing work here is to write as many of these operations as possible in JavaScript and then compile them for the new machine. Ultimately, everything reduces to a small kernel (such as garbage collection and the mechanism for applying actual machine primitives) that is hand-coded for the new machine.
[7] This strategy leads to amusing tests of correctness of the compiler, such as checking whether the compilation of a program on the new machine, using the compiled compiler, is identical with the compilation of the program on the original JavaScript system. Tracking down the source of differences is fun but often frustrating, because the results are extremely sensitive to minuscule details.
5.5.7 Interfacing Compiled Code to the Evaluator