9. Runt

Inside of the Scheme interpreter is a Runt interpreter. Runt is a small and simple (and possibly stupid) stack-based language. It is the language actually interfaces with the Graforge engine used to synthesize audio.

9.1. Core Data

Runt is contained in a top-level struct called runt_vm. A corresponding flag is used to indiciate if the data has been loaded. Is is set to be false.

<<struct_contents>>=
int runt_loaded;
runt_vm vm;
<<function_declarations>>=
#ifdef RUNT_H
runt_vm * monolith_runt_vm(monolith_d *m);
#endif
<<functions>>=
runt_vm * monolith_runt_vm(monolith_d *m)
{
    return &m->vm;
}

It is allocated and initialized at runtime inside the main init function.

<<init>>=
m->runt_loaded= 0;
runt_vm_alloc(&m->vm, 1024, 4 * RUNT_MEGABYTE);

At cleanup, this memory is freed.

<<cleanup>>=
runt_vm_free(&m->vm);

9.2. Runt Initialization

9.2.1. The Main Runt Loader

9.2.1.1. The Runt Loader in C (monolith_runt_loader)

The runt dictionary is loaded with the function monolith_runt_loader.

NOTE: you may notice some USE_MICROGARDEN declarations. These are needed to load words from the Muvik Labs proprietary DSP library.

<<function_declarations>>=
void monolith_runt_loader(monolith_d *m);

When runt is already loaded (indicated by the runt_loadedflag), Runt will be reloaded. Since there is no automatic runt_reinit function in the Runt API, one will be created here, called tmp_runt_reinit. Someday, this might find it's way into the real runt codebase.

Reloading runt should be a safe enough operation. All Runt memory is done handled with a write-only memory pool. Graforge, once configured, should be able to run completely independent of Runt. This is probably not a thread safe operation, but it is assumed that nothing is trying to touch the Runt VM when this is happening.

<<functions>>=
<<runt_functions>>
#ifdef USE_MICROGARDEN
int runt_load_microgarden(runt_vm *vm, runt_ptr pw);
#endif
int monolith_nodes_loader(runt_vm *vm, runt_ptr pw);
static void tmp_runt_reinit(runt_vm *vm)
{
    runt_init(vm);
    runt_cell_pool_set(vm,
                       vm->cell_pool.cells,
                       vm->cell_pool.size);
    runt_cell_pool_init(vm);
    runt_memory_pool_set(vm,
                         vm->memory_pool.data,
                         vm->memory_pool.size);
}
void monolith_runt_loader(monolith_d *m)
{
    runt_vm *vm;
    runt_ptr p;
    runt_int rc;
    runt_ptr pw;

    vm = &m->vm;
    rc = runt_data_search(vm, "__ml", &p);
    if(rc == RUNT_OK || m->runt_loaded) {
        runt_print(vm, "Re-initializing Runt.\n");
        tmp_runt_reinit(vm);
    }
    runt_filehandle(vm, stdout);
    m->runt_loaded = 1;
    p = runt_mk_cptr(vm, m);
    runt_set_state(&m->vm, RUNT_MODE_INTERACTIVE, RUNT_OFF);
    runt_data(vm, "__ml", 4, p);
    runt_load_stdlib(vm);
    pw = runt_load_graforge_withdata(vm, m->patch);
#ifdef USE_MICROGARDEN
    runt_load_microgarden(vm, pw);
#endif
    monolith_nodes_loader(vm, pw);
<<monolith_runt_loader>>
    monolith_runt_auxload(vm);
    runt_set_state(&m->vm, RUNT_MODE_INTERACTIVE, RUNT_ON);
}
9.2.1.2. Runt Loader in Scheme (monolith:runt-loader)

This function is encapsulated inside the scheme function monolith:runt-loader.

<<primitive_entries>>=
{"monolith:runt-loader", pp_runt_loader, 0, 0, {CHR,___,___}},
<<scheme_functions>>=
static cell pp_runt_loader(cell x) {
    monolith_runt_loader(monolith_data_get());
    return UNSPECIFIC;
}
9.2.1.3. DONE Runt Loader in Janet (monolith/runt-loader)

