Merge pull request #1741 from urbit/reunity

u3: unifies unifying-equality unification
This commit is contained in:
Joe Bryan 2019-09-24 10:55:45 -07:00 committed by GitHub
commit 2f739c0edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 164 additions and 360 deletions

View File

@ -387,6 +387,11 @@
c3_w
u3a_use(u3_noun som);
/* u3a_wed(): unify noun references.
*/
void
u3a_wed(u3_noun* a, u3_noun* b);
/* u3a_luse(): check refcount sanity.
*/
void

View File

@ -124,13 +124,6 @@
u3_weak
u3h_git(u3p(u3h_root) har_p, u3_noun key);
/* u3h_gut(): read from hashtable, unifying key nouns.
**
** `key` is RETAINED.
*/
u3_weak
u3h_gut(u3p(u3h_root) har_p, u3_noun key);
/* u3h_trim_to(): trim to n key-value pairs
*/
void

View File

@ -134,29 +134,14 @@
u3_noun s,
u3_noun b);
/* u3r_sing():
/* u3r_sing(): noun value equality.
**
** Yes iff (a) and (b) are the same noun.
** Unifies noun pointers on inner roads.
*/
c3_o
u3r_sing(u3_noun a, u3_noun b);
/* u3rz_sing(): transferring u3r_sing
*/
c3_o
u3rz_sing(u3_noun a, u3_noun b);
/* u3r_sung(): yes iff (a) and (b) are the same noun, unifying equals.
**
** Make sure you have no live, uncounted pointers to any noun
** within (a) or (b)!
*/
c3_o
u3r_sung(u3_noun a, u3_noun b);
/* u3r_sing_c):
**
** Yes iff (b) is the same noun as the C string [a].
/* u3r_sing_c(): cord/C-string value equivalence.
*/
c3_o
u3r_sing_c(const c3_c* a_c,

View File

@ -1492,6 +1492,103 @@ u3a_use(u3_noun som)
}
}
/* _ca_wed_who(): unify [a] and [b] on [rod_u], keeping the senior
**
** NB: this leaks a reference when it unifies in a senior road
*/
static c3_o
_ca_wed_who(u3a_road* rod_u, u3_noun* a, u3_noun* b)
{
c3_t asr_t = ( c3y == u3a_is_senior(rod_u, *a) );
c3_t bsr_t = ( c3y == u3a_is_senior(rod_u, *b) );
c3_t nor_t = ( c3y == u3a_is_north(rod_u) );
c3_t own_t = ( rod_u == u3R );
// both are on [rod_u]; gain a reference to whichever we keep
//
if ( !asr_t && !bsr_t ) {
// keep [a]; it's deeper in the heap
//
// (N && >) || (S && <)
//
if ( (*a > *b) == nor_t ) {
_me_gain_use(*a);
if ( own_t ) { u3z(*b); }
*b = *a;
}
// keep [b]; it's deeper in the heap
//
else {
_me_gain_use(*b);
if ( own_t ) { u3z(*a); }
*a = *b;
}
return c3y;
}
// keep [a]; it's senior
//
else if ( asr_t && !bsr_t ) {
if ( own_t ) { u3z(*b); }
*b = *a;
return c3y;
}
// keep [b]; it's senior
//
else if ( !asr_t && bsr_t ) {
if ( own_t ) { u3z(*a); }
*a = *b;
return c3y;
}
// both [a] and [b] are senior; we can't unify on [rod_u]
//
return c3n;
}
/* u3a_wed(): unify noun references.
*/
void
u3a_wed(u3_noun* a, u3_noun* b)
{
if ( *a != *b ) {
u3_road* rod_u = u3R;
// while not at home, attempt to unify
//
// we try to unify on our road, and retry on senior roads
// until we succeed or reach the home road.
//
// we can't perform this kind of butchery on the home road,
// where asynchronous things can allocate.
// (XX anything besides u3t_samp?)
//
// when unifying on a higher road, we can't free nouns,
// because we can't track junior nouns that point into
// that road.
//
// this is just an implementation issue -- we could set use
// counts to 0 without actually freeing. but the allocator
// would have to be actually designed for this.
// (alternately, we could keep a deferred free-list)
//
// not freeing may generate spurious leaks, so we disable
// senior unification when debugging memory. this will
// cause a very slow boot process as the compiler compiles
// itself, constantly running into duplicates.
//
while ( (rod_u != &u3H->rod_u) &&
(c3n == _ca_wed_who(rod_u, a, b)) )
{
#ifdef U3_MEMORY_DEBUG
break;
#else
rod_u = u3to(u3_road, rod_u->par_p);
#endif
}
}
}
/* u3a_luse(): check refcount sanity.
*/
void

