diff --git a/src/emu/CMakeLists.txt b/src/emu/CMakeLists.txt index 9fecbbd..3deee46 100644 --- a/src/emu/CMakeLists.txt +++ b/src/emu/CMakeLists.txt @@ -1,47 +1,53 @@ # Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) # SPDX-License-Identifier: GPL-3.0-or-later -add_library(trace STATIC trace.c) -target_link_libraries(trace parson ovni-static) - -add_library(emu STATIC - chan.c - emu.c - nosv.c - openmp.c - ovni.c - tampi.c - nodes.c - kernel.c - nanos6.c - task.c - pcf.c - prv.c -) - include_directories( "${CMAKE_SOURCE_DIR}/src/include" "${CMAKE_SOURCE_DIR}/src" "${CMAKE_SOURCE_DIR}/include" ) -add_executable(ovniemu ovniemu.c) -target_link_libraries(ovniemu emu trace) +add_library(trace STATIC trace.c) +target_link_libraries(trace parson ovni-static) -add_executable(ovnidump ovnidump.c) -target_link_libraries(ovnidump emu trace) +add_library(chan STATIC + chan.c + bay.c + mux.c +) -add_executable(ovnisort ovnisort.c) -target_link_libraries(ovnisort emu trace) - -# Use _ROOT variables if available, commonly used by MPI -# installations -if(POLICY CMP0074) - cmake_policy(SET CMP0074 NEW) -endif() - -find_package(MPI REQUIRED) -add_executable(ovnisync ovnisync.c) -target_link_libraries(ovnisync m MPI::MPI_C) - -install(TARGETS ovniemu ovnidump ovnisync ovnisort RUNTIME DESTINATION bin) +#add_library(emu STATIC +# chan.c +# emu.c +# nosv.c +# openmp.c +# ovni.c +# tampi.c +# nodes.c +# kernel.c +# nanos6.c +# task.c +# pcf.c +# prv.c +#) +# +#add_executable(ovniemu ovniemu.c) +#target_link_libraries(ovniemu emu trace) +# +#add_executable(ovnidump ovnidump.c) +#target_link_libraries(ovnidump emu trace) +# +#add_executable(ovnisort ovnisort.c) +#target_link_libraries(ovnisort emu trace) +# +## Use _ROOT variables if available, commonly used by MPI +## installations +#if(POLICY CMP0074) +# cmake_policy(SET CMP0074 NEW) +#endif() +# +#find_package(MPI REQUIRED) +#add_executable(ovnisync ovnisync.c) +#target_link_libraries(ovnisync m MPI::MPI_C) +# +#install(TARGETS ovniemu ovnidump ovnisync ovnisort RUNTIME DESTINATION bin) diff --git a/src/emu/bay.c b/src/emu/bay.c new file mode 100644 index 0000000..1fd5957 --- /dev/null +++ b/src/emu/bay.c @@ -0,0 +1,151 @@ +#define ENABLE_DEBUG + +#include "bay.h" + +#include "common.h" +#include "uthash.h" +#include "utlist.h" + +/* Called from the channel when it becomes dirty */ +static int +cb_chan_is_dirty(struct chan *chan, void *arg) +{ + UNUSED(chan); + struct bay_chan *bchan = arg; + struct bay *bay = bchan->bay; + + /* TODO: check duplicate? */ + DL_APPEND(bay->dirty, bchan); + return 0; +} + +static struct bay_chan * +find_bay_chan(struct bay *bay, const char *name) +{ + struct bay_chan *bchan = NULL; + HASH_FIND_STR(bay->channels, name, bchan); + + return bchan; +} + +struct chan * +bay_find(struct bay *bay, const char *name) +{ + struct bay_chan *bchan = find_bay_chan(bay, name); + + if (bchan != NULL) + return bchan->chan; + else + return NULL; +} + +int +bay_register(struct bay *bay, struct chan *chan) +{ + struct bay_chan *bchan = find_bay_chan(bay, chan->name); + if (bchan != NULL) { + err("bay_register: channel %s already registered\n", + chan->name); + return -1; + } + + bchan = calloc(1, sizeof(struct bay_chan)); + if (bchan == NULL) { + err("bay_register: calloc failed\n"); + return -1; + } + + bchan->chan = chan; + bchan->bay = bay; + chan_set_dirty_cb(chan, cb_chan_is_dirty, bchan); + + /* Add to hash table */ + HASH_ADD_STR(bay->channels, chan->name, bchan); + + return 0; +} + +int +bay_add_cb(struct bay *bay, struct chan *chan, bay_cb_func_t func, void *arg) +{ + if (func == NULL) { + err("bay_add_cb: func is NULL\n"); + return -1; + } + + struct bay_chan *bchan = find_bay_chan(bay, chan->name); + if (bchan == NULL) { + err("bay_add_cb: cannot find channel %s in bay\n", chan->name); + return -1; + } + + struct bay_cb *cb = calloc(1, sizeof(struct bay_cb)); + + if (cb == NULL) { + err("bay_add_cb: calloc failed\n"); + return -1; + } + + cb->func = func; + cb->arg = arg; + + DL_APPEND(bchan->cb, cb); + bchan->ncallbacks++; + return 0; +} + + + +void +bay_init(struct bay *bay) +{ + memset(bay, 0, sizeof(struct bay)); + bay->state = BAY_READY; +} + +static int +propagate_chan(struct bay_chan *bchan) +{ + dbg("propagating dirty channel %p\n", (void *) bchan); + + struct bay_cb *cur = NULL; + struct bay_cb *tmp = NULL; + DL_FOREACH_SAFE(bchan->cb, cur, tmp) { + if (cur->func(bchan->chan, cur->arg) != 0) { + err("propagate_chan: callback failed\n"); + return -1; + } + } + + return 0; +} + +int +bay_propagate(struct bay *bay) +{ + dbg("propagating channels\n"); + struct bay_chan *cur = NULL; + struct bay_chan *tmp = NULL; + DL_FOREACH_SAFE(bay->dirty, cur, tmp) { + /* May add more dirty channels */ + if (propagate_chan(cur) != 0) { + err("bay_propagate: propagate_chan failed\n"); + return -1; + } + } + + bay->dirty = NULL; + + return 0; +} + +//int +//bay_emit(struct bay *bay) +//{ +// for (chan in dirty channels) { +// /* May add more dirty channels */ +// emit_chan(chan); +// } +// +// return 0; +//} diff --git a/src/emu/bay.h b/src/emu/bay.h new file mode 100644 index 0000000..94114cf --- /dev/null +++ b/src/emu/bay.h @@ -0,0 +1,61 @@ +#ifndef BAY_H +#define BAY_H + +#include "chan.h" +#include "uthash.h" + +/* Handle connections between channels and callbacks */ + +struct bay; +struct bay_cb; +struct bay_chan; + +typedef int (*bay_cb_func_t)(struct chan *chan, void *ptr); + +struct bay_cb { + bay_cb_func_t func; + void *arg; + + /* List of callbacks in one channel */ + struct bay_cb *next; + struct bay_cb *prev; +}; + +#define MAX_BAY_NAME 1024 + +struct bay_chan { + struct chan *chan; + int ncallbacks; + struct bay_cb *cb; + struct bay *bay; + + /* Global hash table with all channels in bay */ + UT_hash_handle hh; + + /* Used by dirty list */ + struct bay_chan *next; + struct bay_chan *prev; +}; + +enum bay_state { + BAY_CONFIG = 1, + BAY_READY, + BAY_PROPAGATING, + BAY_PROPAGATED, + BAY_EMITTING, + BAY_EMITTED +}; + +struct bay { + enum bay_state state; + struct bay_chan *channels; + struct bay_chan *dirty; +}; + +int bay_register(struct bay *bay, struct chan *chan); +struct chan *bay_find(struct bay *bay, const char *name); +int bay_add_cb(struct bay *bay, struct chan *chan, bay_cb_func_t func, void *arg); +void bay_init(struct bay *bay); +int bay_propagate(struct bay *bay); + +#endif /* BAY_H */ diff --git a/src/emu/chan.c b/src/emu/chan.c index 117041e..63138c3 100644 --- a/src/emu/chan.c +++ b/src/emu/chan.c @@ -1,402 +1,216 @@ -/* Copyright (c) 2021 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ +#define ENABLE_DEBUG + #include "chan.h" +#include "common.h" +#include -#include "emu.h" -#include "prv.h" -#include "utlist.h" - -static void -chan_init(struct ovni_chan *chan, enum chan_track track, int row, int type, FILE *prv, int64_t *clock) +void +chan_init(struct chan *chan, enum chan_type type, const char *name) { - chan->n = 0; + int len = strlen(name); + if (len >= MAX_CHAN_NAME) + die("chan_init: name '%s' too long\n", name); + + memset(chan, 0, sizeof(*chan)); + memcpy(chan->name, name, len + 1); chan->type = type; - chan->enabled = 0; - chan->badst = ST_NULL; - chan->ev = -1; - chan->prv = prv; - chan->clock = clock; - chan->t = *clock; - chan->row = row; - chan->dirty = 0; - chan->track = track; - chan->lastst = -1; -} - -static void -mark_dirty(struct ovni_chan *chan, enum chan_dirty dirty) -{ - if (chan->dirty != CHAN_CLEAN) - die("mark_dirty: chan %d already dirty\n", chan->id); - - if (dirty == CHAN_CLEAN) - die("mark_dirty: cannot use CHAN_CLEAN\n"); - - dbg("adding dirty chan %d to list\n", chan->id); - chan->dirty = dirty; - DL_APPEND(*chan->update_list, chan); } void -chan_th_init(struct ovni_ethread *th, - struct ovni_chan **update_list, - enum chan id, - enum chan_track track, - int init_st, - int enabled, - int dirty, - int row, - FILE *prv, - int64_t *clock) +chan_set_dirty_cb(struct chan *chan, chan_cb_t func, void *arg) { - struct ovni_chan *chan = &th->chan[id]; - int prvth = chan_to_prvtype[id]; - - chan_init(chan, track, row, prvth, prv, clock); - - chan->id = id; - chan->thread = th; - chan->update_list = update_list; - chan->enabled = enabled; - chan->stack[chan->n++] = init_st; - if (dirty) - mark_dirty(chan, CHAN_DIRTY_ACTIVE); + chan->dirty_cb = func; + chan->dirty_arg = arg; } -void -chan_cpu_init(struct ovni_cpu *cpu, - struct ovni_chan **update_list, - enum chan id, - enum chan_track track, - int init_st, - int enabled, - int dirty, - int row, - FILE *prv, - int64_t *clock) +enum chan_type +chan_get_type(struct chan *chan) { - struct ovni_chan *chan = &cpu->chan[id]; - int prvcpu = chan_to_prvtype[id]; - - chan_init(chan, track, row, prvcpu, prv, clock); - - chan->id = id; - chan->cpu = cpu; - chan->update_list = update_list; - chan->enabled = enabled; - chan->stack[chan->n++] = init_st; - if (dirty) - mark_dirty(chan, CHAN_DIRTY_ACTIVE); + return chan->type; } -static void -chan_dump_update_list(struct ovni_chan *chan) +static int +set_dirty(struct chan *chan) { - dbg("update list for chan %d at %p:\n", chan->id, (void *) chan); - - for (struct ovni_chan *c = *chan->update_list; c; c = c->next) { - dbg(" chan %d at %p\n", c->id, (void *) c); - } -} - -void -chan_enable(struct ovni_chan *chan, int enabled) -{ - /* Can be dirty */ - - dbg("chan_enable chan=%d enabled=%d\n", chan->id, enabled); - - if (chan->enabled == enabled) { - err("chan_enable: chan already in enabled=%d\n", enabled); - abort(); + if (chan->is_dirty) { + err("channel %s already dirty\n", chan->name); + return -1; } - chan->enabled = enabled; - chan->t = *chan->clock; + chan->is_dirty = 1; - /* Only append if not dirty */ - if (!chan->dirty) { - mark_dirty(chan, CHAN_DIRTY_ACTIVE); - } else { - dbg("already dirty chan %d: skip update list\n", - chan->id); - chan_dump_update_list(chan); + if (chan->dirty_cb != NULL) { + if (chan->dirty_cb(chan, chan->dirty_arg) != 0) { + err("dirty callback failed\n"); + return -1; + } } + + return 0; } -void -chan_disable(struct ovni_chan *chan) +static int +check_duplicates(struct chan *chan, struct value *v) { - chan_enable(chan, 0); + /* If duplicates are allowed just skip the check */ + //if (chan->oflags & CHAN_DUP) + // return 0; + + if (value_is_equal(&chan->last_value, v)) { + err("check_duplicates: same value as last_value\n"); + return -1; + } + + return 0; } int -chan_is_enabled(const struct ovni_chan *chan) +chan_set(struct chan *chan, struct value value) { - return chan->enabled; + if (chan->type != CHAN_SINGLE) { + err("chan_set: cannot set on non-single channel\n"); + return -1; + } + + if (chan->is_dirty) { + err("chan_set: cannot modify dirty channel\n"); + return -1; + } + + if (check_duplicates(chan, &value) != 0) { + err("chan_set: cannot set a duplicated value\n"); + return -1; + } + + char buf[128]; + dbg("chan_set: channel %p sets value %s\n", (void *) chan, + value_str(value, buf)); + chan->data.value = value; + + if (set_dirty(chan) != 0) { + err("chan_set: set_dirty failed\n"); + return -1; + } + + return 0; } -void -chan_set(struct ovni_chan *chan, int st) +/** Adds one value to the stack. Fails if the stack is full. + * + * @param ivalue The new integer value to be added on the stack. + * + * @return On success returns 0, otherwise returns -1. + */ +int +chan_push(struct chan *chan, struct value value) { - /* Can be dirty */ - - dbg("chan_set chan %d st=%d\n", chan->id, st); - - if (!chan->enabled) - die("chan_set: chan %d not enabled\n", chan->id); - - /* Only chan_set can set the 0 state */ - if (st < 0) - die("chan_set: cannot set a negative state %d\n", st); - - /* Don't enforce this check if we are dirty because the channel was - * just enabled; it may collide with a new state 0 set via chan_set() - * used by the tracking channels */ - if (chan->dirty != CHAN_DIRTY_ACTIVE - && chan->lastst >= 0 - && chan->lastst == st) { - err("chan_set id=%d cannot emit the state %d twice\n", - chan->id, st); - abort(); + if (chan->type != CHAN_STACK) { + err("chan_push: cannot push on non-stack channel\n"); + return -1; } - if (chan->n == 0) - chan->n = 1; - - chan->stack[chan->n - 1] = st; - chan->t = *chan->clock; - - /* Only append if not dirty */ - if (!chan->dirty) { - mark_dirty(chan, CHAN_DIRTY_VALUE); - } else { - dbg("already dirty chan %d: skip update list\n", - chan->id); - chan_dump_update_list(chan); - } -} - -void -chan_push(struct ovni_chan *chan, int st) -{ - dbg("chan_push chan %d st=%d\n", chan->id, st); - - if (!chan->enabled) - die("chan_push: chan %d not enabled\n", chan->id); - - if (st <= 0) { - err("chan_push: cannot push a non-positive state %d\n", st); - abort(); + if (chan->is_dirty) { + err("chan_push: cannot modify dirty channel\n"); + return -1; } - /* Cannot be dirty */ - if (chan->dirty != CHAN_CLEAN) - die("chan_push: chan %d not clean", chan->id); - - if (chan->lastst >= 0 && chan->lastst == st) { - err("chan_push id=%d cannot emit the state %d twice\n", - chan->id, st); - abort(); + if (check_duplicates(chan, &value) != 0) { + err("chan_push: cannot push a duplicated value\n"); + return -1; } - if (chan->n >= MAX_CHAN_STACK) { + struct chan_stack *stack = &chan->data.stack; + + if (stack->n >= MAX_CHAN_STACK) { err("chan_push: channel stack full\n"); abort(); } - chan->stack[chan->n++] = st; - chan->t = *chan->clock; + stack->values[stack->n++] = value; - mark_dirty(chan, CHAN_DIRTY_VALUE); + if (set_dirty(chan) != 0) { + err("chan_set: set_dirty failed\n"); + return -1; + } + + return 0; } +/** Remove one value from the stack. Fails if the top of the stack + * doesn't match the expected value. + * + * @param expected The expected value on the top of the stack. + * + * @return On success returns 0, otherwise returns -1. + */ int -chan_pop(struct ovni_chan *chan, int expected_st) +chan_pop(struct chan *chan, struct value evalue) { - dbg("chan_pop chan %d expected_st=%d\n", chan->id, expected_st); - - if (!chan->enabled) - die("chan_pop: chan %d not enabled\n", chan->id); - - /* Cannot be dirty */ - if (chan->dirty != CHAN_CLEAN) - die("chan_pop: chan %d not clean", chan->id); - - if (chan->n <= 0) { - err("chan_pop: channel empty\n"); - abort(); + if (chan->type != CHAN_STACK) { + err("chan_pop: cannot pop on non-stack channel\n"); + return -1; } - int st = chan->stack[chan->n - 1]; - - if (expected_st >= 0 && st != expected_st) { - err("chan_pop: unexpected channel state %d (expected %d)\n", - st, expected_st); - abort(); + if (chan->is_dirty) { + err("chan_pop: cannot modify dirty channel\n"); + return -1; } - chan->n--; + struct chan_stack *stack = &chan->data.stack; - /* Take the current stack value */ - st = chan_get_st(chan); - - /* A st == 0 can be obtained when returning to the initial state */ - if (st < 0) { - err("chan_pop: obtained negative state %d from the stack\n", st); - abort(); + if (stack->n <= 0) { + err("chan_pop: channel stack empty\n"); + return -1; } - if (chan->lastst >= 0 && chan->lastst == st) { - err("chan_pop id=%d cannot emit the state %d twice\n", - chan->id, st); - abort(); + struct value *value = &stack->values[stack->n - 1]; + + if (!value_is_equal(value, &evalue)) { + err("chan_pop: unexpected value %ld (expected %ld)\n", + value->i, evalue.i); + return -1; } - chan->t = *chan->clock; + stack->n--; - mark_dirty(chan, CHAN_DIRTY_VALUE); + if (set_dirty(chan) != 0) { + err("chan_set: set_dirty failed\n"); + return -1; + } - return st; + return 0; } -void -chan_ev(struct ovni_chan *chan, int ev) +static int +get_value(struct chan *chan, struct value *value) { - dbg("chan_ev chan %d ev=%d\n", chan->id, ev); + if (chan->type == CHAN_SINGLE) { + *value = chan->data.value; + return 0; + } - if (!chan->enabled) - die("chan_ev: chan %d not enabled\n", chan->id); + struct chan_stack *stack = &chan->data.stack; + if (stack->n <= 0) { + err("get_value: channel stack empty\n"); + return -1; + } - /* Cannot be dirty */ - if (chan->dirty != CHAN_CLEAN) - die("chan_ev: chan %d is dirty\n", chan->id); + *value = stack->values[stack->n - 1]; - if (ev <= 0) - die("chan_ev: cannot emit non-positive state %d\n", ev); - - if (chan->lastst >= 0 && chan->lastst == ev) - die("chan_ev id=%d cannot emit the state %d twice\n", - chan->id, ev); - - chan->ev = ev; - chan->t = *chan->clock; - - mark_dirty(chan, CHAN_DIRTY_VALUE); + return 0; } +/** Reads the current value of a channel */ int -chan_get_st(const struct ovni_chan *chan) +chan_read(struct chan *chan, struct value *value) { - if (chan->enabled == 0) - return chan->badst; - - if (chan->n == 0) - return 0; - - if (chan->n < 0) - die("chan_get_st: chan %d has negative n\n", chan->id); - - return chan->stack[chan->n - 1]; -} - -void -chan_copy(struct ovni_chan *dst, const struct ovni_chan *src) -{ - if (!chan_is_enabled(src)) { - chan_disable(dst); - return; + if (get_value(chan, value) != 0) { + err("chan_read: get_value failed\n"); + return -1; } - if (!chan_is_enabled(dst)) - chan_enable(dst, 1); - - if (src->ev != -1) { - chan_ev(dst, src->ev); - } else { - int src_st = chan_get_st(src); - int dst_st = chan_get_st(dst); - if (src_st != dst_st) - chan_set(dst, src_st); - } -} - -static void -emit(struct ovni_chan *chan, int64_t t, int state) -{ - if (chan->dirty == CHAN_CLEAN) - die("emit: chan %d is not dirty\n", chan->id); - - /* A channel can only emit the same state as lastst if is dirty because - * it has been enabled or disabled. Otherwise is a bug (ie. you have two - * consecutive ovni events?) */ - if (chan->lastst != -1 - && chan->dirty == CHAN_DIRTY_VALUE - && chan->lastst == state) { - /* TODO: Print the raw clock of the offending event */ - die("emit: chan %d cannot emit the same state %d twice\n", - chan->id, state); - } - - if (chan->lastst != state) { - prv_ev(chan->prv, chan->row, t, chan->type, state); - - chan->lastst = state; - } -} - -static void -emit_ev(struct ovni_chan *chan) -{ - if (!chan->enabled) - die("emit_ev: chan %d is not enabled\n", chan->id); - - if (chan->ev == -1) - die("emit_ev: chan %d cannot emit -1 ev\n", chan->id); - - int new = chan->ev; - int last = chan_get_st(chan); - - emit(chan, chan->t - 1, new); - emit(chan, chan->t, last); - - chan->ev = -1; -} - -static void -emit_st(struct ovni_chan *chan) -{ - int st = chan_get_st(chan); - - emit(chan, chan->t, st); -} - -/* Emits either the current state or punctual event in the PRV file */ -void -chan_emit(struct ovni_chan *chan) -{ - if (likely(chan->dirty == 0)) - return; - - dbg("chan_emit chan %d\n", chan->id); - - /* Emit badst if disabled */ - if (chan->enabled == 0) { - /* No punctual events allowed when disabled */ - if (chan->ev != -1) - die("chan_emit: no punctual event allowed when disabled\n"); - - emit_st(chan); - goto shower; - } - - /* Otherwise, emit punctual event if any or the state */ - if (chan->ev != -1) - emit_ev(chan); - else - emit_st(chan); - -shower: - chan->dirty = 0; + return 0; } diff --git a/src/emu/chan.h b/src/emu/chan.h index 0837b7e..9659c2f 100644 --- a/src/emu/chan.h +++ b/src/emu/chan.h @@ -1,63 +1,58 @@ -/* Copyright (c) 2021 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef OVNI_CHAN_H -#define OVNI_CHAN_H +#ifndef CHAN_H +#define CHAN_H -#include "emu.h" +#include +#include "value.h" -void -chan_th_init(struct ovni_ethread *th, - struct ovni_chan **update_list, - enum chan id, - enum chan_track track, - int init_st, - int enabled, - int dirty, - int row, - FILE *prv, - int64_t *clock); +#define MAX_CHAN_STACK 512 +#define MAX_CHAN_NAME 512 -void -chan_cpu_init(struct ovni_cpu *cpu, - struct ovni_chan **update_list, - enum chan id, - enum chan_track track, - int row, - int init_st, - int enabled, - int dirty, - FILE *prv, - int64_t *clock); +enum chan_type { + CHAN_SINGLE = 0, + CHAN_STACK = 1, + CHAN_MAXTYPE, +}; -void -chan_enable(struct ovni_chan *chan, int enabled); +struct chan_stack { + int n; + struct value values[MAX_CHAN_STACK]; +}; -void -chan_disable(struct ovni_chan *chan); +union chan_data { + struct chan_stack stack; + struct value value; +}; -int -chan_is_enabled(const struct ovni_chan *chan); +struct chan; -void -chan_set(struct ovni_chan *chan, int st); +typedef int (*chan_cb_t)(struct chan *chan, void *ptr); -void -chan_push(struct ovni_chan *chan, int st); +struct chan { + char name[MAX_CHAN_NAME]; + enum chan_type type; + union chan_data data; + int is_dirty; + struct value err_value; + struct value last_value; + chan_cb_t dirty_cb; + void *dirty_arg; +}; -int -chan_pop(struct ovni_chan *chan, int expected_st); +//int chan_enable(struct chan *chan); +//int chan_disable(struct chan *chan); +//int chan_is_enabled(const struct chan *chan); -void -chan_ev(struct ovni_chan *chan, int ev); +void chan_init(struct chan *chan, enum chan_type type, const char *name); +int chan_set(struct chan *chan, struct value value); +int chan_push(struct chan *chan, struct value value); +int chan_pop(struct chan *chan, struct value expected); +int chan_read(struct chan *chan, struct value *value); +enum chan_type chan_get_type(struct chan *chan); -int -chan_get_st(const struct ovni_chan *chan); +/* Called when it becomes dirty */ +void chan_set_dirty_cb(struct chan *chan, chan_cb_t func, void *arg); -void -chan_emit(struct ovni_chan *chan); - -void -chan_copy(struct ovni_chan *dst, const struct ovni_chan *src); - -#endif /* OVNI_CHAN_H */ +#endif /* CHAN_H */ diff --git a/src/emu/mux.c b/src/emu/mux.c new file mode 100644 index 0000000..c6208ef --- /dev/null +++ b/src/emu/mux.c @@ -0,0 +1,196 @@ +#define ENABLE_DEBUG + +#include "mux.h" + +static int +default_select(struct mux *mux, + struct value key, + struct mux_input **input) +{ + if (value_is_null(key)) { + *input = NULL; + return 0; + } + + struct mux_input *tmp = mux_find_input(mux, key); + + if (tmp == NULL) { + char buf[128]; + err("default_select: cannot find input with key %s\n", + value_str(key, buf)); + return -1; + } + + *input = tmp; + + return 0; +} + +static int +select_input(struct mux *mux, + struct value key, + struct mux_input **input) +{ + if (mux->select_func == NULL) + die("select_input: select_func is NULL\n"); + + if (mux->select_func(mux, key, input) != 0) { + err("select_input: select_func failed\n"); + return -1; + } + + return 0; +} + +/** Called when the select channel changes its value */ +static int +cb_select(struct chan *sel_chan, void *ptr) +{ + struct mux *mux = ptr; + + struct value sel_value; + if (chan_read(sel_chan, &sel_value) != 0) { + err("cb_select: chan_read(select) failed\n"); + return -1; + } + + char buf[128]; + dbg("select channel got value %s\n", + value_str(sel_value, buf)); + + struct mux_input *input = NULL; + if (select_input(mux, sel_value, &input) != 0) { + err("cb_select: select_input failed\n"); + return -1; + } + + dbg("selecting mux input %p\n", (void *) input); + + /* Set to null by default */ + struct value out_value = value_null(); + if (input != NULL && chan_read(input->chan, &out_value) != 0) { + err("cb_select: chan_read() failed\n"); + return -1; + } + + if (chan_set(mux->output, out_value) != 0) { + err("cb_select: chan_set() failed\n"); + return -1; + } + + return 0; +} + +/** Called when the input channel changes its value */ +static int +cb_input(struct chan *in_chan, void *ptr) +{ + struct mux *mux = ptr; + + /* TODO: We may need to cache the last read value from select to avoid + * problems reading the select channel too soon */ + struct value sel_value; + if (chan_read(mux->select, &sel_value) != 0) { + err("cb_input: chan_read(select) failed\n"); + return -1; + } + + struct mux_input *input = NULL; + if (select_input(mux, sel_value, &input) != 0) { + err("cb_input: select_input failed\n"); + return -1; + } + + /* Nothing to do, the input is not selected */ + if (input == NULL || input->chan != in_chan) + return 0; + + dbg("selected mux input %s changed\n", in_chan->name); + + /* Set to null by default */ + struct value out_value = value_null(); + + if (chan_read(in_chan, &out_value) != 0) { + err("cb_input: chan_read() failed\n"); + return -1; + } + + if (chan_set(mux->output, out_value) != 0) { + err("cb_input: chan_set() failed\n"); + return -1; + } + + return 0; +} + +int +mux_init(struct mux *mux, + struct bay *bay, + struct chan *select, + struct chan *output, + mux_select_func_t select_func) +{ + if (chan_get_type(output) != CHAN_SINGLE) { + err("mux_init: output channel must be of type single\n"); + return -1; + } + + memset(mux, 0, sizeof(struct mux_input)); + mux->select = select; + mux->output = output; + + if (select_func == NULL) + select_func = default_select; + + mux->select_func = select_func; + mux->bay = bay; + + if (bay_add_cb(bay, select, cb_select, mux) != 0) { + err("mux_init: bay_add_cb failed\n"); + return -1; + } + + return 0; +} + +struct mux_input * +mux_find_input(struct mux *mux, struct value value) +{ + struct mux_input *input = NULL; + /* Only int64 due to garbage */ + if (value.type != VALUE_INT64) + die("bad value type\n"); + + HASH_FIND(hh, mux->input, &value.i, sizeof(value.i), input); + return input; +} + +int +mux_add_input(struct mux *mux, struct value key, struct chan *chan) +{ + if (mux_find_input(mux, key) != NULL) { + char buf[128]; + err("mux_add_input: input key %s already in mux\n", + value_str(key, buf)); + return -1; + } + + struct mux_input *input = calloc(1, sizeof(struct mux_input)); + + if (input == NULL) { + err("mux_add_input: calloc failed\n"); + return -1; + } + + input->key = key; + input->chan = chan; + + HASH_ADD_KEYPTR(hh, mux->input, &input->key.i, sizeof(input->key.i), input); + + if (bay_add_cb(mux->bay, chan, cb_input, mux) != 0) { + err("mux_add_input: bay_add_cb failed\n"); + return -1; + } + + return 0; +} diff --git a/src/emu/mux.h b/src/emu/mux.h new file mode 100644 index 0000000..2d74c6e --- /dev/null +++ b/src/emu/mux.h @@ -0,0 +1,58 @@ +#ifndef MUX_H +#define MUX_H + +#include +#include "bay.h" +#include "uthash.h" + +struct mux_input { + struct value key; + struct chan *chan; + UT_hash_handle hh; +}; + +struct mux; + +typedef int (* mux_select_func_t)(struct mux *mux, + struct value value, + struct mux_input **input); + +/* MUX logic: + * + * select input output + * --------------------------- + * null - null + * input x x + * + */ + +struct mux { + struct bay *bay; + int64_t ninputs; + struct mux_input *input; + mux_select_func_t select_func; + struct chan *select; + struct chan *output; +}; + +void mux_input_init(struct mux_input *mux, + struct value key, + struct chan *chan); + +int mux_init(struct mux *mux, + struct bay *bay, + struct chan *select, + struct chan *output, + mux_select_func_t select_func); + +struct mux_input *mux_find_input(struct mux *mux, + struct value key); + +int mux_add_input(struct mux *mux, + struct value key, + struct chan *input); + +int mux_register(struct mux *mux, + struct bay *bay); + +#endif /* MUX_H */ diff --git a/src/emu/value.h b/src/emu/value.h new file mode 100644 index 0000000..d620968 --- /dev/null +++ b/src/emu/value.h @@ -0,0 +1,77 @@ +#ifndef VALUE_H +#define VALUE_H + +#include +#include +#include +#include "common.h" + +enum value_type { + VALUE_NULL = 0, + VALUE_INT64, + VALUE_DOUBLE +}; + +struct value { + enum value_type type; + union { + int64_t i; + double d; + }; +}; + +static inline int +value_is_equal(struct value *a, struct value *b) +{ + if (a->type != b->type) + return 0; + + if (a->type == VALUE_INT64 && a->i == b->i) + return 1; + else if (a->type == VALUE_DOUBLE && a->d == b->d) + return 1; + else + return 0; +} + +static inline struct value +value_int64(int64_t i) +{ + struct value v = { .type = VALUE_INT64, .i = i }; + return v; +} + +static inline struct value +value_null(void) +{ + struct value v = { .type = VALUE_NULL }; + return v; +} + +static inline int +value_is_null(struct value a) +{ + return (a.type == VALUE_NULL); +} + +static inline char * +value_str(struct value a, char *buf) +{ + switch (a.type) { + case VALUE_NULL: + sprintf(buf, "{NULL}"); + break; + case VALUE_INT64: + sprintf(buf, "{int64_t %ld}", a.i); + break; + case VALUE_DOUBLE: + sprintf(buf, "{double %e}", a.d); + break; + default: + die("value_str: unexpected value type\n"); + } + + return buf; +} + +#endif /* VALUE_H */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b5e12c2..0a8995e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,5 +7,5 @@ set(OVNI_TEST_BUILD_DIR "${CMAKE_BINARY_DIR}/test") include(macros.cmake) add_subdirectory(unit) -add_subdirectory(emu) -add_subdirectory(rt) +#add_subdirectory(emu) +#add_subdirectory(rt) diff --git a/test/macros.cmake b/test/macros.cmake index e64090d..ccb6fd5 100644 --- a/test/macros.cmake +++ b/test/macros.cmake @@ -35,7 +35,7 @@ function(unit_test source) "${CMAKE_SOURCE_DIR}/include" ) add_executable("${OVNI_TEST_NAME}" "${OVNI_TEST_SOURCE}") - target_link_libraries("${OVNI_TEST_NAME}" PRIVATE ovni emu) + target_link_libraries("${OVNI_TEST_NAME}" PRIVATE ovni chan) add_test(NAME "${OVNI_TEST_NAME}" COMMAND "${OVNI_TEST_NAME}" diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index b6575cc..3bedb95 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,4 +1,13 @@ # Copyright (c) 2022 Barcelona Supercomputing Center (BSC) # SPDX-License-Identifier: GPL-3.0-or-later -unit_test(version.c) +#unit_test(version.c) +#unit_test(task.c) +#unit_test(taskstack.c) +#unit_test(taskstack-bad.c) +#unit_test(taskmodel.c) +#unit_test(tbm.c) +#unit_test(tbm_trace.c) +unit_test(chan.c) +unit_test(bay.c) +unit_test(mux.c) diff --git a/test/unit/bay.c b/test/unit/bay.c new file mode 100644 index 0000000..4e4b674 --- /dev/null +++ b/test/unit/bay.c @@ -0,0 +1,51 @@ +#include "emu/bay.h" +#include "common.h" + +static int +callback(struct chan *chan, void *ptr) +{ + struct value value; + if (chan_read(chan, &value) != 0) + die("callback: chan_read failed\n"); + + if (value.type != VALUE_INT64) + die("callback: unexpected value type\n"); + + int64_t *ival = ptr; + *ival = value.i; + + return 0; +} + +int main(void) +{ + struct bay bay; + bay_init(&bay); + + struct chan chan; + chan_init(&chan, CHAN_SINGLE, "testchan"); + + bay_register(&bay, &chan); + + int64_t data = 0; + bay_add_cb(&bay, &chan, callback, &data); + + if (data != 0) + die("data changed after bay_chan_append_cb\n"); + + struct value one = value_int64(1); + if (chan_set(&chan, one) != 0) + die("chan_set failed\n"); + + if (data != 0) + die("data changed after chan_set\n"); + + /* Now the callback should modify 'data' */ + if (bay_propagate(&bay) != 0) + die("bay_propagate failed\n"); + + if (data != 1) + die("data didn't change after bay_propagate\n"); + + return 0; +} diff --git a/test/unit/chan.c b/test/unit/chan.c new file mode 100644 index 0000000..3f8bf00 --- /dev/null +++ b/test/unit/chan.c @@ -0,0 +1,97 @@ +#include "emu/chan.h" +#include "common.h" + +static void +check_single(void) +{ + struct chan chan; + struct value one = { .type = VALUE_INT64, .i = 1 }; + struct value two = { .type = VALUE_INT64, .i = 2 }; + //struct value nil = { .type = VALUE_NULL, .i = 0 }; + + chan_init(&chan, CHAN_SINGLE, "testchan"); + + /* Ensure we cannot push as stack */ + if (chan_push(&chan, one) == 0) + die("chan_push didn't fail\n"); + + /* Now we should be able to write with set */ + if (chan_set(&chan, one) != 0) + die("chan_set failed\n"); + + /* Now is dirty, it shouldn't allow another set */ + if (chan_set(&chan, two) == 0) + die("chan_set didn't fail\n"); + + struct value value; + + if (chan_read(&chan, &value) != 0) + die("chan_read failed\n"); + + if (!value_is_equal(&value, &one)) + die("chan_read returned unexpected value\n"); +} + +//static void +//check_stack(void) +//{ +// struct chan chan; +// +// chan_init(&chan, CHAN_STACK); +// +// /* Ensure we cannot set as single */ +// if (chan_set(&chan, 1) == 0) +// die("chan_set didn't fail\n"); +// +// /* Channels are closed after init */ +// if (chan_push(&chan, 1) != 0) +// die("chan_push failed\n"); +// +// /* Now is closed, it shouldn't allow another value */ +// if (chan_push(&chan, 2) == 0) +// die("chan_push didn't fail\n"); +// +// struct chan_value value = { 0 }; +// +// if (chan_flush(&chan, &value) != 0) +// die("chan_flush failed\n"); +// +// if (!value.ok || value.i != 1) +// die("chan_flush returned unexpected value\n"); +// +// /* Now it should allow to push another value */ +// if (chan_push(&chan, 2) != 0) +// die("chan_push failed\n"); +// +// if (chan_flush(&chan, &value) != 0) +// die("chan_flush failed\n"); +// +// if (!value.ok || value.i != 2) +// die("chan_flush returned unexpected value\n"); +// +// /* Now pop the values */ +// if (chan_pop(&chan, 2) != 0) +// die("chan_pop failed\n"); +// +// if (chan_flush(&chan, &value) != 0) +// die("chan_flush failed\n"); +// +// if (!value.ok || value.i != 1) +// die("chan_flush returned unexpected value\n"); +// +// if (chan_pop(&chan, 1) != 0) +// die("chan_pop failed\n"); +// +// /* Now the stack should be empty */ +// +// if (chan_pop(&chan, 1) == 0) +// die("chan_pop didn't fail\n"); +// +//} + +int main(void) +{ + check_single(); + //check_stack(); + return 0; +} diff --git a/test/unit/mux.c b/test/unit/mux.c new file mode 100644 index 0000000..af1bc20 --- /dev/null +++ b/test/unit/mux.c @@ -0,0 +1,109 @@ +#include "emu/mux.h" +#include "common.h" + +#define N 4 + +//static int +//select_active_thread(struct mux *mux, +// struct value *value, +// struct mux_input **input) +//{ +// if (value->type == VALUE_NULL) { +// *input = NULL; +// return 0; +// } +// +// if (value->type != VALUE_INT64) { +// err("expecting NULL or INT64 channel value\n"); +// return -1; +// } +// +// enum thread_state state = (enum thread_state) value->i; +// +// if (mux->ninputs != 1) { +// err("expecting NULL or INT64 channel value\n"); +// return -1; +// } +// +// switch (state) { +// case TH_ST_RUNNING: +// case TH_ST_COOLING: +// case TH_ST_WARMING: +// *input = only_input; +// break; +// case TH_ST_PAUSED: +// *input = NULL; +// break; +// } +// +// return 0; +//} + +int +main(void) +{ + struct bay bay; + bay_init(&bay); + + struct chan inputs[N]; + struct chan output; + struct chan select; + + chan_init(&output, CHAN_SINGLE, "output"); + chan_init(&select, CHAN_SINGLE, "select"); + + for (int i = 0; i < N; i++) { + char buf[MAX_CHAN_NAME]; + sprintf(buf, "input.%d", i); + chan_init(&inputs[i], CHAN_SINGLE, buf); + } + + /* Register all channels in the bay */ + bay_register(&bay, &output); + bay_register(&bay, &select); + for (int i = 0; i < N; i++) { + bay_register(&bay, &inputs[i]); + } + + struct mux mux; + mux_init(&mux, &bay, &select, &output, NULL); + + for (int i = 0; i < N; i++) + mux_add_input(&mux, value_int64(i), &inputs[i]); + + /* Write something to the input channels */ + for (int i = 0; i < N; i++) { + if (chan_set(&inputs[i], value_int64(1000 + i)) != 0) + die("chan_set failed\n"); + } + + /* Propagate values and call the callbacks */ + if (bay_propagate(&bay) != 0) + die("bay_propagate failed\n"); + + + /* Change select channel */ + if (chan_set(&select, value_int64(2)) != 0) + die("chan_set failed\n"); + + /* Propagate values and call the callbacks */ + if (bay_propagate(&bay) != 0) + die("bay_propagate failed\n"); + + struct value out_value = value_null(); + if (chan_read(&output, &out_value) != 0) + die("chan_read() failed for output channel\n"); + + struct value expected_value = value_int64(1002); + if (!value_is_equal(&out_value, &expected_value)) { + char buf1[128]; + char buf2[128]; + die("unexpected value found %s in output (expected %s)\n", + value_str(out_value, buf1), + value_str(expected_value, buf2)); + } + + err("OK\n"); + + return 0; +}