To generate I6 code from intermediate code.


§1. Target. This generator produces Inform 6 source code, using the Vanilla algorithm.

code_generator *inform6_target = NULL;
void I6Target::create_generator(void) {
    inform6_target = Generators::new(I"inform6");

    METHOD_ADD(inform6_target, BEGIN_GENERATION_MTID, I6Target::begin_generation);
    METHOD_ADD(inform6_target, MANGLE_IDENTIFIER_MTID, I6Target::mangle);
    METHOD_ADD(inform6_target, OFFER_PRAGMA_MTID, I6Target::offer_pragma)
    METHOD_ADD(inform6_target, END_GENERATION_MTID, I6Target::end_generation);

    I6TargetCode::create_generator(inform6_target);
    I6TargetObjects::create_generator(inform6_target);
    I6TargetConstants::create_generator(inform6_target);
    I6TargetVariables::create_generator(inform6_target);
}

§2. We will write a single output file of I6 source code, but segmented as follows:

enum ICL_directives_I7CGS
enum compiler_versioning_matter_I7CGS
enum attributes_I7CGS
enum properties_I7CGS
enum global_variables_I7CGS
enum global_variables_array_I7CGS
enum constants_I7CGS
enum fake_actions_I7CGS
enum arrays_I7CGS
enum classes_I7CGS
enum objects_I7CGS
enum property_stubs_I7CGS
enum functions_I7CGS
enum command_grammar_I7CGS
int I6_target_segments[] = {
    ICL_directives_I7CGS,
    compiler_versioning_matter_I7CGS,
    attributes_I7CGS,
    properties_I7CGS,
    global_variables_I7CGS,
    global_variables_array_I7CGS,
    constants_I7CGS,
    fake_actions_I7CGS,
    arrays_I7CGS,
    classes_I7CGS,
    objects_I7CGS,
    property_stubs_I7CGS,
    functions_I7CGS,
    command_grammar_I7CGS,
    -1
};

§3. This generator uses the following state data while it works:

define I6_GEN_DATA(x) ((I6_generation_data *) (gen->generator_private_data))->x
typedef struct I6_generation_data {
    int attribute_slots_used;
    int value_ranges_needed;
    int value_property_holders_needed;
    int DebugAttribute_seen;
    int subterfuge_count;
    CLASS_DEFINITION
} I6_generation_data;

I6_generation_data *I6Target::new_data(void) {
    I6_generation_data *data = CREATE(I6_generation_data);
    data->attribute_slots_used = 0;
    data->value_ranges_needed = FALSE;
    data->value_property_holders_needed = FALSE;
    data->DebugAttribute_seen = FALSE;
    data->subterfuge_count = 0;
    return data;
}

§4. We return FALSE here to signal that we want the Vanilla algorithm to manage the process.

int I6Target::begin_generation(code_generator *cgt, code_generation *gen) {
    CodeGen::create_segments(gen, I6Target::new_data(), I6_target_segments);
    Compile some I6 oddities4.1;
    Compile some veneer replacement code4.2;
    return FALSE;
}

§4.1. Defining a constant called Grammar__Version tells Inform 6 which storage layout to use for command parser grammar. 2 is the shiny, modern one — 1995 not 1993.

The I6 compiler adds a thin layer of hidden code to every program it compiles, called the "veneer". This layer of code requires a global variable called debug_flag to exist, and since that doesn't exist in the Inter tree, we must make it by hand.

The or_tmp_var variable is not significant to I6, and is just a temporary location we will need for the code we are compiling. But this seems a good time to make it.

See the Inform 6 Technical Manual for more on these oddities.

