#include "moar.h"

/* Wraps a C function into a BOOTCCode object. */
static MVMObject * wrap(MVMThreadContext *tc, void (*func) (MVMThreadContext *, MVMArgs)) {
    MVMObject *BOOTCCode = tc->instance->boot_types.BOOTCCode;
    MVMObject *code_obj = REPR(BOOTCCode)->allocate(tc, STABLE(BOOTCCode));
    ((MVMCFunction *)code_obj)->body.func = func;
    return code_obj;
}

/* The boot-constant dispatcher takes the first position argument of the
 * incoming argument capture and treats it as a constant that should always
 * be produced as the result of the dispatch (modulo established guards). */
static void boot_constant(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMRegister value;
    MVMCallsiteFlags kind;
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVM_capture_arg_pos(tc, capture, 0, &value, &kind);
    MVM_disp_program_record_result_constant(tc, kind, value);
    MVM_args_set_result_obj(tc, tc->instance->VMNull, MVM_RETURN_CURRENT_FRAME);
}

/* Gets the MVMCFunction object wrapping the boot constant dispatcher. */
MVMObject * MVM_disp_boot_constant_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_constant);
}

/* The boot-value dispatcher returns the first positional argument of the
 * incoming argument capture. */
static void boot_value(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVM_disp_program_record_result_tracked_value(tc,
            MVM_disp_program_record_track_arg(tc, capture, 0));
    MVM_args_set_result_obj(tc, tc->instance->VMNull, MVM_RETURN_CURRENT_FRAME);
}

/* Gets the MVMCFunction object wrapping the boot value dispatcher. */
MVMObject * MVM_disp_boot_value_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_value);
}

/* The boot-code-constant dispatcher takes the first positional argument of
 * the incoming argument capture, which should be either an MVMCode or an
 * MVMCFunction. It invokes it with the rest of the args. The provided
 * code object is considered a constant. */
static void boot_code_constant(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVMROOT(tc, capture, {
        /* Get a capture dropping the first argument, which is the callee. */
        MVMObject *args_capture = MVM_disp_program_record_capture_drop_arg(tc, capture, 0);

        /* Work out what the callee is, and set us up to invoke it. */
        MVMObject *code = MVM_capture_arg_pos_o(tc, capture, 0);
        if (REPR(code)->ID == MVM_REPR_ID_MVMCode && IS_CONCRETE(code)) {
            MVM_disp_program_record_code_constant(tc, (MVMCode *)code, args_capture);
        }
        else if (REPR(code)->ID == MVM_REPR_ID_MVMCFunction && IS_CONCRETE(code)) {
            MVM_disp_program_record_c_code_constant(tc, (MVMCFunction *)code, args_capture);
        }
        else {
            MVM_exception_throw_adhoc(tc,
                    "boot-code-constant dispatcher only works with MVMCode or MVMCFunction");
        }
    });

    MVM_args_set_result_obj(tc, tc->instance->VMNull, MVM_RETURN_CURRENT_FRAME);
}

/* Gets the MVMCFunction object wrapping the boot code constant dispatcher. */
MVMObject * MVM_disp_boot_code_constant_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_code_constant);
}

static void boot_foreign_code(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVMROOT(tc, capture, {
        /* Get a capture dropping the first argument, which is the callee. */
        MVMObject *args_capture = MVM_disp_program_record_capture_drop_arg(tc, capture, 0);

        /* Work out what the callee is, and set us up to invoke it. */
        MVMObject *code = MVM_capture_arg_pos_o(tc, capture, 0);
        if (REPR(code)->ID == MVM_REPR_ID_MVMNativeCall && IS_CONCRETE(code)) {
            MVM_disp_program_record_foreign_code_constant(tc, (MVMNativeCall *)code, args_capture);
        }
        else {
            MVM_exception_throw_adhoc(tc,
                    "boot-foreign-code dispatcher only works with MVMNativeCall, got %s", REPR(code)->name);
        }
    });

    MVM_args_set_result_obj(tc, tc->instance->VMNull, MVM_RETURN_CURRENT_FRAME);
}

