diff --git a/pkg/urbit/include/noun/retrieve.h b/pkg/urbit/include/noun/retrieve.h index eed2bcc91..64b5ed67f 100644 --- a/pkg/urbit/include/noun/retrieve.h +++ b/pkg/urbit/include/noun/retrieve.h @@ -81,9 +81,9 @@ u3_noun c, u3_noun d); - /* u3r_mug(): MurmurHash3 on a noun. + /* u3r_mug(): statefully mug a noun with 31-bit murmur3. */ - c3_w + c3_l u3r_mug(u3_noun veb); /* u3r_fing(): diff --git a/pkg/urbit/noun/allocate.c b/pkg/urbit/noun/allocate.c index a57f253f2..345de96b3 100644 --- a/pkg/urbit/noun/allocate.c +++ b/pkg/urbit/noun/allocate.c @@ -1048,64 +1048,274 @@ _me_gain_use(u3_noun dog) } } -#define TAKE_ROOT 0 -#define TAKE_HEAD 1 -#define TAKE_TAIL 2 - #undef VERBOSE_TAKE -// stack frame for recording head vs tail iteration -// -// In Hoon, this structure would be as follows: -// -// $% [%root ~] -// [%head old=* new=*] -// [%tail old=* new=*] -// == -// -typedef struct takeframe +/* _ca_take_atom(): reallocate an indirect atom off the stack. +*/ +static inline u3_atom +_ca_take_atom(u3a_atom* old_u) { - c3_y tag_y; - u3a_cell* old_u; - u3a_cell* new_u; -} takeframe; + c3_w* new_w = u3a_walloc(old_u->len_w + c3_wiseof(u3a_atom)); + u3a_atom* new_u = (u3a_atom*)(void *)new_w; + u3_noun new = u3a_to_pug(u3a_outa(new_u)); -static inline void -_ca_take_push(c3_ys mov, - c3_ys off, - c3_y tag_y, - u3a_cell* old_u, - u3a_cell* new_u) -{ - u3R->cap_p += mov; +#ifdef VERBOSE_TAKE + u3l_log("%s: atom %p to %p\r\n", ( c3y == u3a_is_north(u3R) ) + ? "north" + : "south", + old_u, + new_u); +#endif - // ensure we haven't overflowed the stack - // (off==0 means we're on a north road) + // XX use memcpy? // - if ( 0 == off ) { - if( !(u3R->cap_p > u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - else { - if( !(u3R->cap_p < u3R->hat_p) ) { - u3m_bail(c3__meme); + new_u->mug_w = old_u->mug_w; + new_u->len_w = old_u->len_w; + { + c3_w i_w; + + for ( i_w=0; i_w < old_u->len_w; i_w++ ) { + new_u->buf_w[i_w] = old_u->buf_w[i_w]; } } - takeframe* fam_u = u3to(takeframe, u3R->cap_p + off); - fam_u->tag_y = tag_y; - fam_u->old_u = old_u; - fam_u->new_u = new_u; + // borrow mug slot to record new destination in [old_u] + // + old_u->mug_w = new; + + return new; } -static inline takeframe -_ca_take_pop(c3_ys mov, c3_ys off) +/* _ca_take_cell(): reallocate a cell off the stack. +*/ +static inline u3_cell +_ca_take_cell(u3a_cell* old_u, u3_noun hed, u3_noun tel) { - takeframe* fam_u = u3to(takeframe, u3R->cap_p + off); - u3R->cap_p -= mov; + // XX use u3a_celloc? + // + c3_w* new_w = u3a_walloc(c3_wiseof(u3a_cell)); + u3a_cell* new_u = (u3a_cell*)(void *)new_w; + u3_cell new = u3a_to_pom(u3a_outa(new_u)); - return *fam_u; +#ifdef VERBOSE_TAKE + u3l_log("%s: cell %p to %p\r\n", ( c3y == u3a_is_north(u3R) ) + ? "north" + : "south", + old_u, + new_u); +#endif + + new_u->mug_w = old_u->mug_w; + new_u->hed = hed; + new_u->tel = tel; + + // borrow mug slot to record new destination in [old_u] + // + old_u->mug_w = new; + + return new; +} + +/* _ca_take: stack frame for recording cell travesal +** (u3_none == hed) == head-frame +*/ +typedef struct _ca_take +{ + u3_weak hed; // taken head + u3_cell old; // old cell +} _ca_take; + +/* _ca_take_next_south: take next noun, pushing cells on stack. +*/ +static inline u3_noun +_ca_take_next_north(u3a_pile* pil_u, u3_noun veb) +{ + while ( 1 ) { + // direct atoms and senior refs are not counted. + // + if ( (c3y == u3a_is_cat(veb)) + || (c3y == u3a_north_is_senior(u3R, veb)) ) + { + return veb; + } + // not junior; normal (heap) refs on our road are counted. + // + else if ( c3n == u3a_north_is_junior(u3R, veb) ) { + _me_gain_use(veb); // bypass branches in u3k() + return veb; + } + // junior (stack) refs are copied. + // + else { + u3a_noun* veb_u = u3a_to_ptr(veb); + + // 32-bit mug_w: already copied [veb] and [mug_w] is the new ref. + // + if ( veb_u->mug_w >> 31 ) { + u3_noun nov = (u3_noun)veb_u->mug_w; + + c3_assert( c3y == u3a_north_is_normal(u3R, nov) ); + +#ifdef VERBOSE_TAKE + u3l_log("north: %p is already %p\r\n", veb_u, u3a_to_ptr(nov)); +#endif + + _me_gain_use(nov); // bypass branches in u3k() + return nov; + } + else if ( c3y == u3a_is_atom(veb) ) { + return _ca_take_atom((u3a_atom*)veb_u); + } + else { + u3a_cell* old_u = (u3a_cell*)veb_u; + _ca_take* fam_u = u3a_push(pil_u); + u3a_pile_sane(pil_u); + + fam_u->hed = u3_none; + fam_u->old = veb; + + veb = old_u->hed; + continue; + } + } + } +} + +/* _ca_take_next_south: take next noun, pushing cells on stack. +*/ +static inline u3_noun +_ca_take_next_south(u3a_pile* pil_u, u3_noun veb) +{ + while ( 1 ) { + // direct atoms and senior refs are not counted. + // + if ( (c3y == u3a_is_cat(veb)) + || (c3y == u3a_south_is_senior(u3R, veb)) ) + { + return veb; + } + // not junior; a normal pointer in our road -- refcounted + // + else if ( c3n == u3a_south_is_junior(u3R, veb) ) { + _me_gain_use(veb); // bypass branches in u3k() + return veb; + } + // junior (stack) refs are copied. + // + else { + u3a_noun* veb_u = u3a_to_ptr(veb); + + // 32-bit mug_w: already copied [veb] and [mug_w] is the new ref. + // + if ( veb_u->mug_w >> 31 ) { + u3_noun nov = (u3_noun)veb_u->mug_w; + + c3_assert( c3y == u3a_south_is_normal(u3R, nov) ); + +#ifdef VERBOSE_TAKE + u3l_log("south: %p is already %p\r\n", veb_u, u3a_to_ptr(nov)); +#endif + + _me_gain_use(nov); // bypass branches in u3k() + return nov; + } + else if ( c3y == u3a_is_atom(veb) ) { + return _ca_take_atom((u3a_atom*)veb_u); + } + else { + u3a_cell* old_u = (u3a_cell*)veb_u; + _ca_take* fam_u = u3a_push(pil_u); + u3a_pile_sane(pil_u); + + fam_u->hed = u3_none; + fam_u->old = veb; + + veb = old_u->hed; + continue; + } + } + } +} + +/* _ca_take_north(): in a north road, gain, copying juniors (from stack). +*/ +static u3_noun +_ca_take_north(u3_noun veb) +{ + u3_noun pro; + _ca_take* fam_u; + u3a_pile pil_u; + u3a_pile_prep(&pil_u, sizeof(*fam_u)); + + // commence taking + // + pro = _ca_take_next_north(&pil_u, veb); + + // process cell results + // + if ( c3n == u3a_pile_done(&pil_u) ) { + fam_u = u3a_peek(&pil_u); + + do { + // head-frame: stash copy and continue into the tail + // + if ( u3_none == fam_u->hed ) { + u3a_cell* old_u = u3a_to_ptr(fam_u->old); + fam_u->hed = pro; + pro = _ca_take_next_north(&pil_u, old_u->tel); + fam_u = u3a_peek(&pil_u); + } + // tail-frame: copy cell and pop the stack + // + else { + u3a_cell* old_u = u3a_to_ptr(fam_u->old); + pro = _ca_take_cell(old_u, fam_u->hed, pro); + fam_u = u3a_pop(&pil_u); + } + } while ( c3n == u3a_pile_done(&pil_u) ); + } + + return pro; +} +/* _ca_take_south(): in a south road, gain, copying juniors (from stack). +*/ +static u3_noun +_ca_take_south(u3_noun veb) +{ + u3_noun pro; + _ca_take* fam_u; + u3a_pile pil_u; + u3a_pile_prep(&pil_u, sizeof(*fam_u)); + + // commence taking + // + pro = _ca_take_next_south(&pil_u, veb); + + // process cell results + // + if ( c3n == u3a_pile_done(&pil_u) ) { + fam_u = u3a_peek(&pil_u); + + do { + // head-frame: stash copy and continue into the tail + // + if ( u3_none == fam_u->hed ) { + u3a_cell* old_u = u3a_to_ptr(fam_u->old); + fam_u->hed = pro; + pro = _ca_take_next_south(&pil_u, old_u->tel); + fam_u = u3a_peek(&pil_u); + } + // tail-frame: copy cell and pop the stack + // + else { + u3a_cell* old_u = u3a_to_ptr(fam_u->old); + pro = _ca_take_cell(old_u, fam_u->hed, pro); + fam_u = u3a_pop(&pil_u); + } + } while ( c3n == u3a_pile_done(&pil_u) ); + } + + return pro; } /* u3a_take(): gain, copying juniors. @@ -1113,196 +1323,16 @@ _ca_take_pop(c3_ys mov, c3_ys off) u3_noun u3a_take(u3_noun veb) { - c3_assert(u3_none != veb); - + u3_noun pro; u3t_on(coy_o); - // initialize signed stack offsets (relative to north/south road) - // - c3_o nor_o = u3a_is_north(u3R); - c3_ys mov, off; - { - c3_y wis_y = c3_wiseof(takeframe); - mov = ( c3y == nor_o ? -wis_y : wis_y ); - off = ( c3y == nor_o ? 0 : -wis_y ); - } + c3_assert(u3_none != veb); - // stash the current stack post - // - u3p(takeframe) cap_p = u3R->cap_p; - - // push the (only) ROOT stack frame (our termination condition) - // - _ca_take_push(mov, off, TAKE_ROOT, 0, 0); - - // the finished copy of our current noun .veb - // - u3_noun pro; - - // read from the current noun .veb - // - advance: { - if ( c3y == u3a_is_cat(veb) ) { - pro = veb; - goto retreat; - } - // senior pointers are not refcounted - // - else if ( c3y == (( c3y == nor_o ) - ? u3a_north_is_senior(u3R, veb) - : u3a_south_is_senior(u3R, veb)) ) - { - pro = veb; - goto retreat; - } - // not junior; a normal pointer in our road -- refcounted - // - else if ( c3n == (( c3y == nor_o ) - ? u3a_north_is_junior(u3R, veb) - : u3a_south_is_junior(u3R, veb)) ) - { - // bypass normal road checks in u3k - // - _me_gain_use(veb); - pro = veb; - goto retreat; - - - } - // junior pointers are copied - // - else { - u3a_noun* veb_u = u3a_to_ptr(veb); - - // 32-bit mug_w: already copied .veb and .mug_w is the pointer - // - if ( veb_u->mug_w >> 31 ) { - u3_noun nov = (u3_noun)veb_u->mug_w; - - c3_assert( c3y == (( c3y == nor_o) - ? u3a_north_is_normal(u3R, nov) - : u3a_south_is_normal(u3R, nov)) ); - -#ifdef VERBOSE_TAKE - u3l_log("%s: %p is already %p\r\n", ( c3y == nor_o ) - ? "north" - : "south", - veb_u, - u3a_to_ptr(nov)); -#endif - - // bypass normal road checks in u3k - // - _me_gain_use(nov); - pro = nov; - goto retreat; - } - else { - if ( c3y == u3a_is_atom(veb) ) { - u3a_atom* old_u = u3a_to_ptr(veb); - c3_w* new_w = u3a_walloc(old_u->len_w + c3_wiseof(u3a_atom)); - u3a_atom* new_u = (u3a_atom*)(void *)new_w; - u3_noun new = u3a_to_pug(u3a_outa(new_u)); - -#ifdef VERBOSE_TAKE - u3l_log("%s: atom %p to %p\r\n", ( c3y == nor_o ) - ? "north" - : "south", - old_u, - new_u); -#endif - - new_u->mug_w = old_u->mug_w; - new_u->len_w = old_u->len_w; - { - c3_w i_w; - - for ( i_w=0; i_w < old_u->len_w; i_w++ ) { - new_u->buf_w[i_w] = old_u->buf_w[i_w]; - } - } - - // Borrow mug slot to record new destination in .old_u. - // - old_u->mug_w = new; - - pro = new; - goto retreat; - } - else { - u3a_cell* old_u = u3a_to_ptr(veb); - // XX use u3a_celloc? - // - c3_w* new_w = u3a_walloc(c3_wiseof(u3a_cell)); - u3a_cell* new_u = (u3a_cell*)(void *)new_w; - -#ifdef VERBOSE_TAKE - u3l_log("%s: cell %p to %p\r\n", ( c3y == nor_o ) - ? "north" - : "south", - old_u, - new_u); -#endif - - new_u->mug_w = old_u->mug_w; - - veb = old_u->hed; - _ca_take_push(mov, off, TAKE_HEAD, old_u, new_u); - goto advance; - } - } - } - } - - // consume: popped stack frame, and .pro from above - // - retreat: { - takeframe fam_u = _ca_take_pop(mov, off); - - switch ( fam_u.tag_y ) { - default: { - c3_assert(0); - } - - // .fam_u is our stack root, we're done. - // - case TAKE_ROOT: { - break; - } - - // .pro is the copied head of a cell; save a pointer to it in .new_u - // and advance to copy the tail - // - case TAKE_HEAD: { - fam_u.new_u->hed = pro; - - veb = fam_u.old_u->tel; - _ca_take_push(mov, off, TAKE_TAIL, fam_u.old_u, fam_u.new_u); - goto advance; - } - - // .pro is the copied tail of a cell; save a pointer to it in .new_u, - // and produce the whole copied cell (as if it were a read from above). - // - case TAKE_TAIL: { - fam_u.new_u->tel = pro; - pro = u3a_to_pom(u3a_outa(fam_u.new_u)); - - // Borrow mug slot to record new destination in old_u. - // - fam_u.old_u->mug_w = pro; - - goto retreat; - } - } - } - - // sanity check - // - c3_assert( u3R->cap_p == cap_p ); + pro = ( c3y == u3a_is_north(u3R) ) + ? _ca_take_north(veb) + : _ca_take_south(veb); u3t_off(coy_o); - return pro; } @@ -2572,59 +2602,33 @@ u3a_walk_fore(u3_noun a, void (*pat_f)(u3_atom, void*), c3_o (*cel_f)(u3_noun, void*)) { - // initialize signed stack offsets (relative to N or S road) - // - c3_o nor_o = u3a_is_north(u3R); - c3_ys mov_ys, off_ys; - { - c3_y wis_y = c3_wiseof(u3_noun); - mov_ys = ( c3y == nor_o ? -wis_y : wis_y ); - off_ys = ( c3y == nor_o ? 0 : -wis_y ); - } + u3_noun* top; + u3a_pile pil_u; - // set stack root, push argument + // initialize stack control; push argument // - u3_noun *top, *don; - { - don = u3to(u3_noun, u3R->cap_p + off_ys); - u3R->cap_p += mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); - *top = a; - } + u3a_pile_prep(&pil_u, sizeof(u3_noun)); + top = u3a_push(&pil_u); + *top = a; - while ( top != don ) { + while ( c3n == u3a_pile_done(&pil_u) ) { // visit an atom, then pop the stack // if ( c3y == u3a_is_atom(a) ) { pat_f(a, ptr_v); - u3R->cap_p -= mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); + top = u3a_pop(&pil_u); } // vist a cell, if c3n, pop the stack // else if ( c3n == cel_f(a, ptr_v) ) { - u3R->cap_p -= mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); - + top = u3a_pop(&pil_u); } // otherwise, push the tail and continue into the head // else { - *top = u3t(a); - u3R->cap_p += mov_ys; - - if ( c3y == nor_o ) { - if( !(u3R->cap_p > u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - else { - if( !(u3R->cap_p < u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - - top = u3to(u3_noun, u3R->cap_p + off_ys); + *top = u3t(a); + top = u3a_push(&pil_u); + u3a_pile_sane(&pil_u); *top = u3h(a); } @@ -2640,48 +2644,35 @@ u3a_walk_fore_unsafe(u3_noun a, void (*pat_f)(u3_atom, void*), c3_o (*cel_f)(u3_noun, void*)) { - // initialize signed stack offsets (relative to N or S road) - // - c3_ys mov_ys, off_ys; - { - c3_y wis_y = c3_wiseof(u3_noun); - c3_o nor_o = u3a_is_north(u3R); - mov_ys = ( c3y == nor_o ? -wis_y : wis_y ); - off_ys = ( c3y == nor_o ? 0 : -wis_y ); - } + u3_noun* top; + u3a_pile pil_u; - // set stack root, push argument + // initialize stack control; push argument // - u3_noun *top, *don; - { - don = u3to(u3_noun, u3R->cap_p + off_ys); - u3R->cap_p += mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); - *top = a; - } + u3a_pile_prep(&pil_u, sizeof(u3_noun)); + top = u3a_push(&pil_u); + *top = a; - while ( top != don ) { + while ( c3n == u3a_pile_done(&pil_u) ) { // visit an atom, then pop the stack // if ( c3y == u3a_is_atom(a) ) { pat_f(a, ptr_v); - u3R->cap_p -= mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); + top = u3a_pop(&pil_u); } // vist a cell, if c3n, pop the stack // else if ( c3n == cel_f(a, ptr_v) ) { - u3R->cap_p -= mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); - + top = u3a_pop(&pil_u); } // otherwise, push the tail and continue into the head // else { - *top = u3t(a); - u3R->cap_p += mov_ys; - top = u3to(u3_noun, u3R->cap_p + off_ys); - *top = u3h(a); + *top = u3t(a); + // NB: overflow check elided here + // + top = u3a_push(&pil_u); + *top = u3h(a); } a = *top; diff --git a/pkg/urbit/noun/retrieve.c b/pkg/urbit/noun/retrieve.c index 63589ab1e..18bf9344d 100644 --- a/pkg/urbit/noun/retrieve.c +++ b/pkg/urbit/noun/retrieve.c @@ -219,9 +219,25 @@ u3r_mean(u3_noun som, ...) return ret_o; } -#define SONG_NONE 0 -#define SONG_HEAD 1 -#define SONG_TAIL 2 +// stack frame for tracking noun comparison and unification +// +// we always compare arbitrary nouns in a none-frame. +// when we compare two cells, we change the none-frame to a head-frame +// and push a new none-frame for their heads. if the heads are equal, +// we get the cells from the head-frame and unify their head pointers. +// then, we convert the head-frame to a tail-frame and repeat with +// the tails, mutatis mutandis. +// +// in Hoon, this structure would be: +// +// $% [%none a=* b=*] +// [%head a=^ b=^] +// [%tail a=^ b=^] +// == +// +#define SING_NONE 0 +#define SING_HEAD 1 +#define SING_TAIL 2 typedef struct { c3_y sat_y; @@ -229,34 +245,43 @@ typedef struct { u3_noun b; } eqframe; +/* _cr_sing_push(): push a new stack frame, initialized as SING_NONE. +*/ static inline eqframe* -_eq_push(c3_ys mov, c3_ys off, u3_noun a, u3_noun b) +_cr_sing_push(u3a_pile* pil_u, u3_noun a, u3_noun b) { - u3R->cap_p += mov; - eqframe* cur = u3to(eqframe, u3R->cap_p + off); - cur->sat_y = SONG_NONE; - cur->a = a; - cur->b = b; - return cur; + eqframe* fam_u = u3a_push(pil_u); + fam_u->sat_y = SING_NONE; + fam_u->a = a; + fam_u->b = b; + return fam_u; } -static inline eqframe* -_eq_pop(c3_ys mov, c3_ys off) -{ - u3R->cap_p -= mov; - return u3to(eqframe, u3R->cap_p + off); -} - -/* _song_atom(): check if atom [a] is indirect and equal to noun [b] +/* _cr_sing_mug(): short-circuit comparison if mugs are present and not equal. */ static inline c3_o -_song_atom(u3_atom a, u3_noun b) +_cr_sing_mug(u3a_noun* a_u, u3a_noun* b_u) +{ + // XX add debug assertions that both mugs are 31-bit + // (ie, not u3a_take() relocation references) + // + if ( a_u->mug_w && b_u->mug_w && (a_u->mug_w != b_u->mug_w) ) { + return c3n; + } + + return c3y; +} + +/* _cr_sing_atom(): check if atom [a] is indirect and equal to noun [b] +*/ +static inline c3_o +_cr_sing_atom(u3_atom a, u3_noun b) { // [a] is an atom, not pointer-equal to noun [b]. // if they're not both indirect atoms, they can't be equal. // - if ( (c3n == u3a_is_pug(a)) || - (c3n == u3a_is_pug(b)) ) + if ( (c3n == u3a_is_pug(a)) + || (c3n == u3a_is_pug(b)) ) { return c3n; } @@ -264,23 +289,26 @@ _song_atom(u3_atom a, u3_noun b) u3a_atom* a_u = u3a_to_ptr(a); u3a_atom* b_u = u3a_to_ptr(b); - if ( (0 != a_u->mug_w) && - (0 != b_u->mug_w) && - (a_u->mug_w != b_u->mug_w) ) - { + // [a] and [b] are not equal if their mugs are present and not equal. + // + if ( c3n == _cr_sing_mug((u3a_noun*)a_u, (u3a_noun*)b_u) ) { return c3n; } else { - c3_w w_rez = a_u->len_w; - c3_w w_mox = b_u->len_w; + c3_w a_w = a_u->len_w; + c3_w b_w = b_u->len_w; - if ( w_rez != w_mox ) { + // [a] and [b] are not equal if their lengths are not equal + // + if ( a_w != b_w ) { return c3n; } else { c3_w i_w; - for ( i_w = 0; i_w < w_rez; i_w++ ) { + // XX memcmp + // + for ( i_w = 0; i_w < a_w; i_w++ ) { if ( a_u->buf_w[i_w] != b_u->buf_w[i_w] ) { return c3n; } @@ -292,29 +320,68 @@ _song_atom(u3_atom a, u3_noun b) return c3y; } -/* _song_x_cape(): unifying equality with comparison deduplication - * (tightly coupled to _song_x) +/* _cr_sing_cape_test(): check for previous comparison of [a] and [b]. +*/ +static inline c3_o +_cr_sing_cape_test(u3p(u3h_root) har_p, u3_noun a, u3_noun b) +{ + u3_noun key = u3nc(u3a_to_off(a), u3a_to_off(b)); + u3_noun val; + + u3t_off(euq_o); + val = u3h_git(har_p, key); + u3t_on(euq_o); + + u3z(key); + return ( u3_none == val ) ? c3y : c3n; +} + +/* _cr_sing_cape_keep(): store [a] and [b] to short-circuit subsequent tests. +** NB: [a] and [b] (which MUST be equal nouns) +** are cons'd as offsets (direct atoms) to avoid refcount churn. +*/ +static inline void +_cr_sing_cape_keep(u3p(u3h_root) har_p, u3_noun a, u3_noun b) +{ + // only store if [a] and [b] are copies of each other + // + if ( a != b ) { + u3_noun key = u3nc(u3a_to_off(a), u3a_to_off(b)); + u3t_off(euq_o); + u3h_put(har_p, key, c3y); + u3t_on(euq_o); + u3z(key); + } +} + +/* _cr_sing_cape(): unifying equality with comparison deduplication + * (tightly coupled to _cr_sing) */ static c3_o -_song_x_cape(c3_ys mov, c3_ys off, - eqframe* fam, eqframe* don, - u3p(u3h_root) har_p) +_cr_sing_cape(u3a_pile* pil_u, u3p(u3h_root) har_p) { - u3_noun a, b, key; - u3_weak got; + eqframe* fam_u = u3a_peek(pil_u); + u3_noun a, b, key; + u3_weak got; u3a_cell* a_u; u3a_cell* b_u; - while ( don != fam ) { - a = fam->a; - b = fam->b; - switch ( fam->sat_y ) { - case SONG_NONE: + // loop while arguments remain on the stack + // + do { + a = fam_u->a; + b = fam_u->b; + + switch ( fam_u->sat_y ) { + + // [a] and [b] are arbitrary nouns + // + case SING_NONE: { if ( a == b ) { break; } else if ( c3y == u3a_is_atom(a) ) { - if ( c3n == _song_atom(a, b) ) { + if ( c3n == _cr_sing_atom(a, b) ) { return c3n; } else { @@ -324,97 +391,104 @@ _song_x_cape(c3_ys mov, c3_ys off, else if ( c3y == u3a_is_atom(b) ) { return c3n; } + // [a] and [b] are cells + // else { a_u = u3a_to_ptr(a); b_u = u3a_to_ptr(b); - if ( (0 != a_u->mug_w) && - (0 != b_u->mug_w) && - (a_u->mug_w != b_u->mug_w) ) - { + // short-circuiting mug check + // + if ( c3n == _cr_sing_mug((u3a_noun*)a_u, (u3a_noun*)b_u) ) { return c3n; } + // short-circuiting re-comparison check + // + else if ( c3y == _cr_sing_cape_test(har_p, a, b) ) { + fam_u = u3a_pop(pil_u); + continue; + } + // upgrade none-frame to head-frame, check heads + // else { - key = u3nc(u3a_to_off(a), u3a_to_off(b)); - u3t_off(euq_o); - got = u3h_get(har_p, key); - u3t_on(euq_o); - u3z(key); - if ( u3_none != got ) { - fam = _eq_pop(mov, off); - continue; - } - fam->sat_y = SONG_HEAD; - fam = _eq_push(mov, off, a_u->hed, b_u->hed); + fam_u->sat_y = SING_HEAD; + fam_u = _cr_sing_push(pil_u, a_u->hed, b_u->hed); continue; } } + } break; - case SONG_HEAD: + // cells [a] and [b] have equal heads + // + case SING_HEAD: { a_u = u3a_to_ptr(a); b_u = u3a_to_ptr(b); u3a_wed(&(a_u->hed), &(b_u->hed)); - fam->sat_y = SONG_TAIL; - fam = _eq_push(mov, off, a_u->tel, b_u->tel); - continue; - case SONG_TAIL: + // upgrade head-frame to tail-frame, check tails + // + fam_u->sat_y = SING_TAIL; + fam_u = _cr_sing_push(pil_u, a_u->tel, b_u->tel); + continue; + } + + // cells [a] and [b] are equal + // + case SING_TAIL: { a_u = u3a_to_ptr(a); b_u = u3a_to_ptr(b); u3a_wed(&(a_u->tel), &(b_u->tel)); - break; + } break; - default: + default: { c3_assert(0); - break; + } break; } - // [har_p] is effectively a set of equal pairs. - // we cons [a] and [b] as posts so that we don't - // touch their reference counts. + // track equal pairs to short-circuit possible (re-)comparison // - if ( a != b ) { - key = u3nc(u3a_to_off(a), u3a_to_off(b)); - u3t_off(euq_o); - u3h_put(har_p, key, c3y); - u3t_on(euq_o); - u3z(key); - } - fam = _eq_pop(mov, off); + _cr_sing_cape_keep(har_p, a, b); + + fam_u = u3a_pop(pil_u); } + while ( c3n == u3a_pile_done(pil_u) ); return c3y; } -/* _song_x(): yes if a and b are the same noun, use uni to unify +/* _cr_sing(): unifying equality. */ static c3_o -_song_x(u3_noun a, u3_noun b) +_cr_sing(u3_noun a, u3_noun b) { - u3p(eqframe) empty = u3R->cap_p; + c3_s ovr_s = 0; + u3a_cell* a_u; + u3a_cell* b_u; + eqframe* fam_u; + u3a_pile pil_u; - c3_y wis_y = c3_wiseof(eqframe); - c3_o nor_o = u3a_is_north(u3R); - c3_ys mov = ( c3y == nor_o ? -wis_y : wis_y ); - c3_ys off = ( c3y == nor_o ? 0 : -wis_y ); - c3_s ovr_s = 0; - eqframe* fam = _eq_push(mov, off, a, b); - eqframe* don = u3to(eqframe, empty + off); + // initialize stack control, push arguments onto the stack (none-frame) + // + u3a_pile_prep(&pil_u, sizeof(eqframe)); + fam_u = _cr_sing_push(&pil_u, a, b); - u3a_cell* a_u; - u3a_cell* b_u; + // loop while arguments are on the stack + // + while ( c3n == u3a_pile_done(&pil_u) ) { + a = fam_u->a; + b = fam_u->b; - while ( don != fam ) { - a = fam->a; - b = fam->b; - switch ( fam->sat_y ) { - case SONG_NONE: + switch ( fam_u->sat_y ) { + + // [a] and [b] are arbitrary nouns + // + case SING_NONE: { if ( a == b ) { break; } else if ( c3y == u3a_is_atom(a) ) { - if ( c3n == _song_atom(a, b) ) { - u3R->cap_p = empty; + if ( c3n == _cr_sing_atom(a, b) ) { + u3R->cap_p = pil_u.top_p; return c3n; } else { @@ -422,57 +496,71 @@ _song_x(u3_noun a, u3_noun b) } } else if ( c3y == u3a_is_atom(b) ) { - u3R->cap_p = empty; + u3R->cap_p = pil_u.top_p; return c3n; } + // [a] and [b] are cells + // else { a_u = u3a_to_ptr(a); b_u = u3a_to_ptr(b); - if ( (0 != a_u->mug_w) && - (0 != b_u->mug_w) && - (a_u->mug_w != b_u->mug_w) ) - { - u3R->cap_p = empty; + // short-circuiting mug check + // + if ( c3n == _cr_sing_mug((u3a_noun*)a_u, (u3a_noun*)b_u) ) { + u3R->cap_p = pil_u.top_p; return c3n; } + // upgrade none-frame to head-frame, check heads + // else { - fam->sat_y = SONG_HEAD; - fam = _eq_push(mov, off, a_u->hed, b_u->hed); + fam_u->sat_y = SING_HEAD; + fam_u = _cr_sing_push(&pil_u, a_u->hed, b_u->hed); continue; } } + } break; - case SONG_HEAD: + // cells [a] and [b] have equal heads + // + case SING_HEAD: { a_u = u3a_to_ptr(a); b_u = u3a_to_ptr(b); u3a_wed(&(a_u->hed), &(b_u->hed)); - fam->sat_y = SONG_TAIL; - fam = _eq_push(mov, off, a_u->tel, b_u->tel); - continue; - case SONG_TAIL: + // upgrade head-frame to tail-frame, check tails + // + fam_u->sat_y = SING_TAIL; + fam_u = _cr_sing_push(&pil_u, a_u->tel, b_u->tel); + continue; + } + + // cells [a] and [b] are equal + // + case SING_TAIL: { a_u = u3a_to_ptr(a); b_u = u3a_to_ptr(b); u3a_wed(&(a_u->tel), &(b_u->tel)); - break; + } break; - default: + default: { c3_assert(0); - break; + } break; } - // [ovr_s] counts iterations. when it overflows, we know we've hit a - // pathological case and MUST start de-duplicating comparisons. + // [ovr_s] counts comparisons, if it overflows, we've likely hit + // a pathological case (highly duplicated tree), so we de-duplicate + // subsequent comparisons by maintaining a set of equal pairs. // if ( 0 == ++ovr_s ) { u3p(u3h_root) har_p = u3h_new(); - c3_o ret_o = _song_x_cape(mov, off, fam, don, har_p); + c3_o ret_o = _cr_sing_cape(&pil_u, har_p); u3h_free(har_p); - u3R->cap_p = empty; + u3R->cap_p = pil_u.top_p; return ret_o; } - fam = _eq_pop(mov, off); + + fam_u = u3a_pop(&pil_u); } return c3y; @@ -485,7 +573,7 @@ u3r_sing(u3_noun a, u3_noun b) { c3_o ret_o; u3t_on(euq_o); - ret_o = _song_x(a, b); + ret_o = _cr_sing(a, b); u3t_off(euq_o); return ret_o; } @@ -1455,101 +1543,24 @@ u3r_mug_cell(u3_noun hed, return u3r_mug_both(lus_w, biq_w); } -#define MUG_ROOT 0 -#define MUG_HEAD 1 -#define MUG_TAIL 2 +/* _cr_mug: stack frame for recording cell traversal +** !mug == head-frame +*/ +typedef struct { + c3_l mug_l; + u3_cell cel; +} _cr_mugf; -// stack frame for recording head vs tail iteration -// -// In Hoon, this structure would be as follows: -// -// $% [%root ~] -// [%head cell=^] -// [%tail cell=^ hed-mug=@] -// == -// -typedef struct mugframe +/* _cr_mug_next(): advance mug calculation, pushing cells onto the stack. +*/ +static inline c3_l +_cr_mug_next(u3a_pile* pil_u, u3_noun veb) { - c3_y tag_y; - u3a_cell* cel_u; - c3_w mug_w; -} mugframe; - -static inline void -_mug_push(c3_ys mov, - c3_ys off, - c3_y tag_y, - u3a_cell* cel_u, - c3_w mug_w) -{ - u3R->cap_p += mov; - - // ensure we haven't overflowed the stack - // (off==0 means we're on a north road) - // - if ( 0 == off ) { - if( !(u3R->cap_p > u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - else { - if( !(u3R->cap_p < u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - - mugframe* fam_u = u3to(mugframe, u3R->cap_p + off); - fam_u->tag_y = tag_y; - fam_u->cel_u = cel_u; - fam_u->mug_w = mug_w; -} - -static inline mugframe -_mug_pop(c3_ys mov, c3_ys off) -{ - mugframe* fam_u = u3to(mugframe, u3R->cap_p + off); - u3R->cap_p -= mov; - - return *fam_u; -} - -// u3r_mug(): statefully mug a noun using a 31-bit MurmurHash3 -// -c3_w -u3r_mug(u3_noun veb) -{ - // sanity check (makes a clear error message) - // - c3_assert( u3_none != veb ); - - // initialize signed stack offsets (relative to north/south road) - // - c3_ys mov, off; - { - c3_y wis_y = c3_wiseof(mugframe); - c3_o nor_o = u3a_is_north(u3R); - mov = ( c3y == nor_o ? -wis_y : wis_y ); - off = ( c3y == nor_o ? 0 : -wis_y ); - } - - // stash the current stack post - // - u3p(mugframe) cap_p = u3R->cap_p; - - // push the (only) ROOT stack frame (our termination condition) - // - _mug_push(mov, off, MUG_ROOT, 0, 0); - - c3_w mug_w; - - // read from the current noun .veb - // - advance: { + while ( 1 ) { // veb is a direct atom, mug is not memoized // - if ( _(u3a_is_cat(veb)) ) { - mug_w = u3r_mug_words(&veb, 1); - goto retreat; + if ( c3y == u3a_is_cat(veb) ) { + return (c3_l)u3r_mug_words(&veb, 1); } // veb is indirect, a pointer into the loom // @@ -1558,72 +1569,86 @@ u3r_mug(u3_noun veb) // veb has already been mugged, return memoized value // - if ( 0 != veb_u->mug_w ) { - mug_w = veb_u->mug_w; - goto retreat; + // XX add debug assertion that mug is 31-bit? + // + if ( veb_u->mug_w ) { + return (c3_l)veb_u->mug_w; } // veb is an indirect atom, mug its bytes and memoize // - else if ( _(u3a_is_atom(veb)) ) { + else if ( c3y == u3a_is_atom(veb) ) { u3a_atom* vat_u = (u3a_atom*)veb_u; - mug_w = u3r_mug_words(vat_u->buf_w, vat_u->len_w); - vat_u->mug_w = mug_w; - goto retreat; + c3_l mug_l = u3r_mug_words(vat_u->buf_w, vat_u->len_w); + vat_u->mug_w = mug_l; + return mug_l; } // veb is a cell, push a stack frame to mark head-recursion // and read the head // else { u3a_cell* cel_u = (u3a_cell*)veb_u; - _mug_push(mov, off, MUG_HEAD, cel_u, 0); + _cr_mugf* fam_u = u3a_push(pil_u); + + // check for overflow + // + u3a_pile_sane(pil_u); + + fam_u->mug_l = 0; + fam_u->cel = veb; + veb = cel_u->hed; - goto advance; + continue; } } } +} - // consume the popped stack frame and mug from above - // - retreat: { - mugframe fam_u = _mug_pop(mov, off); - - switch ( fam_u.tag_y ) { - default: { - c3_assert(0); - } - - // we done - // - case MUG_ROOT: { - break; - } - - // mug_w is the mug of the head of cel_u - // push a stack frame to mark tail recursion, - // record the mug of the head, and read the tail - // - case MUG_HEAD: { - _mug_push(mov, off, MUG_TAIL, fam_u.cel_u, mug_w); - - veb = fam_u.cel_u->tel; - goto advance; - } - - // mug_w is the mug of the tail of cel_u - // combine the mugs, memoize the value, and recur - // - case MUG_TAIL: { - u3a_cell* cel_u = fam_u.cel_u; - mug_w = u3r_mug_both(fam_u.mug_w, mug_w); - cel_u->mug_w = mug_w; - goto retreat; - } - } - } +/* u3r_mug(): statefully mug a noun with 31-bit murmur3. +*/ +c3_l +u3r_mug(u3_noun veb) +{ + u3a_pile pil_u; + _cr_mugf* fam_u; + c3_l mug_l; // sanity check // - c3_assert( u3R->cap_p == cap_p ); + c3_assert( u3_none != veb ); - return mug_w; + u3a_pile_prep(&pil_u, sizeof(*fam_u)); + + // commence mugging + // + mug_l = _cr_mug_next(&pil_u, veb); + + // process cell results + // + if ( c3n == u3a_pile_done(&pil_u) ) { + fam_u = u3a_peek(&pil_u); + + do { + // head-frame: stash mug and continue into the tail + // + if ( !fam_u->mug_l ) { + u3a_cell* cel_u = u3a_to_ptr(fam_u->cel); + + fam_u->mug_l = mug_l; + mug_l = _cr_mug_next(&pil_u, cel_u->tel); + fam_u = u3a_peek(&pil_u); + } + // tail-frame: calculate/memoize cell mug and pop the stack + // + else { + u3a_cell* cel_u = u3a_to_ptr(fam_u->cel); + + mug_l = u3r_mug_both(fam_u->mug_l, mug_l); + cel_u->mug_w = mug_l; + fam_u = u3a_pop(&pil_u); + } + } + while ( c3n == u3a_pile_done(&pil_u) ); + } + + return mug_l; } diff --git a/pkg/urbit/noun/serial.c b/pkg/urbit/noun/serial.c index aba236b09..dac9970a5 100644 --- a/pkg/urbit/noun/serial.c +++ b/pkg/urbit/noun/serial.c @@ -286,111 +286,35 @@ u3s_jam_xeno(u3_noun a, c3_d* len_d, c3_y** byt_y) return ur_bsw_done(&jam_u.rit_u, len_d, byt_y); } -#define CUE_ROOT 0 -#define CUE_HEAD 1 -#define CUE_TAIL 2 - -// stack frame for recording head vs tail iteration -// -// In Hoon, this structure would be as follows: -// -// $% [%root ~] -// [%head cell-cursor=@] -// [%tail cell-cursor=@ hed-width=@ hed-value=*] -// == -// -typedef struct _cs_cue_frame -{ - c3_y tag_y; - u3_atom cur; - u3_atom wid; - u3_noun hed; -} cueframe; - -/* _cs_cue_push(): construct a cueframe and push it onto the road stack. +/* _cs_cue: stack frame for tracking intermediate cell results */ -static inline void -_cs_cue_push(c3_ys mov, - c3_ys off, - c3_y tag_y, - u3_atom cur, - u3_atom wid, - u3_noun hed) +typedef struct _cs_cue { + u3_weak hed; // head of a cell or u3_none + u3_atom wid; // bitwidth of [hed] or 0 + u3_atom cur; // bit-cursor position +} _cs_cue; + +/* _cs_rub: rub, TRANSFER [cur], RETAIN [a] +*/ +static inline u3_noun +_cs_rub(u3_atom cur, u3_atom a) { - u3R->cap_p += mov; - - // ensure we haven't overflowed the stack - // (off==0 means we're on a north road) - // - if ( 0 == off ) { - if( !(u3R->cap_p > u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - else { - if( !(u3R->cap_p < u3R->hat_p) ) { - u3m_bail(c3__meme); - } - } - - cueframe* fam_u = u3to(cueframe, u3R->cap_p + off); - fam_u->tag_y = tag_y; - fam_u->cur = cur; - fam_u->wid = wid; - fam_u->hed = hed; + u3_noun pro = u3qe_rub(cur, a); + u3z(cur); + return pro; } -/* _cs_cue_pop(): pop a cueframe off the road stack and return it. +/* _cs_cue_next(): advance into [a], reading next value +** TRANSFER [cur], RETAIN [a] */ -static inline cueframe -_cs_cue_pop(c3_ys mov, c3_ys off) +static inline u3_noun +_cs_cue_next(u3a_pile* pil_u, + u3p(u3h_root) har_p, + u3_atom cur, + u3_atom a, + u3_atom* wid) { - cueframe* fam_u = u3to(cueframe, u3R->cap_p + off); - u3R->cap_p -= mov; - - return *fam_u; -} - -/* u3s_cue(): cue [a] -*/ -u3_noun -u3s_cue(u3_atom a) -{ - // initialize signed stack offsets (relative to north/south road) - // - c3_ys mov, off; - { - c3_y wis_y = c3_wiseof(cueframe); - c3_o nor_o = u3a_is_north(u3R); - mov = ( c3y == nor_o ? -wis_y : wis_y ); - off = ( c3y == nor_o ? 0 : -wis_y ); - } - - // initialize a hash table for dereferencing backrefs - // - u3p(u3h_root) har_p = u3h_new(); - - // stash the current stack post - // - u3p(cueframe) cap_p = u3R->cap_p; - - // push the (only) ROOT stack frame (our termination condition) - // - _cs_cue_push(mov, off, CUE_ROOT, 0, 0, 0); - - // initialize cursor to bit-position 0 - // - u3_atom cur = 0; - - // the bitwidth and product from reading at cursor - // - u3_atom wid, pro; - - // read from atom at cursor - // - // TRANSFER .cur - // - advance: { + while ( 1 ) { // read tag bit at cur // c3_y tag_y = u3qc_cut(0, cur, 1, a); @@ -400,19 +324,14 @@ u3s_cue(u3_atom a) // produce atom and the width we read // if ( 0 == tag_y ) { - u3_noun bur; - { - u3_noun x = u3qa_inc(cur); - bur = u3qe_rub(x, a); - u3z(x); - } + u3_noun bur = _cs_rub(u3i_vint(cur), a); + u3_noun pro = u3k(u3t(bur)); - pro = u3k(u3t(bur)); u3h_put(har_p, cur, u3k(pro)); - wid = u3qa_inc(u3h(bur)); + *wid = u3qa_inc(u3h(bur)); u3z(bur); - goto retreat; + return pro; } else { // read tag bit at (1 + cur) @@ -428,87 +347,94 @@ u3s_cue(u3_atom a) // produce referenced value and the width we read // if ( 1 == tag_y ) { - u3_noun bur; - { - u3_noun x = u3ka_add(2, cur); - bur = u3qe_rub(x, a); - u3z(x); - } + u3_noun bur = _cs_rub(u3ka_add(2, cur), a); + u3_noun pro = u3x_good(u3h_get(har_p, u3t(bur))); - pro = u3h_get(har_p, u3k(u3t(bur))); - - if ( u3_none == pro ) { - return u3m_bail(c3__exit); - } - - wid = u3qa_add(2, u3h(bur)); + *wid = u3qa_add(2, u3h(bur)); u3z(bur); - goto retreat; + return pro; } // next bit unset, (2 + cur) points to the head of a cell // - // push a frame to mark HEAD recursion and read the head + // push a head-frame onto the road stack and read the head // else { - _cs_cue_push(mov, off, CUE_HEAD, cur, 0, 0); + _cs_cue* fam_u = u3a_push(pil_u); + u3a_pile_sane(pil_u); + + // NB: fam_u->wid unused in head-frame + // + fam_u->hed = u3_none; + fam_u->cur = cur; cur = u3qa_add(2, cur); - goto advance; + continue; } } } +} - // consume: popped stack frame, .wid and .pro from above. +u3_noun +u3s_cue(u3_atom a) +{ + // pro: cue'd noun product + // wid: bitwidth read to produce [pro] + // fam_u: stack frame + // har_p: backreference table + // pil_u: stack control structure // - // TRANSFER .wid, .pro, and contents of .fam_u - // (.cur is in scope, but we have already lost our reference to it) + u3_noun pro; + u3_atom wid, cur = 0; + _cs_cue* fam_u; + u3p(u3h_root) har_p = u3h_new(); + u3a_pile pil_u; + + // initialize stack control // - retreat: { - cueframe fam_u = _cs_cue_pop(mov, off); + u3a_pile_prep(&pil_u, sizeof(*fam_u)); - switch ( fam_u.tag_y ) { - default: { - c3_assert(0); - } + // commence cueing at bit-position 0 + // + pro = _cs_cue_next(&pil_u, har_p, 0, a, &wid); - // fam_u is our stack root, we're done. + // process cell results + // + if ( c3n == u3a_pile_done(&pil_u) ) { + fam_u = u3a_peek(&pil_u); + + do { + // head-frame: stash [pro] and [wid]; continue into the tail // - case CUE_ROOT: { - break; - } + if ( u3_none == fam_u->hed ) { + // NB: fam_u->wid unused in head-frame + // + fam_u->hed = pro; + fam_u->wid = wid; - // .wid and .pro are the head of the cell at fam_u.cur. - // save them (and the cell cursor) in a TAIL frame, - // set the cursor to the tail and read there. + // continue reading at the bit-position after [pro] + { + u3_noun cur = u3ka_add(2, u3qa_add(wid, fam_u->cur)); + pro = _cs_cue_next(&pil_u, har_p, cur, a, &wid); + } + + fam_u = u3a_peek(&pil_u); + } + // tail-frame: cons cell, recalculate [wid], and pop the stack // - case CUE_HEAD: { - _cs_cue_push(mov, off, CUE_TAIL, fam_u.cur, wid, pro); - - cur = u3ka_add(2, u3qa_add(wid, fam_u.cur)); - goto advance; + else { + pro = u3nc(fam_u->hed, pro); + u3h_put(har_p, fam_u->cur, u3k(pro)); + u3z(fam_u->cur); + wid = u3ka_add(2, u3ka_add(wid, fam_u->wid)); + fam_u = u3a_pop(&pil_u); } - - // .wid and .pro are the tail of the cell at fam_u.cur, - // construct the cell, memoize it, and produce it along with - // its total width (as if it were a read from above). - // - case CUE_TAIL: { - pro = u3nc(fam_u.hed, pro); - u3h_put(har_p, fam_u.cur, u3k(pro)); - wid = u3ka_add(2, u3ka_add(wid, fam_u.wid)); - goto retreat; - } - } + } while ( c3n == u3a_pile_done(&pil_u) ); } u3z(wid); u3h_free(har_p); - // sanity check - // - c3_assert( u3R->cap_p == cap_p ); - return pro; }