To compile the bones of functions, and their local variable declarations.

§1. The code following is used throughout Inform, whenever we want to compile a function. Sometimes that's in order to define a phrase, but often not.

There are two ways to begin a function: specifying a stack frame which has already been set up, or not. Here's not:

packaging_state Routines::begin(inter_name *name) {
    return Routines::begin_framed(name, NULL);
}

§2. During the time when we're compiling the body of the routine, we need to keep track of:

ph_stack_frame *currently_compiling_in_frame = NULL;  the stack frame for this routine
int currently_compiling_nnp = FALSE;  is this a nonphrasal stack frame we made ourselves?
inter_package *currently_compiling_inter_block = NULL;  where Inter is being emitted to
inter_name *currently_compiling_iname = NULL;  routine we end up with

§3. So here is the general version, in which phsf may or may not be a pre-existing stack frame:

packaging_state Routines::begin_framed(inter_name *iname, ph_stack_frame *phsf) {
    if (iname == NULL) internal_error("no iname for routine");
    currently_compiling_iname = iname;

    Prepare a suitable stack frame3.1;

    Frames::Blocks::begin_code_blocks();

    packaging_state save = Emit::unused_packaging_state();
    currently_compiling_inter_block = Produce::block(Emit::tree(), &save, iname);
    LocalVariables::declare(phsf, FALSE);
    return save;
}

§3.1. If the phsf argument is set, then we'll use that; otherwise we will create a new nonphrasal stack frame.

Prepare a suitable stack frame3.1 =

    if (phsf == NULL) {
        phsf = Frames::new_nonphrasal();
        currently_compiling_nnp = TRUE;
    } else {
        currently_compiling_nnp = FALSE;
    }
    currently_compiling_in_frame = phsf;
    Frames::make_current(phsf);

§4. As can be seen, very much more work is involved in finishing a function than in starting it. This is because we need to split into two cases: one where the code we've just compiled required allocation of heap memory (e.g. for dynamic strings or lists), and another simpler case where it did not.

void Routines::end(packaging_state save) {
    kind *R_kind = LocalVariables::deduced_function_kind(currently_compiling_in_frame);

    inter_name *kernel_name = NULL, *public_name = currently_compiling_iname;
    if ((currently_compiling_in_frame->allocated_pointers) ||
        (currently_compiling_in_frame->no_formal_parameters_needed > 0))
        kernel_name = Produce::kernel(Emit::tree(), public_name);

    int needed = LocalVariables::count(currently_compiling_in_frame);
    if (kernel_name) needed++;
    if (TargetVMs::allow_this_many_locals(Task::vm(), needed) == FALSE)
        Issue a problem for too many locals4.2;

    LocalVariables::declare(currently_compiling_in_frame, FALSE);
    Produce::end_block(Emit::tree());

    Emit::routine(kernel_name?kernel_name:public_name,
        R_kind, currently_compiling_inter_block);

    if (kernel_name) Compile an outer shell routine with the public-facing name4.1;

    Frames::Blocks::end_code_blocks();
    if (currently_compiling_nnp) Frames::remove_nonphrase_stack_frame();
    Frames::remove_current();
    Produce::end_main_block(Emit::tree(), save);
}

§4.1. Compile an outer shell routine with the public-facing name4.1 =

    int returns_block_value =
        Kinds::Behaviour::uses_pointer_values(currently_compiling_in_frame->kind_returned);

    inter_package *block_package = Produce::block(Emit::tree(), NULL, public_name);
    inter_symbol *I7RBLK_symbol = NULL;
    Compile I6 locals for the outer shell4.1.1;
    int NBV = 0;
    Compile some setup code to make ready for the kernel4.1.2;
    Compile a call to the kernel4.1.3;
    Compile some teardown code now that the kernel has finished4.1.4;
    Compile a return from the outer shell4.1.5;
    Produce::end_block(Emit::tree());
    Emit::routine(public_name, R_kind, block_package);

§4.1.1. Suppose the routine has to return a list. Then the routine is compiled with an extra first parameter (called I7RBLK), which is a pointer to the block value in which to write the answer. After that come all of the call parameters of the phrase (but none of the "let" or scratch-use locals). If, on the other hand, the routine returns a word value, I7RBLK is placed after the call parameters, and is used only as a scratch variable.