CLOSED: [2019-11-10 Sun 19:11] Wraps monolith_runt_loader into a janet function called monolith/runt_loader.

<<core_janet_entries>>=
{
"monolith/runt-loader",
j_runt_loader,
"Loads runt words."
},
<<janet_functions>>=
static Janet j_runt_loader(int32_t argc, Janet *argv)
{
    monolith_d *m;
    janet_fixarity(argc, 0);
    m = monolith_data_get();
    monolith_runt_loader(m);
    return janet_wrap_nil();
}

9.2.2. A Runt Loader for the Runt Interpreter

The Runt interpeter is used to directly run runt code from the commandline, rather than indirectly through scheme. A loader callback must take in a single runt_vm argument, and returns an integer.

This loader is designed to load all runt words that do not directly require monolith (graforge, etc).

For now, the standard library, Graforge, and Microgarden are the only libraries that get loaded here.

The code in this loader lifts code from from monolith_runt_loader. Unfortunately, it is hard to avoid the code duplication. In the Runt version, Graforge allocation is managed by Runt, while the main monolith version has Graforge handled externally by Monolith.

That being said, common words between the Runt interpreter and the main Monolith scheme interpreter are loaded with monolith_runt_auxload.

<<static_function_declarations>>=
static runt_int runt_monolith_loader(runt_vm *vm);
<<functions>>=
int monolith_nodes_loader(runt_vm *vm, runt_ptr pw);
static runt_int runt_monolith_loader(runt_vm *vm)
{
    runt_ptr pw;
    runt_load_stdlib(vm);
    runt_filehandle(vm, stdout);
    pw = runt_load_graforge(vm);
#ifdef USE_MICROGARDEN
    runt_load_microgarden(vm, pw);
#endif
    monolith_nodes_loader(vm, pw);
    monolith_runt_auxload(vm);
    return RUNT_OK;
}

9.2.3. Monolith Runt Functions

Monolith Runt Functions are declared with the function monolith_runt_define.

<<function_declarations>>=
void monolith_runt_define(monolith_d *m,
                        const char *name,
                        runt_uint size,
                        runt_proc proc);
<<functions>>=
void monolith_runt_define(monolith_d *m,
                        const char *name,
                        runt_uint size,
                        runt_proc proc)
{
    runt_cell *c;
    runt_keyword_define(&m->vm, name, size, proc, &c);
    runt_cell_data(&m->vm, c, runt_mk_cptr(&m->vm, (void *)m));
}

9.2.4. Auxiliary Runt Words and Libraries

A handful of Runt words exist in the Monolith ecosystem that do not require monolith. These are loaded using a function called monolith_runt_auxload.

This loader is loaded in both monolith_runt_loader and monolith_load.

<<function_declarations>>=
int monolith_runt_auxload(runt_vm *vm);
<<functions>>=
<<monolith_runt_auxload_funcdefs>>
int monolith_runt_auxload(runt_vm *vm)
{
<<monolith_runt_auxload>>
    return runt_is_alive(vm);
}

9.3. Runt Evaluation

9.3.1. In C

A runt string is evaluated using the function monolith_runt_eval.

<<function_declarations>>=
int monolith_runt_eval(monolith_d *m, const char *s);
<<functions>>=
int monolith_runt_eval(monolith_d *m, const char *str)
{
    runt_int rc;
    runt_vm *vm;

    if (!m->runt_loaded) {
        fprintf(stderr, "Runt is not loaded yet.\n");
        return RUNT_NOT_OK;
    }

    vm = &m->vm;
    runt_pmark_set(vm);
    rc = runt_compile(vm, str);
    runt_pmark_free(vm);

    if (rc != RUNT_OK) {
        runt_print(vm, "Runt Error.\n");
    }
    return rc;
}

9.3.2. In Scheme

This function is encapsulated in a scheme function called monolith:runt-eval.