MVMObject * MVM_disp_boot_foreign_code_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_foreign_code);
}

/* The boot-code dispatcher takes the first positional argument of the
 * incoming argument capture, which should be either an MVMCode or an
 * MVMCFunction. It establishes a type and concreteness guard on it,
 * then invokes it with the rest of the args. */
static void boot_code(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVMROOT(tc, capture, {
        /* Get a capture dropping the first argument, which is the callee. */
        MVMObject *args_capture = MVM_disp_program_record_capture_drop_arg(tc, capture, 0);

        MVMROOT(tc, args_capture, {
            /* Work out what the callee is, and set us up to invoke it. */
            MVMObject *code = MVM_capture_arg_pos_o(tc, capture, 0);
            MVMROOT(tc, code, {
                MVMObject *tracked_code = MVM_disp_program_record_track_arg(tc, capture, 0);
                if (REPR(code)->ID == MVM_REPR_ID_MVMCode && IS_CONCRETE(code)) {
                    MVM_disp_program_record_tracked_code(tc, tracked_code, args_capture);
                }
                else if (REPR(code)->ID == MVM_REPR_ID_MVMCFunction && IS_CONCRETE(code)) {
                    MVM_disp_program_record_tracked_c_code(tc, tracked_code, args_capture);
                }
                else {
                    MVM_exception_throw_adhoc(tc,
                            "boot-code dispatcher only works with MVMCode or MVMCFunction");
                }
            });
        });
    });

    MVM_args_set_result_obj(tc, tc->instance->VMNull, MVM_RETURN_CURRENT_FRAME);
}

/* Gets the MVMCFunction object wrapping the boot code dispatcher. */
MVMObject * MVM_disp_boot_code_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_code);
}

/* The boot-syscall dispatcher expects the first argument to be a string,
 * which will typically be a literal. It uses this to invoke functionality
 * provided by the VM. The rest of the arguments are the arguments that go
 * to that call. */