View File

@ -626,102 +626,6 @@ u3h_get(u3p(u3h_root) har_p, u3_noun key)
return pro;
}
/* _ch_buck_gut(): read in bucket, unifying key nouns.
*/
static u3_weak
_ch_buck_gut(u3h_buck* hab_u, u3_noun key)
{
c3_w i_w;
for ( i_w = 0; i_w < hab_u->len_w; i_w++ ) {
u3_noun kev = u3h_slot_to_noun(hab_u->sot_w[i_w]);
if ( _(u3r_sung(key, u3h(kev))) ) {
return u3t(kev);
}
}
return u3_none;
}
/* _ch_node_gut(): read in node, unifying key nouns.
*/
static u3_weak
_ch_node_gut(u3h_node* han_u, c3_w lef_w, c3_w rem_w, u3_noun key)
{
c3_w bit_w, map_w;
lef_w -= 5;
bit_w = (rem_w >> lef_w);
rem_w = CUT_END(rem_w, lef_w);
map_w = han_u->map_w;
if ( !BIT_SET(map_w, bit_w) ) {
return u3_none;
}
else {
c3_w inx_w = _ch_popcount(CUT_END(map_w, bit_w));
c3_w sot_w = han_u->sot_w[inx_w];
if ( _(u3h_slot_is_noun(sot_w)) ) {
u3_noun kev = u3h_slot_to_noun(sot_w);
if ( _(u3r_sung(key, u3h(kev))) ) {
return u3t(kev);
}
else {
return u3_none;
}
}
else {
void* hav_v = u3h_slot_to_node(sot_w);
if ( 0 == lef_w ) {
return _ch_buck_gut(hav_v, key);
}
else return _ch_node_gut(hav_v, lef_w, rem_w, key);
}
}
}
/* u3h_gut(): read from hashtable, unifying key nouns.
**
** `key` is RETAINED.
*/
u3_weak
u3h_gut(u3p(u3h_root) har_p, u3_noun key)
{
u3h_root* har_u = u3to(u3h_root, har_p);
c3_w mug_w = u3r_mug(key);
c3_w inx_w = (mug_w >> 25);
c3_w rem_w = CUT_END(mug_w, 25);
c3_w sot_w = har_u->sot_w[inx_w];
if ( _(u3h_slot_is_null(sot_w)) ) {
return u3_none;
}
else if ( _(u3h_slot_is_noun(sot_w)) ) {
u3_noun kev = u3h_slot_to_noun(sot_w);
if ( _(u3r_sung(key, u3h(kev))) ) {
har_u->sot_w[inx_w] = u3h_noun_be_warm(sot_w);
return u3k(u3t(kev));
}
else {
return u3_none;
}
}
else {
u3h_node* han_u = u3h_slot_to_node(sot_w);
u3_weak pro = _ch_node_gut(han_u, 25, rem_w, key);
if ( u3_none == pro ) {
return u3_none;
}
else {
return u3k(pro);
}
}
}
/* _ch_free_buck(): free bucket
*/
static void

View File