Compile I6 locals for the outer shell4.1.1 =

    if (returns_block_value) I7RBLK_symbol = Emit::local(K_number, I"I7RBLK", 0, I"pointer to return value");
    LocalVariables::declare(currently_compiling_in_frame, TRUE);
    if (!returns_block_value) I7RBLK_symbol = Emit::local(K_number, I"I7RBLK", 0, I"pointer to stack frame");

§4.1.2. We allocate memory for each pointer value used in the stack frame:

Compile some setup code to make ready for the kernel4.1.2 =

    Produce::push(Emit::tree(), Hierarchy::find(I7SFRAME_HL));

    for (pointer_allocation *pall=currently_compiling_in_frame->allocated_pointers; pall; pall=pall->next_in_frame) {
        if (pall->offset_past > NBV) NBV = pall->offset_past;
    }
    inter_name *iname = Hierarchy::find(STACKFRAMECREATE_HL);
    Produce::inv_call_iname(Emit::tree(), iname);
    Produce::down(Emit::tree());
    Produce::val(Emit::tree(), K_number, LITERAL_IVAL, (inter_ti) NBV);
    Produce::up(Emit::tree());

    for (pointer_allocation *pall=currently_compiling_in_frame->allocated_pointers; pall; pall=pall->next_in_frame)
        Kinds::RunTime::emit_heap_allocation(pall->allocation);

    for (int i=0; i<currently_compiling_in_frame->no_formal_parameters_needed; i++) {
        nonlocal_variable *nlv = NonlocalVariables::temporary_formal(i);
        Produce::push(Emit::tree(), NonlocalVariables::iname(nlv));
    }

§4.1.3. Compile a call to the kernel4.1.3 =

    Produce::inv_primitive(Emit::tree(), STORE_BIP);
    Produce::down(Emit::tree());
    Produce::ref_symbol(Emit::tree(), K_value, I7RBLK_symbol);
    if (returns_block_value) {
        inter_name *iname = Hierarchy::find(BLKVALUECOPY_HL);
        Produce::inv_call_iname(Emit::tree(), iname);
        Produce::down(Emit::tree());
        Produce::val_symbol(Emit::tree(), K_number,I7RBLK_symbol);
    }

    Produce::inv_call_iname(Emit::tree(), kernel_name);
    Produce::down(Emit::tree());
    LocalVariables::emit_parameter_list(currently_compiling_in_frame);
    Produce::up(Emit::tree());

    if (returns_block_value) {
        Produce::up(Emit::tree());
    }
    Produce::up(Emit::tree());

§4.1.4. Here we deallocate all the memory allocated earlier.

Compile some teardown code now that the kernel has finished4.1.4 =

    for (int i=currently_compiling_in_frame->no_formal_parameters_needed-1; i>=0; i--) {
        nonlocal_variable *nlv = NonlocalVariables::temporary_formal(i);
        Produce::pull(Emit::tree(), NonlocalVariables::iname(nlv));
    }

    for (pointer_allocation *pall=currently_compiling_in_frame->allocated_pointers; pall; pall=pall->next_in_frame) {
        inter_name *iname = Hierarchy::find(BLKVALUEFREEONSTACK_HL);
        Produce::inv_call_iname(Emit::tree(), iname);
        Produce::down(Emit::tree());
        Produce::val(Emit::tree(), K_value, LITERAL_IVAL, (inter_ti) pall->offset_index);
        Produce::up(Emit::tree());
    }

    Produce::pull(Emit::tree(), Hierarchy::find(I7SFRAME_HL));

§4.1.5. Compile a return from the outer shell4.1.5 =

    Produce::inv_primitive(Emit::tree(), RETURN_BIP);
    Produce::down(Emit::tree());
        Produce::val_symbol(Emit::tree(), K_value, I7RBLK_symbol);
    Produce::up(Emit::tree());

§4.2. Issue a problem for too many locals4.2 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_TooManyLocals),
        "there are too many temporarily-named values in this phrase",
        "which may be a sign that it is complicated enough to need breaking up "
        "into smaller phrases making use of each other. "
        "The limit is 15 at a time for a Z-machine project (see the Settings) "
        "and 256 at a time for Glulx. That has to include both values created in the "
        "declaration of a phrase (e.g. the 'N' in 'To deduct (N - a number) points: "
        "...', or the 'watcher' in 'Instead of taking something in the presence of "
        "a man (called the watcher): ...'), and also values created with 'let' or "
        "'repeat' (each 'repeat' loop claiming two such values) - not to mention "
        "one or two values occasionally needed to work with Tables. Because of all "
        "this, it's best to keep the complexity to a minimum within any single phrase.");