static void boot_syscall(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* Look up the syscall information. */
    MVMString *name = MVM_capture_arg_pos_s(tc, capture, 0);
    MVMDispSysCall *syscall = MVM_disp_syscall_find(tc, name);
    if (!syscall) {
        char *c_name = MVM_string_utf8_encode_C_string(tc, name);
        char *waste[] = { c_name, NULL };
        MVM_exception_throw_adhoc_free(tc, waste,
                "No MoarVM syscall with name '%s'", c_name);
    }

    /* Drop the name from the args capture, and then check them. */
    MVMObject *args_capture;
    MVMROOT(tc, name, {
        args_capture = MVM_disp_program_record_capture_drop_arg(tc, capture, 0);
    });
    MVMCallsite *cs = ((MVMCapture *)args_capture)->body.callsite;
    if (MVM_callsite_has_nameds(tc, cs)) {
        char *c_name = MVM_string_utf8_encode_C_string(tc, name);
        char *waste[] = { c_name, NULL };
        MVM_exception_throw_adhoc_free(tc, waste,
                "Cannot pass named arguments to MoarVM syscall '%s'", c_name);
    }
    if (cs->num_pos < syscall->min_args) {
        char *c_name = MVM_string_utf8_encode_C_string(tc, name);
        char *waste[] = { c_name, NULL };
        MVM_exception_throw_adhoc_free(tc, waste,
                "Too few arguments to MoarVM syscall '%s'; got %d, need %d..%d",
                        c_name, cs->num_pos, syscall->min_args, syscall->max_args);
    }
    if (cs->num_pos > syscall->max_args) {
        char *c_name = MVM_string_utf8_encode_C_string(tc, name);
        char *waste[] = { c_name, NULL };
        MVM_exception_throw_adhoc_free(tc, waste,
                "Too many arguments to MoarVM syscall '%s'; got %d, need %d..%d",
                        c_name, cs->num_pos, syscall->min_args, syscall->max_args);
    }
    MVMuint32 i;
    for (i = 0; i < cs->num_pos; i++) {
        /* Check we got the expected kind of argument. */
        if ((cs->arg_flags[i] & MVM_CALLSITE_ARG_TYPE_MASK) != syscall->expected_kinds[i]) {
            char *c_name = MVM_string_utf8_encode_C_string(tc, name);
            char *waste[] = { c_name, NULL };
            MVM_exception_throw_adhoc_free(tc, waste,
                    "Argument %d to MoarVM syscall '%s' had kind %s, but should be %s",
                    i, c_name,
                    MVM_callsite_arg_type_name(cs->arg_flags[i]),
                    MVM_callsite_arg_type_name(syscall->expected_kinds[i]));
        }

        /* Add any guards. */
        if (syscall->expected_kinds[i] == MVM_CALLSITE_ARG_OBJ) {
            if (syscall->expected_reprs[i]) {
                MVMuint32 expected = syscall->expected_reprs[i];
                MVMuint32 got = REPR(MVM_capture_arg_pos_o(tc, args_capture, i))->ID;
                if (expected == got) {
                    MVMROOT2(tc, name, args_capture, {
                        MVM_disp_program_record_guard_type(tc,
                                MVM_disp_program_record_track_arg(tc, args_capture, i));
                    });
                }
                else {
                    char *c_name = MVM_string_utf8_encode_C_string(tc, name);
                    char *waste[] = { c_name, NULL };
                    MVM_exception_throw_adhoc_free(tc, waste,
                            "Argument %d to MoarVM syscall '%s' has repr %s, but should be %s",
                            i, c_name,
                            MVM_repr_get_by_id(tc, got)->name,
                            MVM_repr_get_by_id(tc, expected)->name);
                }
            }
            if (syscall->expected_concrete[i]) {
                if (IS_CONCRETE(MVM_capture_arg_pos_o(tc, args_capture, i))) {
                    MVMROOT2(tc, name, args_capture, {
                        MVM_disp_program_record_guard_concreteness(tc,
                                MVM_disp_program_record_track_arg(tc, args_capture, i));
                    });
                }
                else {
                    char *c_name = MVM_string_utf8_encode_C_string(tc, name);
                    char *waste[] = { c_name, NULL };
                    MVM_exception_throw_adhoc_free(tc, waste,
                            "Argument %d to MoarVM syscall '%s' must be concrete, not a type object",
                            i, c_name);
                }
            }
        }
    }

    /* Produce an invoke C function outcome. */
    MVM_disp_program_record_c_code_constant(tc, syscall->wrapper, args_capture);
    MVM_args_set_result_obj(tc, tc->instance->VMNull, MVM_RETURN_CURRENT_FRAME);
}

/* Gets the MVMCFunction object wrapping the boot syscall dispatcher. */
MVMObject * MVM_disp_boot_syscall_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_syscall);
}

/* The boot-resume dispatcher resumes the first dispatcher found
 * by walking down the call stack (not counting the current one). */
static void boot_resume(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVM_disp_program_record_resume(tc, capture);
}

/* Gets the MVMCFunction object wrapping the boot resume dispatcher. */
MVMObject * MVM_disp_boot_resume_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_resume);
}

/* The boot-resume-caller dispatcher skips past the current frame on
 * the callstack (and any immediately preceding dispatcher), and then
 * proceeds as `boot-resume` would. */
static void boot_resume_caller(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVM_disp_program_record_resume_caller(tc, capture);
}

/* Gets the MVMCFunction object wrapping the boot resume caller dispatcher. */
MVMObject * MVM_disp_boot_resume_caller_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_resume_caller);
}