@ -486,7 +486,7 @@ _cj_find_cold(u3_noun bat)
u3a_road* rod_u = u3R;
while ( 1 ) {
u3_weak bar = u3h_gut(rod_u->jed.cod_p, bat);
u3_weak bar = u3h_get(rod_u->jed.cod_p, bat);
if ( u3_none != bar ) {
return bar;
@ -508,7 +508,7 @@ _cj_find_warm(u3_noun loc)
u3a_road* rod_u = u3R;
while ( 1 ) {
u3_weak ank = u3h_gut(rod_u->jed.war_p, loc);
u3_weak ank = u3h_get(rod_u->jed.war_p, loc);
if ( u3_none != ank ) {
return ank;

View File

@ -744,9 +744,9 @@ u3m_leap(c3_w pad_w)
rod_u = _pave_south(u3a_into(bot_p), c3_wiseof(u3a_road), len_w);
#if 0
u3l_log("leap: from north %p (cap %x), to south %p\r\n",
fprintf(stderr, "leap: from north %p (cap 0x%x), to south %p\r\n",
u3R,
u3R->cap_p + len_p,
u3R->cap_p + len_w,
rod_u);
#endif
}
@ -756,9 +756,9 @@ u3m_leap(c3_w pad_w)
rod_u = _pave_north(u3a_into(bot_p), c3_wiseof(u3a_road), len_w);
#if 0
u3l_log("leap: from north %p (cap %p), to south %p\r\n",
fprintf(stderr, "leap: from south %p (cap 0x%x), to north %p\r\n",
u3R,
u3R->cap_p - len_p,
u3R->cap_p - len_w,
rod_u);
#endif
}
@ -791,13 +791,13 @@ u3m_fall()
c3_assert(0 != u3R->par_p);
#if 0
u3l_log("fall: from %s %p, to %s %p (cap %p, was %p)\r\n",
fprintf(stderr, "fall: from %s %p, to %s %p (cap 0x%x, was 0x%x)\r\n",
_(u3a_is_north(u3R)) ? "north" : "south",
u3R,
_(u3a_is_north(u3R)) ? "north" : "south",
_(u3a_is_north(u3to(u3_road, u3R->par_p))) ? "north" : "south",
u3to(u3_road, u3R->par_p),
u3R->hat_w,
u3R->rut_w);
u3R->hat_p,
u3R->rut_p);
#endif
u3to(u3_road, u3R->par_p)->pro.nox_d += u3R->pro.nox_d;

View File

@ -220,59 +220,6 @@ u3r_mean(u3_noun som, ...)
return ret_o;
}
/* _sang_one(): unify but leak old.
*/
static void
_sang_one(u3_noun* a, u3_noun* b)
{
if ( *a == *b ) {
return;
}
else {
c3_o asr_o = u3a_is_senior(u3R, *a);
c3_o bsr_o = u3a_is_senior(u3R, *b);
if ( _(asr_o) && _(bsr_o) ) {
// You shouldn't have let this happen. We don't want to
// descend down to a lower road and free there, because
// synchronization - though this could be revisited under
// certain circumstances.
//
return;
}
if ( _(asr_o) && !_(bsr_o) ){
// u3z(*b);
*b = *a;
}
if ( _(bsr_o) && !_(asr_o) ) {
// u3z(*a);
*a = *b;
}
if ( u3a_is_north(u3R) ) {
if ( *a <= *b ) {
u3k(*a);
// u3z(*b);
*b = *a;
} else {
u3k(*b);
// u3z(*a);
*a = *b;
}
}
else {
if ( *a >= *b ) {
u3k(*a);
// u3z(*b);
*b = *a;
} else {
u3k(*b);
// u3z(*a);
*a = *b;
}
}
}
}
#define SONG_NONE 0
#define SONG_HEAD 1
#define SONG_TAIL 2
@ -301,115 +248,26 @@ _eq_pop(c3_ys mov, c3_ys off)
return u3to(eqframe, u3R->cap_p + off);
}
/* _sing_one(): do not pick a unified pointer for identical (a) and (b).
/* _song_atom(): check if atom [a] is indirect and equal to noun [b]
*/
static void
_sing_one(u3_noun* a, u3_noun* b)
{
// this space left intentionally blank
}
/* _sung_one(): pick a unified pointer for identical (a) and (b).
**
** Assumes exclusive access to noun memory.
*/
static void
_sung_one(u3_noun* a, u3_noun* b)
{
if ( *a == *b ) {
return;
} else {
u3_road* rod_u = u3R;
while ( 1 ) {
//
// we can't perform this kind of butchery on the home road,
// where asynchronous things can allocate.
//
if ( u3R == &u3H->rod_u ) {
break;
}
else {
c3_o asr_o = u3a_is_senior(u3R, *a);
c3_o bsr_o = u3a_is_senior(u3R, *b);
if ( _(asr_o) && _(bsr_o) ) {
//
// when unifying on a higher road, we can't free nouns,
// because we can't track junior nouns that point into
// that road.
//
// this is just an implementation issue -- we could set use
// counts to 0 without actually freeing. but the allocator
// would have to be actually designed for this.
//
// not freeing may generate spurious leaks, so we disable
// senior unification when debugging memory. this will
// cause a very slow boot process as the compiler compiles
// itself, constantly running into duplicates.
//
#ifdef U3_MEMORY_DEBUG
return;
#else
u3R = u3to(u3_road, u3R->par_p);
continue;
#endif
}
if ( _(asr_o) && !_(bsr_o) ){
if ( u3R == rod_u ) { u3z(*b); }
*b = *a;
}
if ( _(bsr_o) && !_(asr_o) ) {
if ( u3R == rod_u ) { u3z(*a); }
*a = *b;
}
if ( u3a_is_north(u3R) ) {
if ( *a <= *b ) {
u3k(*a);
if ( u3R == rod_u ) { u3z(*b); }
*b = *a;
} else {
u3k(*b);
if ( u3R == rod_u ) { u3z(*a); }
*a = *b;
}
}
else {
if ( *a >= *b ) {
u3k(*a);
if ( u3R == rod_u ) { u3z(*b); }
*b = *a;
} else {
u3k(*b);
if ( u3R == rod_u ) { u3z(*a); }
*a = *b;
}
}
break;
}
}
u3R = rod_u;
}
}
static inline c3_o
_song_atom(u3_atom a, u3_atom b)
_song_atom(u3_atom a, u3_noun b)
{
u3a_atom* a_u = u3a_to_ptr(a);
if ( !_(u3a_is_atom(b)) ||
_(u3a_is_cat(a)) ||
_(u3a_is_cat(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)) )
{
return c3n;
}
else {
u3a_atom* a_u = u3a_to_ptr(a);
u3a_atom* b_u = u3a_to_ptr(b);
if ( a_u->mug_w &&
b_u->mug_w &&
(a_u->mug_w != b_u->mug_w) )
if ( (0 != a_u->mug_w) &&
(0 != b_u->mug_w) &&
(a_u->mug_w != b_u->mug_w) )
{
return c3n;
}
@ -431,6 +289,7 @@ _song_atom(u3_atom a, u3_atom b)
}
}
}
return c3y;
}
@ -440,8 +299,7 @@ _song_atom(u3_atom a, u3_atom b)
static c3_o
_song_x_cape(c3_ys mov, c3_ys off,
eqframe* fam, eqframe* don,
u3p(u3h_root) har_p,
void (*uni)(u3_noun*, u3_noun*))
u3p(u3h_root) har_p)
{
u3_noun a, b, key;
u3_weak got;
@ -468,12 +326,13 @@ _song_x_cape(c3_ys mov, c3_ys off,
return c3n;
}
else {
u3a_cell* a_u = u3a_to_ptr(a);
u3a_cell* b_u = u3a_to_ptr(b);
a_u = u3a_to_ptr(a);
b_u = u3a_to_ptr(b);
if ( a_u->mug_w &&
b_u->mug_w &&
(a_u->mug_w != b_u->mug_w) ) {
if ( (0 != a_u->mug_w) &&
(0 != b_u->mug_w) &&
(a_u->mug_w != b_u->mug_w) )
{
return c3n;
}
else {
@ -495,7 +354,7 @@ _song_x_cape(c3_ys mov, c3_ys off,
case SONG_HEAD:
a_u = u3a_to_ptr(a);
b_u = u3a_to_ptr(b);
uni(&(a_u->hed), &(b_u->hed));
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;
@ -503,7 +362,7 @@ _song_x_cape(c3_ys mov, c3_ys off,
case SONG_TAIL:
a_u = u3a_to_ptr(a);
b_u = u3a_to_ptr(b);
uni(&(a_u->tel), &(b_u->tel));
u3a_wed(&(a_u->tel), &(b_u->tel));
break;
default:
@ -511,6 +370,10 @@ _song_x_cape(c3_ys mov, c3_ys off,
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.
//
key = u3nc(u3a_to_off(a), u3a_to_off(b));
u3t_off(euq_o);
u3h_put(har_p, key, c3y);
@ -525,7 +388,7 @@ _song_x_cape(c3_ys mov, c3_ys off,
/* _song_x(): yes if a and b are the same noun, use uni to unify
*/
static c3_o
_song_x(u3_noun a, u3_noun b, void (*uni)(u3_noun*, u3_noun*))
_song_x(u3_noun a, u3_noun b)
{
u3p(eqframe) empty = u3R->cap_p;
@ -565,9 +428,10 @@ _song_x(u3_noun a, u3_noun b, void (*uni)(u3_noun*, u3_noun*))
a_u = u3a_to_ptr(a);
b_u = u3a_to_ptr(b);
if ( a_u->mug_w &&
b_u->mug_w &&
(a_u->mug_w != b_u->mug_w) ) {
if ( (0 != a_u->mug_w) &&
(0 != b_u->mug_w) &&
(a_u->mug_w != b_u->mug_w) )
{
u3R->cap_p = empty;
return c3n;
}
@ -581,7 +445,7 @@ _song_x(u3_noun a, u3_noun b, void (*uni)(u3_noun*, u3_noun*))
case SONG_HEAD:
a_u = u3a_to_ptr(a);
b_u = u3a_to_ptr(b);
uni(&(a_u->hed), &(b_u->hed));
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;
@ -589,7 +453,7 @@ _song_x(u3_noun a, u3_noun b, void (*uni)(u3_noun*, u3_noun*))
case SONG_TAIL:
a_u = u3a_to_ptr(a);
b_u = u3a_to_ptr(b);
uni(&(a_u->tel), &(b_u->tel));
u3a_wed(&(a_u->tel), &(b_u->tel));
break;
default:
@ -597,9 +461,12 @@ _song_x(u3_noun a, u3_noun b, void (*uni)(u3_noun*, u3_noun*))
break;
}
// [ovr_s] counts iterations. when it overflows, we know we've hit a
// pathological case and MUST start de-duplicating comparisons.
//
if ( 0 == ++ovr_s ) {
u3p(u3h_root) har_p = u3h_new();
c3_o ret_o = _song_x_cape(mov, off, fam, don, har_p, uni);
c3_o ret_o = _song_x_cape(mov, off, fam, don, har_p);
u3h_free(har_p);
u3R->cap_p = empty;
return ret_o;
@ -610,59 +477,14 @@ _song_x(u3_noun a, u3_noun b, void (*uni)(u3_noun*, u3_noun*))
return c3y;
}
/* u3r_sang(): yes iff (a) and (b) are the same noun, unifying equals.
*/
c3_o
u3r_sang(u3_noun a, u3_noun b)
{
c3_o ret_o;
u3t_on(euq_o);
ret_o = _song_x(a, b, &_sang_one);
u3t_off(euq_o);
return ret_o;
}
/* u3r_sing():
**
** Yes iff (a) and (b) are the same noun.
/* u3r_sing(): Yes iff [a] and [b] are the same noun.
*/
c3_o
u3r_sing(u3_noun a, u3_noun b)
{
#ifndef U3_MEMORY_DEBUG
if ( u3R->par_p ) {
return u3r_sang(a, b);
}
#endif
{
c3_o ret_o;
u3t_on(euq_o);
ret_o = _song_x(a, b, &_sing_one);
u3t_off(euq_o);
return ret_o;
}
}
/* u3rz_sing(): transferring u3r_sing
*/
c3_o
u3rz_sing(u3_noun a, u3_noun b)
{
c3_o ret_o = u3r_sing(a, b);
u3z(a); u3z(b);
return ret_o;
}
/* u3r_sung(): yes iff (a) and (b) are the same noun, unifying equals.
*/
c3_o
u3r_sung(u3_noun a, u3_noun b)
{
c3_o ret_o;
u3t_on(euq_o);
ret_o = _song_x(a, b, &_sung_one);
ret_o = _song_x(a, b);
u3t_off(euq_o);
return ret_o;
}
@ -841,13 +663,11 @@ u3r_nord(u3_noun a,
}
}
/* u3r_sing_c():
**
** Yes iff (b) is the same noun as the C string a_c.
/* u3r_sing_c(): cord/C-string value equivalence.
*/
c3_o
u3r_sing_c(const c3_c* a_c,
u3_noun b)
u3_noun b)
{
c3_assert(u3_none != b);

View File

@ -970,7 +970,7 @@ u3_cttp_ef_http_client(u3_noun fav)
{
u3_creq* ceq_u;
if ( c3y == u3rz_sing(u3i_string("request"), u3k(u3h(fav))) ) {
if ( c3y == u3r_sing_c("request", u3h(fav)) ) {
u3_noun p_fav, q_fav;
u3x_cell(u3t(fav), &p_fav, &q_fav);
@ -983,7 +983,7 @@ u3_cttp_ef_http_client(u3_noun fav)
u3l_log("cttp: strange request (unparsable url)\n");
}
}
else if ( c3y == u3rz_sing(u3i_string("cancel-request"), u3k(u3h(fav))) ) {
else if ( c3y == u3r_sing_c("cancel-request", u3h(fav)) ) {
ceq_u =_cttp_creq_find(u3r_word(0, u3t(fav)));
if ( ceq_u ) {

View File

@ -1505,16 +1505,16 @@ u3_http_ef_http_server(c3_l sev_l,
// sets server configuration
//
if ( c3y == u3rz_sing(u3i_string("set-config"), u3k(tag)) ) {
if ( c3y == u3r_sing_c("set-config", tag) ) {
u3_http_ef_form(u3k(dat));
}
// responds to an open request
//
else if ( 0 != (req_u = _http_search_req(sev_l, coq_l, seq_l)) ) {
if ( c3y == u3rz_sing(u3i_string("response"), u3k(tag)) ) {
if ( c3y == u3r_sing_c("response", tag) ) {
u3_noun response = dat;
if ( c3y == u3rz_sing(u3i_string("start"), u3k(u3h(response))) ) {
if ( c3y == u3r_sing_c("start", u3h(response)) ) {
// Separate the %start message into its components.
//
u3_noun response_header, data, complete;
@ -1525,7 +1525,7 @@ u3_http_ef_http_server(c3_l sev_l,
_http_start_respond(req_u, u3k(status), u3k(headers), u3k(data),
u3k(complete));
}
else if ( c3y == u3rz_sing(u3i_string("continue"), u3k(u3h(response))) ) {
else if ( c3y == u3r_sing_c("continue", u3h(response)) ) {
// Separate the %continue message into its components.
//
u3_noun data, complete;
@ -1533,7 +1533,7 @@ u3_http_ef_http_server(c3_l sev_l,
_http_continue_respond(req_u, u3k(data), u3k(complete));
}
else if (c3y == u3rz_sing(u3i_string("cancel"), u3k(u3h(response)))) {
else if (c3y == u3r_sing_c("cancel", u3h(response))) {
u3l_log("http: %%cancel not handled yet\n");
}
else {

View File

@ -280,7 +280,7 @@ _reck_kick_spec(u3_pier* pir_u, u3_noun pox, u3_noun fav)
if ( (c3n == u3r_cell(t_pox, &it_pox, &tt_pox)) ) {
u3z(pox); u3z(fav); return c3n;
}
else if ( c3y == u3rz_sing(u3i_string("http-server"), u3k(it_pox)) ) {
else if ( c3y == u3r_sing_c("http-server", it_pox) ) {
u3_noun pud = tt_pox;
u3_noun p_pud, t_pud, tt_pud, q_pud, r_pud, s_pud;
c3_l sev_l, coq_l, seq_l;
@ -317,7 +317,7 @@ _reck_kick_spec(u3_pier* pir_u, u3_noun pox, u3_noun fav)
u3z(pox); u3z(fav);
return c3y;
}
else if ( c3y == u3rz_sing(u3i_string("http-client"), u3k(it_pox)) ) {
else if ( c3y == u3r_sing_c("http-client", it_pox) ) {
u3_cttp_ef_http_client(u3k(fav));
u3z(pox); u3z(fav);