Compile some I6 oddities4.1 =

    segmentation_pos saved = CodeGen::select(gen, compiler_versioning_matter_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("Constant Grammar__Version 2;\n");
    WRITE("Global debug_flag;\n");
    WRITE("Global or_tmp_var;\n");
    CodeGen::deselect(gen, saved);

§4.2. As noted above, I6 will add a veneer of code to what we compile. That veneer will contain a function called OC__Cl which implements "ofclass", the I6 condition determining whether an object belongs to a given class. The I6 compiler's stock copy of OC__Cl doesn't work right with I7 code, though, so we replace it here with a better one. (The I6 compiler uses our definition in preference to its own.)

We need do this only when compiling to the Z-machine; our replacement function is implemented in pure Z-machine assembly language. See the Z-Machine Standards Document for a specification.

Compile some veneer replacement code4.2 =

    segmentation_pos saved = CodeGen::select(gen, functions_I7CGS);
    text_stream *OUT = CodeGen::current(gen);
    WRITE("#Ifdef TARGET_ZCODE;\n");
    WRITE("Global max_z_object;\n");
    WRITE("[ OC__Cl obj cla j a n objflag;\n"); INDENT;
    WRITE("@jl obj 1 ?NotObj;\n");
    WRITE("@jg obj max_z_object ?NotObj;\n");
    WRITE("@inc objflag;\n");
    WRITE("#Ifdef K1_room;\n");
    WRITE("@je cla K1_room ?~NotRoom;\n");
    WRITE("@test_attr obj mark_as_room ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE(".NotRoom;\n");
    WRITE("#Endif;\n");
    WRITE("#Ifdef K2_thing;\n");
    WRITE("@je cla K2_thing ?~NotObj;\n");
    WRITE("@test_attr obj mark_as_thing ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE("#Endif;\n");
    WRITE(".NotObj;\n");
    WRITE("\n");
    WRITE("@je cla Object Class ?ObjOrClass;\n");
    WRITE("@je cla Routine String ?RoutOrStr;\n");
    WRITE("\n");
    WRITE("@jin cla 1 ?~Mistake;\n");
    WRITE("\n");
    WRITE("@jz objflag ?rfalse;\n");
    WRITE("@get_prop_addr obj 2 -> a;\n");
    WRITE("@jz a ?rfalse;\n");
    WRITE("@get_prop_len a -> n;\n");
    WRITE("\n");
    WRITE("@div n 2 -> n;\n");
    WRITE(".Loop;\n");
    WRITE("@loadw a j -> sp;\n");
    WRITE("@je sp cla ?rtrue;\n");
    WRITE("@inc j;\n");
    WRITE("@jl j n ?Loop;\n");
    WRITE("@rfalse;\n");
    WRITE("\n");
    WRITE(".ObjOrClass;\n");
    WRITE("@jz objflag ?rfalse;\n");
    WRITE("@je cla Object ?JustObj;\n");
    WRITE("\n");
    WRITE("! So now cla is Class\n");
    WRITE("@jg obj String ?~rtrue;\n");
    WRITE("@jin obj Class ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE("\n");
    WRITE(".JustObj;\n");
    WRITE("! So now cla is Object\n");
    WRITE("@jg obj String ?~rfalse;\n");
    WRITE("@jin obj Class ?rfalse;\n");
    WRITE("@rtrue;\n");
    WRITE("\n");
    WRITE(".RoutOrStr;\n");
    WRITE("@jz objflag ?~rfalse;\n");
    WRITE("@call_2s Z__Region obj -> sp;\n");
    WRITE("@inc sp;\n");
    WRITE("@je sp cla ?rtrue;\n");
    WRITE("@rfalse;\n");
    WRITE("\n");
    WRITE(".Mistake;\n");
    WRITE("RT__Err(\"apply 'ofclass' for\", cla, -1);\n");
    WRITE("rfalse;\n");
    OUTDENT; WRITE("];\n");
    WRITE("#Endif;\n");
    CodeGen::deselect(gen, saved);

§5. Pragmas are interpreted as ICL directives — ICL being the Inform Configuration Language part of Inform 6, which basically allows maxima in the compiler to be tweaked. These go at the top of the source code and typically look like this:

 $MAX_NUM_STATIC_STRINGS=500000
void I6Target::offer_pragma(code_generator *cgt, code_generation *gen,
    inter_tree_node *P, text_stream *tag, text_stream *content) {
    if (Str::eq(tag, I"Inform6")) {
        segmentation_pos saved = CodeGen::select(gen, ICL_directives_I7CGS);
        text_stream *OUT = CodeGen::current(gen);
        WRITE("!%% %S\n", content);
        CodeGen::deselect(gen, saved);
    }
}

§6. Names are not mangled: all Inter identifiers are used as-is.

void I6Target::mangle(code_generator *cgt, OUTPUT_STREAM, text_stream *identifier) {
    WRITE("%S", identifier);
}

§7. The end:

int I6Target::end_generation(code_generator *cgt, code_generation *gen) {
    I6TargetObjects::end_generation(cgt, gen);
    I6TargetCode::end_generation(cgt, gen);
    return FALSE;
}