/* The lang-call dispatcher first looks at if we have a VM-level code handle
 * or C function handle, and if so invokes it. Otherwise, it looks at the
 * HLL of the type of the object it finds, resolves the dispatcher of that
 * HLL, and then delegates to it. If there is no dispatcher found for the
 * HLL in question, an exception is thrown. It expects the first argument in
 * the capture to be the target of the invocation and the rest to be the
 * arguments. Establishes a type guard on the callee. */
static void lang_call(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* Obtain and guard on the first argument of the capture, which is the
     * thing to invoke. */
    MVMObject *tracked_invokee;
    MVMROOT(tc, capture, {
         tracked_invokee = MVM_disp_program_record_track_arg(tc, capture, 0);
    });
    MVM_disp_program_record_guard_type(tc, tracked_invokee);

    /* If it's a VM code object or a VM function, we'll delegate to the
     * boot code dispatcher. */
    MVMObject *invokee = MVM_capture_arg_pos_o(tc, capture, 0);
    MVMString *delegate;
    if (REPR(invokee)->ID == MVM_REPR_ID_MVMCode ||
            REPR(invokee)->ID == MVM_REPR_ID_MVMCFunction) {
        if (!IS_CONCRETE(invokee))
            MVM_exception_throw_adhoc(tc, "lang-code code handle must be concrete");
        MVM_disp_program_record_guard_concreteness(tc, tracked_invokee);
        delegate = tc->instance->str_consts.boot_code;
    }

    /* Otherwise go on langauge and delegate to its registered disaptcher. */
    else {
        MVMHLLConfig *hll = STABLE(invokee)->hll_owner;
        if (!hll)
            MVM_exception_throw_adhoc(tc,
                    "lang-call cannot invoke object of type '%s' belonging to no language",
                    STABLE(invokee)->debug_name);
        delegate = hll->call_dispatcher;
        if (!delegate) {
            char *lang_name = MVM_string_utf8_encode_C_string(tc, hll->name);
            char *waste[] = { lang_name, NULL };
            MVM_exception_throw_adhoc_free(tc, waste,
                    "No language call dispatcher registered for %s",
                    lang_name);
        }
    }

    MVM_disp_program_record_delegate(tc, delegate, capture);
}

/* Gets the MVMCFunction object wrapping the language-sensitive call dispatcher. */
MVMObject * MVM_disp_lang_call_dispatch(MVMThreadContext *tc) {
    return wrap(tc, lang_call);
}

/* The lang-meth-call dispatcher looks at the language of the type of the
 * invocant, which should be in the first argument of the capture. If
 * there is one, it delegates to the registered method dispatcher for that
 * language, establishing a HLL guard on the invocant. If not, then the
 * type must have a metaclass that is a KnowHOW, and the method will be
 * resolved using the method table, with guards being established on both
 * the invocant and the name (which must be the second argument). There
 * will then be a delegation to the lang-call dispatcher to take care of
 * the invocation. The arguments (including the invocant, possibly simply
 * repeated or possibly in a container) should follow the method name. If
 * there is neither a language set on the invocant and it does not have a
 * KnowHOW metaclass, an exception will be thrown. */
