The technique of producing an execution function for each instruction is just what we used in section 4.1.7 to speed up the evaluator by separating analysis from runtime execution. As we saw in chapter 4, much useful analysis of JavaScript expressions could be performed without knowing the actual values of names. Here, analogously, much useful analysis of register-machine-language expressions can be performed without knowing the actual contents of machine registers. For example, we can replace references to registers by pointers to the register objects, and we can replace references to labels by pointers to the place in the instruction sequence that the label designates.
Before it can generate the instruction execution functions, the assembler must know what all the labels refer to, so it begins by scanning the controller sequence to separate the labels from the instructions. As it scans the controller, it constructs both a list of instructions and a table that associates each label with a pointer into that list. Then the assembler augments the instruction list by inserting the execution function for each instruction.
The assemble function is the main entry to the assembler. It takes the controller sequence and the machine model as arguments and returns the instruction sequence to be stored in the model. The function assemble calls extract_labels to build the initial instruction list and label table from the supplied controller. The second argument to extract_labels is a function to be called to process these results: This function uses update_insts to generate the instruction execution functions and insert them into the instruction list, and returns the modified list.
function assemble(controller, machine) {
return extract_labels(controller,
(insts, labels) => {
update_insts(insts, labels, machine);
return insts;
});
}
The function extract_labels takes a list controller and a function receive as arguments. The function receive will be called with two values: (1) a list insts of instruction data structures, each containing an instruction from controller; and (2) a table called labels, which associates each label from controller with the position in the list insts that the label designates.
function extract_labels(controller, receive) {
return is_null(controller)
? receive(null, null)
: extract_labels(
tail(controller),
(insts, labels) => {
const next_element = head(controller);
return is_string(next_element)
? receive(insts,
pair(make_label_entry(next_element,
insts),
labels))
: receive(pair(make_inst(next_element),
insts),
labels);
});
}
The function update_insts modifies the instruction list, which initially contains only the controller instructions, to include the corresponding execution functions:
function update_insts(insts, labels, machine) {
const pc = get_register(machine, "pc");
const flag = get_register(machine, "flag");
const stack = machine("stack");
const ops = machine("operations");
return for_each(inst => set_inst_execution_fun(
inst,
make_execution_function(
inst_controller_instruction(inst),
labels, machine, pc,
flag, stack, ops)),
insts);
}
The machine instruction data structure simply pairs the controller instruction with the corresponding execution function. The execution function is not yet available when extract_labels constructs the instruction, and is inserted later by update_insts.
function make_inst(inst_controller_instruction) {
return pair(inst_controller_instruction, null);
}
function inst_controller_instruction(inst) {
return head(inst);
}
function inst_execution_fun(inst) {
return tail(inst);
}
function set_inst_execution_fun(inst, fun) {
set_tail(inst, fun);
}
Elements of the label table are pairs:
function make_label_entry(label_name, insts) {
return pair(label_name, insts);
}
function lookup_label(labels, label_name) {
const val = assoc(label_name, labels);
return is_undefined(val)
? error(label_name, "undefined label -- assemble")
: tail(val);
}
"start",
go_to(label("here")),
"here",
assign("a", constant(3)),
go_to(label("there")),
"here",
assign("a", constant(4)),
go_to(label("there")),
"there",
function extract_labels(controller) {
if (is_null(controller)) {
return pair(null, null);
} else {
const result = extract_labels(tail(controller));
const insts = head(result);
const labels = tail(result);
const next_element = head(controller);
return is_string(next_element)
? pair(insts,
pair(make_label_entry(next_element, insts), labels))
: pair(pair(make_inst(next_element), insts),
labels);
}
}
function assemble(controller, machine) {
const result = extract_labels(controller);
const insts = head(result);
const labels = tail(result);
update_insts(insts, labels, machine);
return insts;
}continuation.Recall that we also used continuations to implement the backtracking control structure in the amb evaluator in section 4.3.3.