<<primitive_entries>>=
{"monolith:runt-eval", pp_runt_eval, 1, 1, {STR,___,___}},
<<scheme_functions>>=
static cell pp_runt_eval(cell x) {
    cell p;
    int rc;
    p = car(x);
    rc = monolith_runt_eval(monolith_data_get(), string(p));
    if (rc != RUNT_OK) {
        return error("runt-eval", p);
    }
    return UNSPECIFIC;
}

9.3.3. In Janet

In janet, this is known as monolith/runt-eval.

<<core_janet_entries>>=
{
"monolith/runt-eval",
j_runt_eval,
"Evaluates a runt string."
},
<<janet_functions>>=
static Janet j_runt_eval(int32_t argc, Janet *argv)
{
    monolith_d *m;
    const char *str;
    m = monolith_data_get();
    janet_fixarity(argc, 1);
    str = (const char *)janet_unwrap_string(argv[0]);
    monolith_runt_eval(m, str);
    return janet_wrap_nil();
}

9.4. Runt Mark Set

Runt uses a pool-based model for memory, which requires you to "set" memory markers any time you want to retain memory for doing things like creating runt words. This function can be called indirectely using the function monolith_runt_mark_set.

<<function_declarations>>=
void monolith_runt_mark_set(monolith_d *m);
<<functions>>=
void monolith_runt_mark_set(monolith_d *m)
{
    runt_mark_set(&m->vm);
}

9.5. Creating a new runt keyword

A new keyword can be added using the function monolith_runt_keyword.

<<function_declarations>>=
void monolith_runt_keyword(monolith_d *m,
                           const char *name,
                           runt_uint size,
                           runt_proc proc,
                           void *ud);
<<functions>>=
void monolith_runt_keyword(monolith_d *m,
                           const char *name,
                           runt_uint size,
                           runt_proc proc,
                           void *ud)
{
    runt_cell *c;
    runt_keyword_define(&m->vm, name, size, proc, &c);
    runt_cell_data(&m->vm, c, runt_mk_cptr(&m->vm, ud));
}

9.6. Runt Word Search Loader

Monolith has a special keyword called "ws" which allows one to look up keywords in the dictionary. It was originally built for monomer, the predecessor to monolith. It is loaded at runtime.

<<monolith_runt_auxload_funcdefs>>=
void load_ws(runt_vm *vm);
<<monolith_runt_auxload>>=
load_ws(vm);

9.7. Runt Libraries

9.7.1. Runt-img

# # # #

9.8. C Data, Runt and Janet Janet

9.8.1. Push/Pop Data from Janet

9.8.1.1. Pushing

In certain situations (like working with TDelay in scheme), it is useful to push user data from Janet to the runt stack.

monolith/runt-push-cptr will take a generic void pointer, and then push it to the stack. Subsequent runt functions would then be able to pull from this stack and use it.

<<core_janet_entries>>=
{
"monolith/runt-push-cptr",
j_runt_push_cptr,
"Pushes C pointer to the Runt Stack.\n"
},
<<janet_functions>>=
static Janet j_runt_push_cptr(int32_t argc, Janet *argv)
{
    runt_vm *vm;
    runt_int rc;
    runt_stacklet *s;
    monolith_d *m;
    void *ud;

    janet_fixarity(argc, 1);

    m = monolith_data_get();
    vm = monolith_runt_vm(m);
    ud = janet_unwrap_abstract(argv[0]);

    rc = runt_ppush(vm, &s);

    if (rc != RUNT_OK) {
        return janet_wrap_nil();
    }

    s->p = runt_mk_cptr(vm, ud);

    return janet_wrap_nil();
}
9.8.1.2. Popping

Used to retrieve data from Runt. Saves it to Janet as an abstract value. We'll call it monolith/runt-pop-cptr!

<<core_janet_entries>>=
{
"monolith/runt-pop-cptr",
j_runt_pop_cptr,
"Pops C pointer from the runt stack.\n"
},
<<janet_functions>>=
static Janet j_runt_pop_cptr(int32_t argc, Janet *argv)
{
    runt_vm *vm;
    runt_int rc;
    runt_stacklet *s;
    monolith_d *m;
    void *ud;

    janet_fixarity(argc, 0);

    m = monolith_data_get();
    vm = monolith_runt_vm(m);

    rc = runt_ppop(vm, &s);

    if (rc != RUNT_OK) {
        return janet_wrap_nil();
    }

    ud = runt_to_cptr(s->p);

    return janet_wrap_abstract(ud);
}