static void lang_meth_call(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* If the invocant has an associated HLL and method dispatcher, delegate there. */
    MVMObject *tracked_invocant;
    MVMROOT(tc, capture, {
         tracked_invocant = MVM_disp_program_record_track_arg(tc, capture, 0);
    });
    MVMObject *invocant = MVM_capture_arg_pos_o(tc, capture, 0);
    MVMHLLConfig *hll = STABLE(invocant)->hll_owner;
    if (hll && hll->method_call_dispatcher) {
        MVM_disp_program_record_guard_hll(tc, tracked_invocant);
        MVM_disp_program_record_delegate(tc, hll->method_call_dispatcher, capture);
        return;
    }

    /* Otherwise if it's a KnowHOW, then look in its method table (this is how
     * method dispatch bottoms out in the VM). Since the method table depends
     * on the invocant, add a guard. */
    MVM_disp_program_record_guard_type(tc, tracked_invocant);
    MVMObject *HOW;
    MVMROOT2(tc, capture, invocant, {
        HOW = MVM_6model_get_how(tc, STABLE(invocant));
    });
    if (REPR(HOW)->ID == MVM_REPR_ID_KnowHOWREPR && IS_CONCRETE(HOW)) {
        MVMObject *methods = ((MVMKnowHOWREPR *)HOW)->body.methods;
        MVMString *method_name = MVM_capture_arg_pos_s(tc, capture, 1);
        MVMObject *method = MVM_repr_at_key_o(tc, methods, method_name);
        if (IS_CONCRETE(method)) {
            MVMROOT2(tc, capture, method, {
                /* Method found. Guard on the name. */
                MVMObject *tracked_name = MVM_disp_program_record_track_arg(tc, capture, 1);
                MVM_disp_program_record_guard_literal(tc, tracked_name);

                /* Drop leading invocant and name. */
                MVMObject *args_capture = MVM_disp_program_record_capture_drop_arg(tc,
                        MVM_disp_program_record_capture_drop_arg(tc, capture, 0), 0);

                /* Insert resolved method. */
                MVMRegister method_reg = { .o = method };
                MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                        args_capture, 0, MVM_CALLSITE_ARG_OBJ, method_reg);
                MVM_disp_program_record_delegate(tc, tc->instance->str_consts.lang_call,
                        del_capture);
            });
        }
        else {
            MVM_disp_program_record_delegate(tc,
                    tc->instance->str_consts.lang_meth_not_found, capture);
        }
        return;
    }

    /* Otherwise, error. */
    MVM_exception_throw_adhoc(tc,
            "lang-meth-call cannot work out how to dispatch on type '%s'",
            STABLE(invocant)->debug_name);
}

/* Gets the MVMCFunction object wrapping the language-sensitive method call
 * dispatcher. */
MVMObject * MVM_disp_lang_meth_call_dispatch(MVMThreadContext *tc) {
    return wrap(tc, lang_meth_call);
}

/* Receives three arguments: an invocant, a method name (str), and a flag
 * indicating that failure to find the method is an exception. Evaluates to
 * a value that is either the resolved method or, in the case that a method
 * is not found but the exception flag is not set, a VMNull. */
