diff --git a/src/emu/bay.c b/src/emu/bay.c index 1fd5957..a8f968b 100644 --- a/src/emu/bay.c +++ b/src/emu/bay.c @@ -106,7 +106,7 @@ bay_init(struct bay *bay) static int propagate_chan(struct bay_chan *bchan) { - dbg("propagating dirty channel %p\n", (void *) bchan); + dbg("- propagating dirty channel %s\n", bchan->chan->name); struct bay_cb *cur = NULL; struct bay_cb *tmp = NULL; @@ -123,7 +123,7 @@ propagate_chan(struct bay_chan *bchan) int bay_propagate(struct bay *bay) { - dbg("propagating channels\n"); + dbg("-- propagating channels begins\n"); struct bay_chan *cur = NULL; struct bay_chan *tmp = NULL; DL_FOREACH_SAFE(bay->dirty, cur, tmp) { @@ -134,8 +134,20 @@ bay_propagate(struct bay *bay) } } + /* Flush channels after running all the dirty callbacks, so we + * capture any potential double write when running the + * callbacks */ + DL_FOREACH_SAFE(bay->dirty, cur, tmp) { + if (chan_flush(cur->chan) != 0) { + err("bay_propagate: chan_flush failed\n"); + return -1; + } + } + bay->dirty = NULL; + dbg("-- propagating channels ends\n"); + return 0; } diff --git a/src/emu/chan.c b/src/emu/chan.c index 63138c3..638aaf4 100644 --- a/src/emu/chan.c +++ b/src/emu/chan.c @@ -36,6 +36,10 @@ static int set_dirty(struct chan *chan) { if (chan->is_dirty) { + /* Already dirty and allowed, no need to do anything */ + if (chan->allow_dirty_write) + return 0; + err("channel %s already dirty\n", chan->name); return -1; } @@ -44,7 +48,8 @@ set_dirty(struct chan *chan) if (chan->dirty_cb != NULL) { if (chan->dirty_cb(chan, chan->dirty_arg) != 0) { - err("dirty callback failed\n"); + err("set_dirty %s: dirty callback failed\n", + chan->name); return -1; } } @@ -60,7 +65,8 @@ check_duplicates(struct chan *chan, struct value *v) // return 0; if (value_is_equal(&chan->last_value, v)) { - err("check_duplicates: same value as last_value\n"); + err("check_duplicates %s: same value as last_value\n", + chan->name); return -1; } @@ -71,27 +77,30 @@ int chan_set(struct chan *chan, struct value value) { if (chan->type != CHAN_SINGLE) { - err("chan_set: cannot set on non-single channel\n"); + err("chan_set %s: cannot set on non-single channel\n", + chan->name); return -1; } - if (chan->is_dirty) { - err("chan_set: cannot modify dirty channel\n"); + if (chan->is_dirty && !chan->allow_dirty_write) { + err("chan_set %s: cannot modify dirty channel\n", + chan->name); return -1; } if (check_duplicates(chan, &value) != 0) { - err("chan_set: cannot set a duplicated value\n"); + err("chan_set %s: cannot set a duplicated value\n", + chan->name); return -1; } char buf[128]; - dbg("chan_set: channel %p sets value %s\n", (void *) chan, - value_str(value, buf)); + dbg("chan_set %s: sets value to %s\n", + chan->name, value_str(value, buf)); chan->data.value = value; if (set_dirty(chan) != 0) { - err("chan_set: set_dirty failed\n"); + err("chan_set %s: set_dirty failed\n", chan->name); return -1; } @@ -108,31 +117,34 @@ int chan_push(struct chan *chan, struct value value) { if (chan->type != CHAN_STACK) { - err("chan_push: cannot push on non-stack channel\n"); + err("chan_push %s: cannot push on non-stack channel\n", + chan->name); return -1; } - if (chan->is_dirty) { - err("chan_push: cannot modify dirty channel\n"); + if (chan->is_dirty && !chan->allow_dirty_write) { + err("chan_push %s: cannot modify dirty channel\n", + chan->name); return -1; } if (check_duplicates(chan, &value) != 0) { - err("chan_push: cannot push a duplicated value\n"); + err("chan_push %s: cannot push a duplicated value\n", + chan->name); return -1; } struct chan_stack *stack = &chan->data.stack; if (stack->n >= MAX_CHAN_STACK) { - err("chan_push: channel stack full\n"); - abort(); + err("chan_push %s: channel stack full\n", chan->name); + return -1; } stack->values[stack->n++] = value; if (set_dirty(chan) != 0) { - err("chan_set: set_dirty failed\n"); + err("chan_push %s: set_dirty failed\n", chan->name); return -1; } @@ -150,34 +162,36 @@ int chan_pop(struct chan *chan, struct value evalue) { if (chan->type != CHAN_STACK) { - err("chan_pop: cannot pop on non-stack channel\n"); + err("chan_pop %s: cannot pop on non-stack channel\n", + chan->name); return -1; } - if (chan->is_dirty) { - err("chan_pop: cannot modify dirty channel\n"); + if (chan->is_dirty && !chan->allow_dirty_write) { + err("chan_pop %s: cannot modify dirty channel\n", + chan->name); return -1; } struct chan_stack *stack = &chan->data.stack; if (stack->n <= 0) { - err("chan_pop: channel stack empty\n"); + err("chan_pop %s: channel stack empty\n", chan->name); return -1; } 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); + err("chan_pop %s: unexpected value %ld (expected %ld)\n", + chan->name, value->i, evalue.i); return -1; } stack->n--; if (set_dirty(chan) != 0) { - err("chan_set: set_dirty failed\n"); + err("chan_pop %s: set_dirty failed\n", chan->name); return -1; } @@ -194,7 +208,7 @@ get_value(struct chan *chan, struct value *value) struct chan_stack *stack = &chan->data.stack; if (stack->n <= 0) { - err("get_value: channel stack empty\n"); + err("get_value %s: channel stack empty\n", chan->name); return -1; } @@ -208,9 +222,29 @@ int chan_read(struct chan *chan, struct value *value) { if (get_value(chan, value) != 0) { - err("chan_read: get_value failed\n"); + err("chan_read %s: get_value failed\n", chan->name); return -1; } return 0; } + +/** Remove the dirty state */ +int +chan_flush(struct chan *chan) +{ + if (!chan->is_dirty) { + err("chan_flush %s: channel is not dirty\n", chan->name); + return -1; + } + + chan->is_dirty = 0; + + return 0; +} + +void +chan_dirty_write(struct chan *chan, int allow) +{ + chan->allow_dirty_write = allow; +} diff --git a/src/emu/chan.h b/src/emu/chan.h index 9659c2f..47bd839 100644 --- a/src/emu/chan.h +++ b/src/emu/chan.h @@ -35,6 +35,7 @@ struct chan { enum chan_type type; union chan_data data; int is_dirty; + int allow_dirty_write; struct value err_value; struct value last_value; chan_cb_t dirty_cb; @@ -51,6 +52,8 @@ 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_flush(struct chan *chan); +void chan_dirty_write(struct chan *chan, int allow); /* Called when it becomes dirty */ void chan_set_dirty_cb(struct chan *chan, chan_cb_t func, void *arg); diff --git a/src/emu/mux.c b/src/emu/mux.c index 15c990c..0c46cb6 100644 --- a/src/emu/mux.c +++ b/src/emu/mux.c @@ -64,7 +64,8 @@ cb_select(struct chan *sel_chan, void *ptr) return -1; } - dbg("selecting mux input %p\n", (void *) input); + dbg("mux selects input key=%s chan=%s\n", + value_str(sel_value, buf), input->chan->name); /* Set to null by default */ struct value out_value = value_null(); @@ -102,8 +103,11 @@ cb_input(struct chan *in_chan, void *ptr) } /* Nothing to do, the input is not selected */ - if (input == NULL || input->chan != in_chan) + if (input == NULL || input->chan != in_chan) { + dbg("mux: input channel %s changed but not selected\n", + in_chan->name); return 0; + } dbg("selected mux input %s changed\n", in_chan->name); @@ -135,6 +139,11 @@ mux_init(struct mux *mux, return -1; } + /* The output channel must accept multiple writes in the same + * propagation phase, as we may write to the input and select + * channel at the same time. */ + chan_dirty_write(output, 1); + memset(mux, 0, sizeof(struct mux_input)); mux->select = select; mux->output = output; diff --git a/test/unit/chan.c b/test/unit/chan.c index 3f8bf00..6e17f82 100644 --- a/test/unit/chan.c +++ b/test/unit/chan.c @@ -2,7 +2,46 @@ #include "common.h" static void -check_single(void) +test_dirty(void) +{ + struct chan chan; + chan_init(&chan, CHAN_SINGLE, "testchan"); + + if (chan_set(&chan, value_int64(1)) != 0) + die("chan_set failed\n"); + + /* Now channel is dirty */ + + if (chan_set(&chan, value_int64(2)) == 0) + die("chan_set didn't fail\n"); + + if (chan_flush(&chan) != 0) + die("chan_flush failed\n"); + + chan_dirty_write(&chan, 1); + + if (chan_set(&chan, value_int64(3)) != 0) + die("chan_set failed\n"); + + /* Now is dirty, but it should allow multiple writes */ + if (chan_set(&chan, value_int64(4)) != 0) + die("chan_set failed\n"); + + struct value value; + struct value four = value_int64(4); + + if (chan_read(&chan, &value) != 0) + die("chan_read failed\n"); + + if (!value_is_equal(&value, &four)) + die("chan_read returned unexpected value\n"); + + if (chan_flush(&chan) != 0) + die("chan_flush failed\n"); +} + +static void +test_single(void) { struct chan chan; struct value one = { .type = VALUE_INT64, .i = 1 }; @@ -33,7 +72,7 @@ check_single(void) } //static void -//check_stack(void) +//test_stack(void) //{ // struct chan chan; // @@ -91,7 +130,8 @@ check_single(void) int main(void) { - check_single(); - //check_stack(); + test_single(); + test_dirty(); + //test_stack(); return 0; } diff --git a/test/unit/mux.c b/test/unit/mux.c index af1bc20..862b995 100644 --- a/test/unit/mux.c +++ b/test/unit/mux.c @@ -1,7 +1,7 @@ #include "emu/mux.h" #include "common.h" -#define N 4 +#define N 6 //static int //select_active_thread(struct mux *mux, @@ -39,6 +39,132 @@ // return 0; //} +static void +check_output(struct mux *mux, struct value expected) +{ + struct value out_value = value_null(); + if (chan_read(mux->output, &out_value) != 0) + die("chan_read() failed for output channel\n"); + + if (!value_is_equal(&out_value, &expected)) { + char buf1[128]; + char buf2[128]; + die("unexpected value found %s in output (expected %s)\n", + value_str(out_value, buf1), + value_str(expected, buf2)); + } + + err("----- output ok -----\n"); +} + +static void +test_select(struct mux *mux, int key) +{ + if (chan_set(mux->select, value_int64(key)) != 0) + die("chan_set failed\n"); + + if (bay_propagate(mux->bay) != 0) + die("bay_propagate failed\n"); + + check_output(mux, value_int64(1000 + key)); +} + +static void +test_input(struct mux *mux, int key) +{ + /* Set the select channel to the selected key */ + test_select(mux, key); + + int new_value = 2000 + key; + + /* Then change that channel */ + struct mux_input *mi = mux_find_input(mux, value_int64(key)); + if (mi == NULL) + die("mux_find_input failed to locate input %d\n", key); + + if (chan_set(mi->chan, value_int64(new_value)) != 0) + die("chan_set failed\n"); + + if (bay_propagate(mux->bay) != 0) + die("bay_propagate failed\n"); + + check_output(mux, value_int64(new_value)); +} + +static void +test_select_and_input(struct mux *mux, int key) +{ + /* Set the select channel to the selected key, but don't + * propagate the changes yet */ + if (chan_set(mux->select, value_int64(key)) != 0) + die("chan_set failed\n"); + + int new_value = 2000 + key; + + /* Also change that channel */ + struct mux_input *mi = mux_find_input(mux, value_int64(key)); + if (mi == NULL) + die("mux_find_input failed to locate input %d\n", key); + + if (chan_set(mi->chan, value_int64(new_value)) != 0) + die("chan_set failed\n"); + + /* Write twice to the output */ + if (bay_propagate(mux->bay) != 0) + die("bay_propagate failed\n"); + + check_output(mux, value_int64(new_value)); +} + +static void +test_input_and_select(struct mux *mux, int key) +{ + int new_value = 2000 + key; + + /* First change the input */ + struct mux_input *mi = mux_find_input(mux, value_int64(key)); + if (mi == NULL) + die("mux_find_input failed to locate input %d\n", key); + + if (chan_set(mi->chan, value_int64(new_value)) != 0) + die("chan_set failed\n"); + + /* Then change select */ + if (chan_set(mux->select, value_int64(key)) != 0) + die("chan_set failed\n"); + + /* Write twice to the output */ + if (bay_propagate(mux->bay) != 0) + die("bay_propagate failed\n"); + + check_output(mux, value_int64(new_value)); +} + +static void +test_mid_propagate(struct mux *mux, int key) +{ + int new_value = 2000 + key; + + struct mux_input *mi = mux_find_input(mux, value_int64(key)); + if (mi == NULL) + die("mux_find_input failed to locate input %d\n", key); + + if (chan_set(mi->chan, value_int64(new_value)) != 0) + die("chan_set failed\n"); + + if (bay_propagate(mux->bay) != 0) + die("bay_propagate failed\n"); + + if (chan_set(mux->select, value_int64(key)) != 0) + die("chan_set failed\n"); + + if (bay_propagate(mux->bay) != 0) + die("bay_propagate failed\n"); + + check_output(mux, value_int64(new_value)); +} + + int main(void) { @@ -81,27 +207,11 @@ main(void) 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)); - } + test_select(&mux, 1); + test_input(&mux, 2); + test_select_and_input(&mux, 3); + test_input_and_select(&mux, 4); + test_mid_propagate(&mux, 5); err("OK\n");