9.8.2. TODO Push/Pop Data from Scheme

9.8.2.1. TODO Pushing

Data from runt can be popped using monolith:runt-pop-cptr. This will save it as the recently-added pointer type in s9 scheme.

9.8.2.2. TODO Popping

A pointer type stored in scheme can be pushed to the runt stack with monolith:runt-push-cptr.

9.8.3. TODO Set/Get Registers From Scheme

9.8.3.1. Getting

The function monolith:runt-regset will set a value from Scheme. Value should be a C pointer.

9.8.3.2. Setting

The function monolith:runt-regget will get a value from a register and store it as a generic C pointer in Scheme.

9.9. Next Free Register

The function monolith_nextfree will return the next apparently free register id number. This should hopefully abstract away some of the tedious register management required anytime a signal needs to be duplicated.

The argument supplied to this function is the starting position to begin looking for a free register. It will swing back when it reaches the end. This can help speed up lookup in certain cases.

9.9.1. Nextfree in C

<<function_declarations>>=
int monolith_nextfree(monolith_d *m, int start);
<<functions>>=
int monolith_nextfree(monolith_d *m, int start)
{
    if (!m->runt_loaded) {
        fprintf(stderr, "nextfree: Runt is not loaded yet.\n");
        return -1;
    }

    return runt_register_nextfree(&m->vm, start);
}

9.9.2. Nextfree in Scheme

<<primitive_entries>>=
{"monolith:nextfree", pp_nextfree, 1, 1, {INT,___,___}},
<<scheme_functions>>=
static cell pp_nextfree(cell x) {
    int start;
    int nextfree;

    start = integer_value(NULL, car(x));
    nextfree = monolith_nextfree(monolith_data_get(),
                                 start);

    return make_integer(nextfree);
}

9.10. Loading More Runt Words

While it's not recommended, it is technically possible to define more keywords at any point in time. This can be done with monolith_runt_more_words. The function itself takes a loader callback. The loader need only take in the monolith data struct. From there, things like runt and graforge data can be retrieved using getters.

<<function_declarations>>=
void monolith_runt_more_words(monolith_d *m,
                              void (*loader)(monolith_d *));

The reason why it's not implemented is due to Runt's current memory-pool model that it uses to allocate memory for dictionary entries. If a string or some other temporarily allocated thing is in the memory pool while new keywords are being defined, they will stuck in there. So it's best to use this as close to run time as possible.

Turning off interactive mode makes it so runt can define new words and keywords.

Calling monolith_runt_mark_set causes the memory pool to store the current size.

<<functions>>=
void monolith_runt_more_words(monolith_d *m,
                              void (*loader) (monolith_d *))
{
    runt_set_state(&m->vm, RUNT_MODE_INTERACTIVE, RUNT_OFF);
    loader(m);
    monolith_runt_mark_set(m);
    runt_set_state(&m->vm, RUNT_MODE_INTERACTIVE, RUNT_ON);
}

9.11. Retrieving Runt Graforge Pointer

Graforge is accessed from runt via a runt pointer. This can be retrieved using the function monolith_runt_graforge.

<<function_declarations>>=
runt_ptr monolith_runt_graforge(monolith_d *m);
<<functions>>=
runt_ptr monolith_runt_graforge(monolith_d *m)
{
    runt_ptr p;
    int rc;

    rc = rgf_plugin_data(&m->vm, &p);

    if (rc != RUNT_OK) return m->vm.nil;

    return p;
}

9.12. Runt + Scheme Error Handling

Some things to make writing error handling a little less tedious.

<<exported_macros>>=
#define MONOLITH_SCHEME_ERROR_CHECK(RC, STR) if(RC != RUNT_OK) {\
    return monolith_scheme_error(STR, UNSPECIFIC);\
}



prev | home | next