static void lang_find_meth(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* If the invocant has an associated HLL and find method dispatcher,
     * guard on the HLL and delegate there. */
    MVMObject *invocant = MVM_capture_arg_pos_o(tc, capture, 0);
    MVMHLLConfig *hll = STABLE(invocant)->hll_owner;
    if (hll && hll->find_method_dispatcher) {
        MVMROOT(tc, capture, {
            MVMObject *tracked_invocant = MVM_disp_program_record_track_arg(tc, capture, 0);
            MVM_disp_program_record_guard_hll(tc, tracked_invocant);
        });
        MVM_disp_program_record_delegate(tc, hll->find_method_dispatcher, capture);
        return;
    }

    /* Obtain and guard on the first argument of the capture, which is the
     * invocant of the method call, and then also on the name and the
     * exception flag. */
    MVMROOT(tc, capture, {
        MVMObject *tracked_invocant = MVM_disp_program_record_track_arg(tc, capture, 0);
        MVM_disp_program_record_guard_type(tc, tracked_invocant);
        for (MVMuint8 i = 1; i <= 2; i++) {
            MVMObject *tracked_arg = MVM_disp_program_record_track_arg(tc, capture, i);
            MVM_disp_program_record_guard_literal(tc, tracked_arg);
        }
    });


    /* Otherwise if it's a KnowHOW, then look in its method table (this is how
     * method dispatch bottoms out in the VM). */
    MVMint64 exceptional = MVM_capture_arg_pos_i(tc, capture, 2);
    MVMObject *HOW;
    MVMROOT2(tc, capture, invocant, {
        HOW = MVM_6model_get_how(tc, STABLE(invocant));
    });
    if (REPR(HOW)->ID == MVM_REPR_ID_KnowHOWREPR && IS_CONCRETE(HOW)) {
        MVMObject *methods = ((MVMKnowHOWREPR *)HOW)->body.methods;
        MVMString *method_name = MVM_capture_arg_pos_s(tc, capture, 1);
        MVMObject *method = MVM_repr_at_key_o(tc, methods, method_name);
        if (IS_CONCRETE(method)) {
            /* Method found, produce that as the value result. */
            MVMRegister method_reg = { .o = method };
            MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                    capture, 0, MVM_CALLSITE_ARG_OBJ, method_reg);
            MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
                    del_capture);
        }
        else if (exceptional) {
            /* Method not found, and we want an exception. Drop the final
             * argument (exception flag) and delgate to the not found thrower. */
            MVMObject *del_capture = MVM_disp_program_record_capture_drop_arg(tc, capture, 2);
            MVM_disp_program_record_delegate(tc,
                    tc->instance->str_consts.lang_meth_not_found, del_capture);
        }
        else {
            /* Not found; produce a VM null value. */
            MVMRegister method_reg = { .o = tc->instance->VMNull };
            MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                    capture, 0, MVM_CALLSITE_ARG_OBJ, method_reg);
            MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
                    del_capture);
        }
        return;
    }

    /* Otherwise, error or null. */
    if (exceptional) {
        MVM_exception_throw_adhoc(tc,
                "lang-find-meth cannot work out how to look for a method on type '%s'",
                STABLE(invocant)->debug_name);
    }
    else {
        MVMRegister method_reg = { .o = tc->instance->VMNull };
        MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                capture, 0, MVM_CALLSITE_ARG_OBJ, method_reg);
        MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
                del_capture);
    }
}

/* Gets the MVMCFunction object wrapping the language-sensitive find method
 * dispatcher. */
MVMObject * MVM_disp_lang_find_meth_dispatch(MVMThreadContext *tc) {
    return wrap(tc, lang_find_meth);
}

/* Checks if the calling language has a method not found handler configured,
 * and if so invokes it. Failing that, throws an exception. Expects the same
 * arguments that lang-meth-call does. */
static void lang_meth_not_found(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);
    MVMHLLConfig *hll = MVM_hll_current(tc);
    if (hll && hll->method_not_found_error) {
        /* Have a handler for this langauge. */
        MVMRegister code_reg = { .o = hll->method_not_found_error };
        MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                capture, 0, MVM_CALLSITE_ARG_OBJ, code_reg);
        MVM_disp_program_record_delegate(tc, tc->instance->str_consts.lang_call,
                del_capture);
    }
    else {
        /* No handler, so just produce error. */
        MVMObject *invocant = MVM_capture_arg_pos_o(tc, capture, 0);
        MVMString *method_name = MVM_capture_arg_pos_s(tc, capture, 1);
        char *c_name = MVM_string_utf8_encode_C_string(tc, method_name);
        char *waste[] = { c_name, NULL };
        MVM_exception_throw_adhoc_free(tc, waste,
                "Cannot find method '%s' on object of type %s",
                c_name, STABLE(invocant)->debug_name);
    }
}

/* Gets the MVMCFunction object wrapping the language-aware method not found
 * error production dispatcher. */
MVMObject * MVM_disp_lang_meth_not_found_dispatch(MVMThreadContext *tc) {
    return wrap(tc, lang_meth_not_found);
}

/* The boot-boolify dispatcher looks at the boolification protocol setting
 * configured on the target object and uses it to decide what to do. */
static void boot_boolify(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 1);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* Always guard on the type of the object being tested. */
    MVMObject *tracked_object;
    MVMROOT(tc, capture, {
         tracked_object = MVM_disp_program_record_track_arg(tc, capture, 0);
    });
    MVM_disp_program_record_guard_type(tc, tracked_object);

    /* Now go on the boolification protocol value of the object. */
    MVMObject *object = MVM_capture_arg_pos_o(tc, capture, 0);
    MVMBoolificationSpec *bs = object->st->boolification_spec;
    MVMString *syscall = NULL;
    switch (bs ? bs->mode : MVM_BOOL_MODE_NOT_TYPE_OBJECT) {
        case MVM_BOOL_MODE_CALL_METHOD: {
            /* Insert the method to call and delegate to the lang-call
             * dispatcher. */
            MVMRegister callee_reg = { .o = bs->method };
            MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                capture, 0, MVM_CALLSITE_ARG_OBJ, callee_reg);
            MVM_disp_program_record_delegate(tc, tc->instance->str_consts.lang_call,
                del_capture);
            return;
        }
        case MVM_BOOL_MODE_UNBOX_INT:
            syscall = tc->instance->str_consts.boolify_boxed_int;
            break;
        case MVM_BOOL_MODE_UNBOX_NUM:
            syscall = tc->instance->str_consts.boolify_boxed_num;
            break;
        case MVM_BOOL_MODE_UNBOX_STR_NOT_EMPTY:
            syscall = tc->instance->str_consts.boolify_boxed_str;
            break;
        case MVM_BOOL_MODE_UNBOX_STR_NOT_EMPTY_OR_ZERO:
            syscall = tc->instance->str_consts.boolify_boxed_str_with_zero_false;
            break;
        case MVM_BOOL_MODE_NOT_TYPE_OBJECT: {
            MVM_disp_program_record_guard_concreteness(tc, tracked_object);
            MVMRegister result = { .i64 = IS_CONCRETE(object) };
            MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                    capture, 0, MVM_CALLSITE_ARG_INT, result);
            MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
                    del_capture);
            return;
        }
        case MVM_BOOL_MODE_BIGINT:
            syscall = tc->instance->str_consts.boolify_bigint;
            break;
        case MVM_BOOL_MODE_ITER:
            syscall = tc->instance->str_consts.boolify_iter;
            break;
        case MVM_BOOL_MODE_HAS_ELEMS:
            syscall = tc->instance->str_consts.boolify_using_elems;
            break;
        default:
            printf("%d\n", bs ? bs->mode : MVM_BOOL_MODE_NOT_TYPE_OBJECT);
            MVM_exception_throw_adhoc(tc, "Invalid boolification spec mode used");
    }

    /* If we get here, we're delegating to a syscall if the object is concrete
     * or producing 0 if it's not. */
    MVM_disp_program_record_guard_concreteness(tc, tracked_object);
    if (IS_CONCRETE(object)) {
        assert(syscall);
        MVMRegister callname = { .s = syscall };
        MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                capture, 0, MVM_CALLSITE_ARG_STR, callname);
        MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_syscall,
                del_capture);
    }
    else {
        MVMRegister result = { .i64 = 0 };
        MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                capture, 0, MVM_CALLSITE_ARG_INT, result);
        MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
                del_capture);
    }
}

/* Gets the MVMCFunction object wrapping the boolification dispatcher. */
MVMObject * MVM_disp_boot_boolify_dispatch(MVMThreadContext *tc) {
    return wrap(tc, boot_boolify);
}

/* The lang-hllize dispatcher looks at the current HLL and delegates
 * to the registered language hllization dispatcher */
static void lang_hllize(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 2);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* Guard on the type's HLL (something later may strengthen it, if it is
     * doing a type mapping). */
    MVMROOT(tc, capture, {
        MVM_disp_program_record_guard_hll(tc,
                MVM_disp_program_record_track_arg(tc, capture, 0));
    });

    MVMCallsite *cs = ((MVMCapture *)capture)->body.callsite;

    MVMHLLConfig *hll;
    if (cs->num_pos == 1) {
        hll = MVM_disp_program_record_get_hll(tc);
    }
    else {
        MVMROOT(tc, capture, {
            MVM_disp_program_record_guard_literal(tc,
                MVM_disp_program_record_track_arg(tc, capture, 1));
        });
        MVMRegister name;
        MVMCallsiteFlags name_kind;
        MVM_capture_arg_pos(tc, capture, 1, &name, &name_kind);
        hll = MVM_hll_get_config_for(tc,
            name_kind == MVM_CALLSITE_ARG_STR
                ? name.s
                : MVM_repr_get_str(tc, name.o)
        );
        capture = MVM_disp_program_record_capture_drop_arg(tc, capture, 1);
    }

    MVMRegister value;
    MVMCallsiteFlags kind;
    MVM_capture_arg_pos(tc, capture, 0, &value, &kind);

    if (hll && hll->hllize_dispatcher && (kind != MVM_CALLSITE_ARG_OBJ || hll != STABLE(value.o)->hll_owner)) {
        MVM_disp_program_record_delegate(tc, hll->hllize_dispatcher, capture);
        return;
    }

    MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_value,
            capture);
}

/* Gets the MVMCFunction object wrapping the hllization dispatcher. */
MVMObject * MVM_disp_lang_hllize_dispatch(MVMThreadContext *tc) {
    return wrap(tc, lang_hllize);
}


/* The lang-isinvokable dispatcher first checks if the object is a VM bytecode
 * handle, and if so evalutes to 1. Failing that, it looks at the HLL of the
 * type of the object, sees if it has an isinvokable dispatcher registered,
 * and delegates to it if so. Otherwise, evalutes to 0. */
static void lang_isinvokable(MVMThreadContext *tc, MVMArgs arg_info) {
    MVMArgProcContext arg_ctx;
    MVM_args_proc_setup(tc, &arg_ctx, arg_info);
    MVM_args_checkarity(tc, &arg_ctx, 1, 2);
    MVMObject *capture = MVM_args_get_required_pos_obj(tc, &arg_ctx, 0);

    /* Guard on the type, as that's how we make our decision about either
     * the outcome or where to delegate. */
    MVMROOT(tc, capture, {
        MVM_disp_program_record_guard_type(tc,
                MVM_disp_program_record_track_arg(tc, capture, 0));
    });

    /* Get the value, and ensure it's an object (natives are certainly not
     * invokable). */
    MVMRegister value;
    MVMCallsiteFlags kind;
    MVM_capture_arg_pos(tc, capture, 0, &value, &kind);
    if (kind == MVM_CALLSITE_ARG_OBJ) {
        /* If it's a code ref, we're done. */
        MVMObject *object = value.o;
        if (MVM_code_iscode(tc, object)) {
            MVMRegister res_reg = { .i64 = 1 };
            MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
                    capture, 0, MVM_CALLSITE_ARG_INT, res_reg);
            MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
                    del_capture);
            return;
        }

        /* Otherwise, look at the HLL and see if it has a dispatcher. */
        MVMHLLConfig *hll = STABLE(object)->hll_owner;
        if (hll && hll->isinvokable_dispatcher) {
            MVM_disp_program_record_delegate(tc, hll->isinvokable_dispatcher, capture);
            return;
        }
    }

    /* If we get here, can decide it's not invokable. */
    MVMRegister res_reg = { .i64 = 0 };
    MVMObject *del_capture = MVM_disp_program_record_capture_insert_constant_arg(tc,
            capture, 0, MVM_CALLSITE_ARG_INT, res_reg);
    MVM_disp_program_record_delegate(tc, tc->instance->str_consts.boot_constant,
            del_capture);
}

/* Gets the MVMCFunction object wrapping the lang-isinvokable dispatcher. */
MVMObject * MVM_disp_lang_isinvokable_dispatch(MVMThreadContext *tc) {
    return wrap(tc, lang_isinvokable);
}
