sima/autoconf/
sima/hosts/i386/
sima/mudlib/
sima/mudlib/kernel/
sima/mudlib/obj/
sima/mudlib/sys/
sima/synhash/mips/
/* Copyright J"orn Rennecke 1993 - 1997 */

#include <stdio.h>
#include "config.h"
#include "common.h"
#include "interpret.h"
#include "object.h"
#include "uid.h"
#include "regexp.h"
#include "alloc.h"
#include "schedule.h"
#include "exec.h"

#define MIN_P_INT ( -1 << (sizeof(p_int) * 8 - 1) )
#define MIN_PH_INT ( -1 << (sizeof(ph_int) * 8 - 1) )

#define EMPTY_MAPPING_THRESHOLD 2000

extern struct wiz_list default_wizlist_entry;

struct hmap_x dirty_mapping_head_hash;
struct mapping dirty_mapping_head = {
		     T_MAPPING,
    /* ref        */ 1,
    /* num_values */ 0,
    /* hash/user  */ { (struct uid *)&dirty_mapping_head_hash },
    /* condensed  */ 0,
};

/* get_map_lvalue likes to read the first value */
p_int empty_cmap[2] = { COMBINE8_24(T_CONDENSED_MAP, 0), 0 };

mp_int num_mappings = 0;

union svalue last_dirty_mapping = TO_SVALUE(&dirty_mapping_head);
mp_int num_dirty_mappings = 0;

static mp_int empty_mapping_load = 2*-EMPTY_MAPPING_THRESHOLD;
static mp_int empty_mapping_base = 0;

struct mapping *stale_mappings; /* for garbage_collection */
extern struct lambda *stale_misc_closures;

svalue allocate_mapping(mp_int size, int num_values, svalue ob) {
    union svalue sv;
    union svalue m;

    /*
     * we want to use alloc_gen() / free_gen for the map_chains, thus we
     * restrict num_values accordingly (approx. 16k)
     */
    if (num_values > (0xffff - sizeof(struct map_chain))/ sizeof(union svalue))
	return SV_NULL;
    m = ALLOC(T_MAPPING, 1, sizeof(struct mapping));
    if (m.i) {
	SV_MAPPING(m).condensed = EMPTY_CMAP;
	SV_MAPPING(m).num_values = num_values;
	(SV_MAPPING(m).x.uid = SV_OBJECT(ob).x.uid->self)->total_mapping +=
	  sizeof SV_MAPPING(m);
	if (size) {
	    struct hmap_x *hm;
	    struct map_chain **mcp;

	    size |= size >> 1;
	    size |= size >> 2;
	    size |= size >> 4;
	    size |= size >> 8;
	    size |= size >> 16;
	    size >>= 1; /* This is now actually the mask, which is one lower */
	    if (size > ((MAXINT - sizeof *hm - 0x100000) / sizeof *mcp) ||
	    !(sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1,
		sizeof (char *) + sizeof *hm + sizeof *mcp * size) ).i )
	    {
		free_block(m.p, sizeof(struct mapping));
		return SV_NULL;
	    }
	    hm = (struct hmap_x *)&sv.p[sizeof (char *) -1];
	    hm->mask = size;
	    hm->used = hm->condensed_deleted = hm->ref = 0;
	    SV_MAPPING(last_dirty_mapping).x.hash->next_dirty = m;
	    last_dirty_mapping = m;
#ifdef DEBUG
	    hm->next_dirty = 0;
	    hm->deleted = 0;
#endif
	    num_dirty_mappings++;
	    SET_JOB(do_compact_mappings);
	    mcp = hm->chains;
	    do *mcp++ = 0; while (--size >= 0);
	    hm->uid = SV_MAPPING(m).x.uid;
	    SV_MAPPING(m).x.hash = hm;
	}
	num_mappings++;
    }
    return m;
}

static void remove_empty_mappings();

void _free_mapping(union svalue m) {
    struct hmap_x *hm;
    union svalue *cm;
    mp_int i, total;
    int j, num_values;

#ifdef DEBUG
    if (!SV_MAPPING(m).x.uid)
	fatal("No uid for mapping");
#endif
    num_mappings--;
    num_values = SV_MAPPING(m).num_values;
    cm = SV_MAPPING(m).condensed;
    total = CMAP_SIZE(cm);
    if (total) {
	if (!num_values) {
	    union svalue old = SV_NULL;
	    i = total - 1;
	    do {
		union svalue sv = cm[i];
		if ((sv.i & 3) == 1 && sv.p != old.p) {
		    FREE_ALLOCED_SVALUE(sv);
		    old = sv;
		}
	    } while (--i >= 0);
	} else {
	    /* FIXME: don't free multiple mentioned keys (from deletion) multiple times */
	    total = total * (1 + num_values);
	    i = total - 1;
	    do {
		union svalue sv = cm[i];
		if ((sv.i & 3) == 1) /* deleted members are equiv. 3 mod 4. */
		FREE_ALLOCED_SVALUE(sv);
	    } while (--i >= 0);
	}
	total = total * sizeof m + sizeof m;
	free_block((uint8 *)cm - sizeof(char *) + 1, total);
    }
    SV_MAPPING(m).x.uid->self->total_mapping -= sizeof SV_MAPPING(m) + total;
    hm = SV_MAPPING(m).x.hash;
    if (MAPX_TYPE(hm) >= IT_X_MAP) {
	if (MAPX_TYPE(hm) > IT_X_MAP) {
	    struct map_chain **mcp, *mc, *next;
	    union svalue next_dirty;
	    union svalue sv;

#ifdef DEBUG
	    if (hm->ref)
		fatal("Ref count in freed hash mapping: %ld\n", hm->ref);
#endif
	    mcp = hm->chains;
	    i = hm->mask + 1;
	    do {
		for (next = *mcp++; mc = next; ) {
		    j = num_values;
		    do {
			union svalue sv2 = (&mc->key)[j];
			FREE_SVALUE(sv2);
		    } while (--j >= 0);
		    next = mc->next;
		    free_gen( (char *)mc );
		}
	    } while (--i);
	    next_dirty = hm->next_dirty;
	    sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1,
		    sizeof (char *) + sizeof *hm);
	    if (!sv.i) {
		i = hm->mask;
		do {
		    hm->chains[i] = 0;
		} while (--i);
	    } else {
		free_block((char *)hm - sizeof (char *) + 1,
		  sizeof (char *) + sizeof *hm + sizeof *mcp * hm->mask);
		hm = (struct hmap_x *)&sv.p[sizeof (char *) -1];
		hm->mask = 0;
	    }
	    hm->chains[0] = 0;
	    hm->used = hm->condensed_deleted = hm->ref = 0;
	    hm->next_dirty = next_dirty;
	    SV_MAPPING(m).condensed = 0;
	    SV_MAPPING(m).x.hash = hm;
	    if ( (empty_mapping_load += 2) > 0)
		remove_empty_mappings();
	    return;
	} else {
	    free_block((uint8 *)hm - sizeof (char *) + 1,
	      sizeof(char *)+ sizeof(struct map_x));
	}
    }
    free_block((uint8 *)&SV_MAPPING(m), sizeof SV_MAPPING(m));
}

void free_empty_mapping(union svalue m) {
    struct hmap_x *hm;
    union svalue *cm;
    mp_int num_values;
    mp_int total;

#ifdef DEBUG
    if (!SV_MAPPING(m).x.uid)
	fatal("No wizlist pointer for mapping");
#endif
    num_mappings--;
    num_values = SV_MAPPING(m).num_values;
    cm = SV_MAPPING(m).condensed;
    total =  CMAP_SIZE(cm);
    if (total) {
	total =
	  sizeof (char *) + total * (1 + num_values) * sizeof(union svalue);
	free_block((uint8 *)cm - sizeof(char *) + 1, total);
    }
    SV_MAPPING(m).x.uid->self->total_mapping -= sizeof SV_MAPPING(m) + total;
    hm = SV_MAPPING(m).x.hash;
    if (MAPX_TYPE(hm) >= IT_X_MAP) {
	if (MAPX_TYPE(hm) > IT_X_MAP) {
	    struct map_chain **mcp, *mc, *next;
	    union svalue next_dirty;
	    mp_int i;
	    union svalue sv;

#ifdef DEBUG
	    if (hm->ref)
		fatal("Ref count in freed hash mapping: %ld\n", hm->ref);
#endif
	    mcp = hm->chains;
	    i = hm->mask + 1;
	    do {
		for (next = *mcp++; mc = next; ) {
		    next = mc->next;
		    free_gen( (char *)mc );
		}
	    } while (--i);
	    next_dirty = hm->next_dirty;
	    sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1,
		    sizeof (char *) + sizeof *hm);
	    if (!sv.i) {
		i = hm->mask;
		do {
		    hm->chains[i] = 0;
		} while (--i);
	    } else {
		free_block((uint8 *)hm - sizeof (char *) + 1,
		  sizeof (char *) + sizeof *hm + sizeof *mcp * hm->mask);
		hm = (struct hmap_x *)&sv.p[sizeof (char *) -1];
		hm->mask = 0;
	    }
	    hm->chains[0] = 0;
	    hm->used = hm->condensed_deleted = hm->ref = 0;
	    hm->next_dirty = next_dirty;
	    SV_MAPPING(m).condensed = 0;
	    SV_MAPPING(m).x.hash = hm;
	    if ( (empty_mapping_load += 2) > 0)
		remove_empty_mappings();
	    return;
	} else {
	    free_block((uint8 *)hm - sizeof (char *) + 1,
	      sizeof(char *)+ sizeof(struct map_x));
	}
    }
    free_block((uint8 *)&SV_MAPPING(m), sizeof SV_MAPPING(m));
}

#ifdef DEBUG
void check_dirty_mapping_list() {
    int i;                                                           
    struct mapping *m;                                               
                                                                     
    for (m = &dirty_mapping_head, i = num_dirty_mappings; --i >=0; ) {
	m = m->hash->next_dirty;
    }
    if (m != last_dirty_mapping)
	fatal("last_dirty_mapping not at end of dirty list\n");
    if (m->hash->next_dirty)
	fatal("dirty mapping list not terminated\n");
}
#endif

static void remove_empty_mappings() {
    union svalue *mp, m, last; /* mapping svalues */
    struct hmap_x *hm;

    empty_mapping_load += empty_mapping_base - num_dirty_mappings;
    empty_mapping_base = num_dirty_mappings;
    if (empty_mapping_load <= -EMPTY_MAPPING_THRESHOLD)
	return;
#ifdef DEBUG
    /* We have stored all these superflous zeroes.
     * Now check that there is one in the proper place.
     */
    if (last_dirty_mapping->hash->next_dirty != 0)
	fatal("Dirty mapping list not terminated\n");
#endif
    SV_MAPPING(last_dirty_mapping).x.hash->next_dirty = SV_NULL;
    last = TO_SVALUE(&dirty_mapping_head);
    mp = &dirty_mapping_head_hash.next_dirty;
    m = *mp;
    do {
	hm = SV_MAPPING(m).x.hash;
	if (!SV_MAPPING(m).condensed) {
	    free_block((uint8 *)&SV_MAPPING(m), sizeof SV_MAPPING(m));
	    *mp = m = hm->next_dirty;
	    free_block((uint8 *)hm -sizeof(char *) + 1,
	      sizeof (char *) + sizeof *hm + sizeof hm->chains[0] * hm->mask);
	    continue;
	}
	last = m;
	mp = &hm->next_dirty;
	m = *mp;
    } while (m.i);
    last_dirty_mapping = last;
    num_dirty_mappings -=
      empty_mapping_load + 2*EMPTY_MAPPING_THRESHOLD + empty_mapping_base >> 1;
    empty_mapping_load = 2*-EMPTY_MAPPING_THRESHOLD - empty_mapping_base;
#ifdef DEBUG
    check_dirty_mapping_list();
#endif
}

void free_protector_mapping(union svalue m) {
    struct hmap_x *hm;

    /* This is part of a T_PROTECTOR_MAPPING svalue, which is only
     * created for mappings with a hashed part, and has the ref of the
     * hashed part incremented at creation.
     */
#ifdef DEBUG
    if (!SV_MAPPING(m).x.hash || SV_MAPPING(m).x.hash->ref <= 0) {
	/* This shouldn't happen */
	printf("free_protector_mapping() : no hash %s\n",
		SV_MAPPING(m).x.hash ? "reference" : "part");
#ifdef TRACE_CODE
	{
	    extern int last_instructions(int, int, struct svalue **);

	    last_instructions(TOTAL_TRACE_LENGTH, 1, 0);
	}
#endif
	printf("free_protector_mapping() : no hash %s\n",
		SV_MAPPING(m).x.hash ? "reference" : "part");
	free_mapping(m);
    }
#endif /* DEBUG */
    hm = SV_MAPPING(m).x.hash;
    if (!--hm->ref) {
	int num_values = SV_MAPPING(m).num_values;
	struct map_chain *mc, *next;

	for (mc = hm->deleted; mc; mc = next) {
	    mp_int j;

	    j = num_values;
	    do {
		union svalue sv = (&mc->key)[j];
		FREE_SVALUE(sv);
	    } while (--j >= 0);
	    next = mc->next;
	    free_gen(mc);
	}
    }
    FREE_ALLOCED_SVALUE(m);
}

static struct hmap_x *add_hash(svalue m) {
    union svalue sv;
    struct hmap_x *hm;

    sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1, sizeof (char *) + sizeof *hm);
    if (!sv.i) {
	return 0;
    }
    hm->uid = SV_MAPPING(m).x.uid->self;
    if (MAPX_TYPE(hm) == IT_X_MAP) {
	MAPX_REF(hm) = MAPX_REF(SV_MAPPING(m).x.x);
	free_block(
	  (uint8 *)SV_MAPPING(m).x.hash-sizeof(char *)+1,
	  sizeof(char *)+ sizeof(struct map_x));
    }
    SV_MAPPING(m).x.hash = hm;
    hm->mask = hm->used = hm->condensed_deleted = 0;
    hm->chains[0] = 0;
    SV_MAPPING(last_dirty_mapping).x.hash->next_dirty = m;
    last_dirty_mapping = m;
#ifdef DEBUG
    hm->next_dirty = 0;
    hm->deleted = 0;
#endif
    hm->ref = 0;
    num_dirty_mappings++;
    SET_JOB(do_compact_mappings);
    return hm;
}

struct lvalue m_delete_marker;

union svalue *get_map_lvalue(union svalue m, union svalue map_index, int insert)
{
    mp_int size;
    svalue *cm = SV_MAPPING(m).condensed;
    struct hmap_x *hm;
    int num_values = SV_MAPPING(m).num_values;
    p_int hash;

    if (SV_IS_NUMBER(map_index)) {
	goto got_number;
    } else switch(SV_TYPE(map_index)) {
      case T_DESTRUCTED:
	map_index.i = 0;
      case T_STRING:
      case T_LSTRING:
	map_index = findstring(map_index);
	goto got_pointer;
      case T_ISTRING:
      case T_ILSTRING:
        map_index = SV_ISTRING(map_index);
      case T_GSTRING:
      case T_GLSTRING:
      case T_ARRAY:
      case T_MAPPING:
      case T_OBJECT:
      got_pointer:
      got_number:
      /* We can test equality and order on the svalue itself */
      {
	svalue first, *key;
	mp_int offset, orig_size;

	cm = SV_MAPPING(m).condensed;
	size = CMAP_SIZE(cm);
	key = cm;
	orig_size = size;
	first = *cm;
	if (!SV_IS_NUMBER(first) && !SV_STRONG_EQUALITY(SV_TYPE(first))) {
	    /* size >= 1, since the empty cmap has a number first */
	    mp_int o = size;
	    do {
		o++;
		o>>= 1;
		if (o <= size &&
		    !SV_IS_NUMBER(first = key[o-1]) ||
		    !SV_STRONG_EQUALITY(SV_TYPE(first))	)
		{
		    key += o;
		    size -= o;
		}
	    } while (o > 1);
	}
	if (size) {
	    offset = size-1;
	    offset |= offset >> 1;
	    offset |= offset >> 2;
	    offset |= offset >> 4;
	    offset |= offset >> 8;
	    offset |= offset >> 16;
	    if ( (offset = offset+1 >> 1) ) {
		svalue search = map_index;
		do {
		    if (offset >= size) continue;
		    if ( search.i >= key[offset].i ) {
			key += offset;
			size -= offset;
		    }
		} while ( (offset >>= 1) );
	    }
	    if ( map_index.i == key->i ) {
		/* found it */
		if (insert >= 0) {
#ifndef FAST_MULTIPLICATION
		    if (num_values == 1) /* speed up this case */
			return key + orig_size;
		    else
#endif/*FAST_MULTIPLICATION*/
			return key + orig_size * num_values;
		} else {
		    svalue sv;

		    hm = SV_MAPPING(m).x.hash;
		    if (MAPX_TYPE(hm) != IT_X_HMAP) {
			hm = add_hash(m);
			if (!hm)
			    return 0;
		    }
		    hm->condensed_deleted++;
		    sv = *key;
		    if (SV_IS_NUMBER(sv)) {
			sv.i = sv.i-1 | 2;
		    } else {
			FREE_ALLOCED_SVALUE(sv);
			sv.i |= 2;
			if (sv.i > key[1].i && size > 1) {
			    int i;

			    *key = key[1];
			    key[1] = sv;
			    key += orig_size;
			    for (i = num_values; --i >= 0; key++) {
				sv = *key;
				FREE_SVALUE(sv);
				*key = key[num_values];
				key[num_values].i = 0;
			    }
			    return 0;
			}
		    }
		    *key = sv;
		    return 0;
		}
	    }
	    /* not found */
	}
	hm = SV_MAPPING(m).x.hash;
	if (MAPX_TYPE(hm) != IT_X_HMAP) {
	    struct map_chain *mc;
	    mp_int i;
	    svalue sv;

     insert_first:
	    if (insert <= 0)
		return EMPTY_CMAP;
	    sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1,
	      sizeof (char *) + sizeof *hm);
	    if (!sv.i)
		return 0;
	    hm = (struct hmap_x *)(sv.p - 1 + sizeof(p_int));
	    hm->mask = hm->condensed_deleted = 0;
	    hm->ref = 0;
	    hm->used = 1;
	    hm->uid = SV_MAPPING(m).x.uid->self;
	    SV_MAPPING(last_dirty_mapping).x.hash->next_dirty = m;
	    if (MAP_HAS_X(&SV_MAPPING(m))) {
		((uint16 *)hm)[-1] = MAP_REF(&SV_MAPPING(m));
		free_block((uint8 *)SV_MAPPING(m).x.hash - sizeof (char *) + 1,
		  sizeof(char *)+ sizeof(struct map_x));
	    }
	    last_dirty_mapping = m;
#ifdef DEBUG
	    hm->next_dirty = 0;
	    hm->deleted = 0;
#endif
	    num_dirty_mappings++;
	    SET_JOB(do_compact_mappings);
	    SV_MAPPING(m).x.hash = hm;
	    mc = (struct map_chain *)alloc_gen(MAP_CHAIN_SIZE(num_values));
	    hm->chains[0] = mc;
	    if (!mc)
		return 0;
	    mc->next = 0;
	    mc->key = COPY_SVALUE(map_index);
	    i = num_values;
	    if (i) do {
		mc->data[i-1].i = 0;
	    } while (--i);
	    return mc->data;
	} else {
	    struct map_chain **mcp, *mc;
	    mp_int i;

	    hash = map_index.i;
	    hash = hash ^ hash >> 16;
	    hash = hash ^ hash >> 8;
	    i = hash & hm->mask;
	    mcp = &hm->chains[i];
	    if ((mc = *mcp)) for (;;) {
		if (mc->key.i != map_index.i) {
		    mcp = &mc->next;
		    if ((mc = *mcp)) continue;
		    break;
		}
		if (insert >= 0) {
		    return mc->data;
		} else {
		    *mcp = mc->next;
		    if (hm->ref) {
			mc->next = hm->deleted;
			hm->deleted = mc;
		    } else {
			int j;
			svalue sv;

			j = num_values;
			do {
			    sv = (&mc->key)[j];
			    FREE_SVALUE(sv);
			} while (--j >= 0);
			free_gen(mc);
		    }
		    hm->used--;
		    return 0;
		}
	    }
     insert_another:
	    if (insert <= 0)
		return EMPTY_CMAP;
	    if (hm->used & ~hm->mask<<1) {
		/* Avoid average map_chains lengths above 2 by reallocating the
		 * array of pointers
		 */
		struct hmap_x *hm2;
		mp_int mask, j;
		struct map_chain **mcp, **mcp2, *next;
		svalue sv;

		hm2 = hm;
		size = (hm->mask << 1) + 2;
		mask = size - 1;
		sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1,
		    sizeof (char *) + sizeof *hm + sizeof hm->chains[0] * mask);
		if (!sv.i)
		    return 0;
		hm = (struct hmap_x *)&sv.p[sizeof (char *) -1];
		MAPX_REF(hm) = MAPX_REF(hm2);
		*hm = *hm2;
		hm->mask = mask;
		mcp = hm->chains;
		do *mcp++ = 0; while (--size);
		mcp = hm->chains;
		mcp2 = hm2->chains;
		for (j = hm2->mask + 1; --j >= 0; ) {
		    for (mc = *mcp2++; mc; mc = next) {
			next = mc->next;
			i = mc->key.i;
			i = i ^ i >> 16;
			i = i ^ i >> 8;
			i &= mask;
			mc->next = mcp[i];
			mcp[i] = mc;
		    }
		}
		SV_MAPPING(m).x.hash = hm;
		free_block((uint8 *)hm2 - sizeof(char *) + 1,
		  sizeof(char *) + sizeof *hm2 + sizeof hm2->chains[0] * hm2->mask);
	    }
	    mc = (struct map_chain *)alloc_gen(MAP_CHAIN_SIZE(num_values));
	    if (!mc)
		return 0;
	    hm->used++;
	    i = hash & hm->mask;
	    mc->next = hm->chains[i];
	    hm->chains[i] = mc;
	    mc->key = COPY_SVALUE(map_index);
	    i = num_values;
	    if (i) do {
		mc->data[i-1].i = 0;
	    } while (--i);
	    return mc->data;
	}
	break;
      }
      {
	/* Simular code in f_member() . */
	svalue *min, *med, *lub, medv;
	p_int size;
	p_int mk0, mk1, m2, mk2, m3, mk3;
      default:
	m2 = 0;
	m3 = 0;
	goto search_value;
      case T_FLOAT:
	m2 = ~0;
	mk2 = SV_KEY(map_index)[2];
	m3 = 0;
	goto search_value;
      case T_CLOSURE:
	switch(SV_CLOSURE(map_index).g.closure_type) {
	  case CLOSURE_BOUND_LAMBDA:
	    m2 = ~0;
	    mk2 = SV_KEY(map_index)[2];
	    m3 = 0;
	    break;
	  case CLOSURE_ALIEN_LFUN:
	    m2 = S2I32(0, 0xffff);
	    mk2 = SV_KEY(map_index)[2];
	    m3 = ~0;
	    mk3 = SV_KEY(map_index)[3];
	    break;
	  case CLOSURE_LFUN:
	  case CLOSURE_IDENTIFIER:
	    m2 = S2I32(0, 0xffff);
	    mk2 = SV_KEY(map_index)[2];
	    m3 = 0;
	    break;
	  default:
	    m2 = 0;
	    m3 = 0;
	    break;
	}
	goto search_value;
      case T_QUOTED:
	/*       byte 4..7:sv   byte 2..3:quotes   byte 0:type */
	m2 = 0;
	m3 = 0;
      search_value:
	/* we exploit the fact that types with SV_STRONG_EQUALITY sort lower */
	min = cm;
	lub = cm + (size = CMAP_SIZE(cm));
	mk0 = SV_KEY(map_index)[0];
	EXTRACT_T_WORD(mk0);
	mk1 = SV_KEY(map_index)[1];
	mk2 &= m2;
	mk3 &= m3;
	while (min < lub) {
	    p_int tmp;

	    med = (svalue *)((((p_int)min + (p_int)lub) >> 1) & -sizeof *med);
	    medv = *med;
	    if ((medv.i & 3) != 1)
		goto raise_min;
	    tmp = SV_KEY(medv)[0];
	    EXTRACT_T_WORD(tmp);
	    if (mk0 == tmp) {
		tmp = SV_KEY(medv)[1];
		if (mk1 == tmp) {
		    tmp = SV_KEY(medv)[2] & m2;
		    if (mk2 == tmp) {
			tmp = SV_KEY(medv)[3] & m3;
			if (mk3 == tmp) {
			    goto found_value;
			} else if (mk3 <= tmp) {
			    goto lower_lub;
			} else {
			    goto raise_min;
			}
		    } else if (mk2 <= tmp) {
			goto lower_lub;
		    } else {
			goto raise_min;
		    }
		} else if (mk1 <= tmp) {
		    goto lower_lub;
		} else {
		    goto raise_min;
		}
	    } else if (mk0 > tmp) {
	  raise_min:
		min = med + 1;
	    } else {
	  lower_lub:
		lub = med;
	    }
	}
	/* not found in cmap */
	hm = SV_MAPPING(m).x.hash;
	if (MAPX_TYPE(hm) != IT_X_HMAP) {
	    goto insert_first;
	} else {
	    struct map_chain **mcp, *mc;
	    mp_int i;

#ifndef WORDS_BIGENDIAN
	    mk0 = (p_uint)mk0 << 8 | (p_uint)mk0 >> 24;
#endif
	    hash = mk0 ^ mk1 ^ (mk2 & m2) ^ (mk3 & m3);
	    hash = hash ^ hash >> 16;
	    hash = hash ^ hash >> 8;
	    i = hash & hm->mask;
	    mcp = &hm->chains[i];
	    if ((mc = *mcp)) for (;;) {
		if (((*SV_KEY(mc->key) & COMBINE8_8_16(T_MASK,0,0xffff)) ^ mk0) |
		     (SV_KEY(mc->key)[1] ^ mk1) ||
		     ((SV_KEY(mc->key)[2] & m2) ^ mk2) | 
		     ((SV_KEY(mc->key)[3] & m3) ^ mk3)
		
		) {
		    mcp = &mc->next;
		    if ((mc = *mcp)) continue;
		    break;
		}
		if (insert >= 0) {
		    return mc->data;
		} else {
		    *mcp = mc->next;
		    if (hm->ref) {
			mc->next = hm->deleted;
			hm->deleted = mc;
		    } else {
			int j;
			svalue sv;

			j = num_values;
			do {
			    sv = (&mc->key)[j];
			    FREE_SVALUE(sv);
			} while (--j >= 0);
			free_gen(mc);
		    }
		    hm->used--;
		    return 0;
		}
	    }
	    /* not found in hash table either */
	    goto insert_another;
	}
      found_value:
	if (insert >= 0) {
	    med += size * num_values;
	    if (med->p == TO_SVALUE(&m_delete_marker).p) {
		if (insert) {
		    svalue *svp = med, sv;
		    int i;
		    med->i = 0;
		    for (i = num_values; --i; ) {
			sv = svp[i];
			FREE_SVALUE(sv);
			svp[i].i = 0;
		    }
		} else {
		    return EMPTY_CMAP;
		}
	    }
	    return med;
	} else {
	    hm = SV_MAPPING(m).x.hash;
	    if (MAPX_TYPE(hm) != IT_X_HMAP) {
		hm = add_hash(m);
		if (!hm)
		    return 0;
	    }
	    hm->condensed_deleted++;
	    if (num_values) {
		svalue sv;

		med += size * num_values;
		sv = *med;
		FREE_SVALUE(sv);
		*med = TO_SVALUE(&m_delete_marker);
		return 0;
	    } else {
		svalue sv;

		map_index = *med;
		FREE_ALLOCED_SVALUE(map_index);
		while (med > cm && med[-1].p == map_index.p)
		    med--;
		if (med == cm || ((sv = med[-1]).i & 3) != 1 ||
		    SV_STRONG_EQUALITY(SV_TYPE(sv)))
		{
		    sv.i = (p_uint)~0 >> 1;
		}
		do {
		    *med = sv;
		    med++;
		} while (med->p == map_index.p);
		return 0;
	    }
	}
	break;
      }
    }
}

svalue copy_mapping(svalue m, svalue ob) {
    union svalue m2;
    struct hmap_x *hm, *hm2;
    union svalue *cm, *cm2;
    mp_int num_values = SV_MAPPING(m).num_values;
    mp_int size;
    mp_int i;
    svalue sv;

    cm = SV_MAPPING(m).condensed;
    size = CMAP_SIZE(cm);
    if (!size) {
	cm2 = EMPTY_CMAP;
    } else {
	union svalue *svp, *svp2;

	size = size * (num_values+1);
	sv = alloc(CMAP_HEADER(size), sizeof(char *) + size * sizeof(char *));
	size *= sizeof(union svalue);
	if (!sv.i)
	    return 0;
	cm2 = (union svalue *)(sv.p - 1 + sizeof(char *));
	*cm2 = *cm;
	for (svp = cm, svp2 = cm2, i = size; i -= sizeof *svp >= 0;) {
	    sv = *(union svalue *)((char *)svp + i);
	    if ((sv.i & 3) == 1)
		REF_INC_IN_VAR(sv);
	    *(union svalue *)((char *)svp2 + i) = sv;
	}
    }
    m2 = ALLOC(T_MAPPING, 1, sizeof SV_MAPPING(m2));
    if (!m2.i)
	return 0;
    SV_MAPPING(m2).condensed = cm2;
    SV_MAPPING(m2).num_values = num_values;
    (SV_MAPPING(m2).x.uid = SV_OBJECT(ob).x.uid->self)->total_mapping +=
      sizeof SV_MAPPING(m2) + sizeof(char*) + size;
    num_mappings++;
    hm = SV_MAPPING(m).x.hash;
    if (MAPX_TYPE(hm) == IT_X_HMAP) {
	struct map_chain **mcp, **mcp2;
	mp_int linksize;

	size = hm->mask + 1;
	sv = ALLOC_TTS(T_INTERNAL, IT_X_HMAP, 1, GEN_ALLOCED_LEN(hm));
	if (!sv.i)
	    return 0;
	hm2 = (struct hmap_x *)&sv.p[sizeof (char *) -1];
	hm2->uid = SV_MAPPING(m2).x.uid;
	SV_MAPPING(m2).x.hash = hm2;
	SV_MAPPING(last_dirty_mapping).x.hash->next_dirty = m2;
	last_dirty_mapping = m2;
	hm2->mask = hm->mask;
	hm2->used = hm->used;
	hm2->condensed_deleted = hm->condensed_deleted;
#ifdef DEBUG
	hm2->next_dirty = 0;
	hm->deleted = 0;
#endif
	hm2->ref = 0;
	num_dirty_mappings++;
	mcp = hm->chains;
	mcp2 = hm2->chains;
	linksize = MAP_CHAIN_SIZE(num_values);
	do {
	    struct map_chain *last = 0, *mc, *mc2;

	    for(mc = *mcp++; mc; mc = mc->next) {
		union svalue *svp, *svp2;

		mc2 = (struct map_chain *)alloc_gen(linksize);
		i = num_values;
		svp = &mc->key;
		svp2 = &mc2->key;
		do {
		    union svalue sv2 = *svp;
		    COPY_SVALUE_IN_VAR(sv2);
		    *svp2 = sv2;
		} while (--i >= 0);
		mc2->next = last;
		last = mc2;
	    }
	    *mcp2++ = last;
	} while (--size);
    }
    return m2;
}

svalue add_mapping(svalue m1, svalue m2, svalue ob) {
    svalue m3;
#if 1
    m3 = copy_mapping(m1, ob);
    if (m3.i)
	add_to_mapping(m3, m2);
#else
    struct hmap_x *hm;
    svalue *cm1, *cm2, *cm3;
    struct svalue *condensed_start, *condensed_end;
    mp_int num_values = SV_MAPPING(m1).num_values;
    mp_int size, size1, size2;
    mp_int misc_size;
    mp_int i;
    char **str1, **str2, **str3;
    struct svalue *svp1, *svp2, *svp3;
    mp_int u_d;
    struct svalue *data1, *data2, *data3;
    mp_int dirty;

    cm1 = SV_MAPPING(m1).condensed;
    cm2 = SV_MAPPING(m2).condensed;
    if (SV_MAPPING(m2).num_values != num_values) {
	if (!CMAP_SIZE(cm1) && MAP_X_TYPE(&SV_MAPPING(m1)) != IT_X_HMAP) {
	    return copy_mapping(m2, ob);
	} else {
	    return copy_mapping(m1, ob);
	}
    }
    misc_size = CMAP_SIZE(cm1) + CMAP_SIZE(cm2);
    if (!misc_size) {
	cm3 = EMPTY_CMAP;
    } else {
	union svalue csv;

	size = sizeof *cm3 + misc_size * (1+num_values);
	csv = alloc(CMAP_HEADER(size), sizeof(char *) + size * sizeof(char *));
	size *= sizeof(union svalue);
	if (!csv.i)
            return SV_NULL;
	cm3 = (union svalue *)(csv.p - 1 + sizeof(char *));
    }
    condensed_end = (struct svalue *)((char *)cm + size);
    cm3 = (struct condensed_mapping *)
      ( (char *)condensed_start + misc_size * (1 + num_values) );
    cm3->string_size = string_size;
    cm3->misc_size = misc_size;
    dirty = 0;
    size1 = cm1->string_size;
    size2 = cm2->string_size;
    str1 = CM_STRING(cm1);
    data1 = (struct svalue *)( (char *)str1 + size1 );
    str2 = CM_STRING(cm2);
    data2 = (struct svalue *)( (char *)str2 + size2 );
    str3 = CM_STRING(cm3);
    data3 = (struct svalue *)( (char *)str3 + string_size );
    sv1 = *svp1;
    sv2 = *svp2;
    for(;size1 && size2; svp3++) {
	if (sv1.i < sv2.i) {
	    sv3 = sv1;
	    for (i = num_values; --i >= 0; )
		*data3++ = COPY_SVALUE(*data1++);
	    sv1 = *++svp1;
	    size1 -= sizeof *svp1;
	} else {
	    if (sv1.i == sv2.i) {
		if((*sv1.i & 3) != 3) {
		    dirty++;
		}
		data1 += num_values;
		size1 -= sizeof *svp1;
	    }
	    sv3 = sv2;
	    for (i = num_values; --i >= 0; )
		*data3++ = COPY_SVALUE(*data2++);
	    sv2 = *++svp2;
	    size2 -= sizeof *svp2;
	}
	if ((sv3.i & 3) == 1)
	    COPY_SVALUE_IN_VAR(sv3);
	*svp3 = sv3;
    }
    for (j = dirty; --j >= 0; svp++) {
	svp->i = ~(p_uint)0 >> 1;
	for (i = num_values; --i >= 0; data3++)
	    data3->i = 0;
    }




    for(;size1 && size2; svp3++) {
	if (sv1.i < sv2.i) {
	    sv3 = sv1;
	    for (i = num_values; --i >= 0; )
		*data3++ = COPY_SVALUE(*data1++);
	    sv1 = *++svp1;
	    size1 -= sizeof *svp1;
	} else {
	    if (sv1.i == sv2.i) {
		if( *sv1.i & 2 ) {
		    *str3++ = sv1;
		} else {
		    dirty++;
		    *str3++ = sv1  - 1;
		}
		sv1 = *++svp1;
		for (i = num_values; --i >= 0; )
		    *data3++ = TO_SVALUE(&m_delete_marker);
		data1 += num_values;
		size1 -= sizeof *svp1;
	    }
	    sv3 = sv2;
	    for (i = num_values; --i >= 0; )
		*data3++ = COPY_SVALUE(*data2++);
	    sv2 = *++svp2;
	    size2 -= sizeof *svp2;
	}
	if ((sv3.i & 3) == 1)
	    COPY_SVALUE_IN_VAR(sv3);
	*svp3 = sv3;
    }





    if (!size1) {
	str1 = str2;
	size1 = size2;
	data1 = data2;
    }
    for (;(size1 -= sizeof *str1) >= 0;) {
	if ( !( (p_int)(*str3 = *str1++) & 1) )
	    increment_string_ref(*str3);
	str3++;
	for (i = num_values; --i >= 0; )
	    assign_svalue_no_free(data3++, data1++);
    }
    size1 = cm1->misc_size;
    size2 = cm2->misc_size;
    svp1 = CM_MISC(cm1) - 1;
    data1 = (struct svalue *)( (char *)svp1 - size1 );
    svp2 = CM_MISC(cm2) - 1;
    data2 = (struct svalue *)( (char *)svp2 - size2 );
    svp3 = CM_MISC(cm3);
    data3 = (struct svalue *)( (char *)svp3 - misc_size );
    for(;size1 && size2; ) {
	if ( !(u_d = (svp1->u.number >> 1) - (svp2->u.number >> 1)) )
	  if ( !(u_d = svp1->x.generic - svp2->x.generic) )
	    if ( !(u_d = svp1->type - svp2->type) ) {
		dirty += svp1->type != T_INVALID;
		svp1--;
		data1 -= num_values;
		size1 -= sizeof *svp1;
	    }
	if (u_d < 0) {
	    assign_svalue_no_free(--svp3, svp1--);
	    for (i = num_values; --i >= 0; )
		assign_svalue_no_free(--data3, data1--);
	    size1 -= sizeof *svp1;
	} else {
	    assign_svalue_no_free(--svp3, svp2--);
	    for (i = num_values; --i >= 0; )
		assign_svalue_no_free(--data3, data2--);
	    size2 -= sizeof *svp2;
	}
    }
    if (!size1) {
	svp1 = svp2;
	size1 = size2;
	data1 = data2;
    }
    while ( (size1 -= sizeof *svp1) >= 0) {
	assign_svalue_no_free(--svp3, svp1--);
	for (i = num_values; --i >= 0; )
	    assign_svalue_no_free(--data3, data1--);
    }
    while (data3 > condensed_start) {
	(--svp3)->type = T_INVALID;
	svp3->x.generic = MIN_PH_INT;
	svp3->u.number = MIN_P_INT;
	for (i = num_values; --i >= 0; )
	    (--data3)->type = T_INVALID;
    }
    size1 =
      (m1->hash ? dirty += m1->hash->condensed_deleted, m1->hash->used : 0) +
      (m2->hash ? dirty += m2->hash->condensed_deleted, m2->hash->used : 0) ;
    size1 += !size1 && dirty;
    if ( !(m3 = allocate_mapping(size1, num_values)) ) {
	xfree((char *)condensed_start);
	/* There's a value leak here, well, gcollect will take care of this */
	return 0;
    }
    xfree( (char *)m3->condensed );
    m3->condensed = cm3;
    if (size1)
	m3->hash->condensed_deleted = dirty;
    (m3->user = current_object->user)->mapping_total += size - sizeof *cm3;
      /*  allocate_mapping has already accounted most of the total size
       *  sizeof *m3 + sizeof(char*) + size + sizeof(char*);
       */

    if (hm = m1->hash) {
	struct map_chain **mcp;

	size = hm->mask + 1;
	mcp = hm->chains;
	do {
	    struct map_chain *mc;

	    for(mc = *mcp++; mc; mc = mc->next) {
		data1 = mc->data;
		data3 = get_map_lvalue(m3, &mc->key, 1);
		if (data3 < condensed_start || data3 >= condensed_end) {
		    for (i = num_values; --i >= 0; )
			assign_svalue(data3++, data1++);
		}
	    }
	} while (--size);
    }
    if (hm = m2->hash) {
	struct map_chain **mcp;

	size = hm->mask + 1;
	mcp = hm->chains;
	do {
	    struct map_chain *mc;

	    for(mc = *mcp++; mc; mc = mc->next) {
		data1 = mc->data;
		data2 = get_map_lvalue(m3, &mc->key, 1);
		for (i = num_values; --i >= 0; )
		    assign_svalue(data2++, data1++);
	    }
	} while (--size);
    }
#endif
    return m3;
}

void m_indices_filter(svalue key, svalue *data, uint8 *extra) {
    svalue **svpp = (svalue **)extra;

    *(*svpp)++ = COPY_SVALUE(key);
}

void walk_mapping(
    union svalue m, void (*func)(union svalue, union svalue *, uint8 *),
    char *extra)
{
    union svalue *keyp, key, *data;
    mp_int size;
    mp_int num_values;
    struct hmap_x *hm;

    num_values = SV_MAPPING(m).num_values;
    keyp = SV_MAPPING(m).condensed;
    size = CMAP_SIZE(keyp);
    data = keyp + size;
    while (--size >= 0) {
	key = keyp;
	if (key.i & 3 != 3)
	    (*func)(key, data, extra);
	data += num_values;
    }
    hm = SV_MAPPING(m).x.hash;
    if (MAPX_TYPE(hm) == IT_X_HMAP) {
	struct map_chain **mcp, *mc;

	mcp = hm->chains;
	size = hm->mask;
	do {
	    if (mc = *mcp++) {
		do {
		    (*func)(&mc->key, mc->data, extra);
		} while (mc = mc->next);
	    }
	} while (--size >= 0);
    }
}

void f_walk_mapping_filter(key, data, extra)
    svalue key, *data;
    uint8 *extra;
{
    svalue *svp;

    if (!SV_IS_NUMBER(key)) {
	if (SV_TYPE(key) == T_DESTRUCTED)
	    return;
	REF_INC_IN_VAR(key);
    }
    svp = *(svalue **)extra;
    *svp = key;
    (++svp)->lvalue = data;
    *(svalue **)extra = ++svp;
}

void f_walk_mapping_cleanup(svalue m, svalue *arg) {
    svalue *svp;
    mp_int i;

    if (arg[-1].i) {
	struct hmap_x *hm;
	int num_values;

	hm = SV_MAPPING(m).x.hash;
	num_values = SV_MAPPING(m).num_values;
	if (!--hm->ref) {
	    struct map_chain *mc, *next;
	    svalue *svp2;

	    for (mc = hm->deleted; mc; mc = next) {
		mp_int j;

		svp2 = &mc->key;
		j = num_values;
		do {
		    svalue sv = *svp2++;
		    FREE_SVALUE(sv);
		} while (--j >= 0);
		next = mc->next;
		free_gen(mc);
	    }
	}
    }
    svp = arg;
    i = arg[-2].i;
    if (i) do {
	svalue sv = *svp;
	FREE_SVALUE(sv);
	svp += 2;
    } while (--i > 0);
    x_free((char *)(arg - 2));
}

struct walk_mapping_descriptor {
    svalue *start, *end, *extra;
    p_int num_values, num_extra;
    struct lvalue *lvalues;
    struct hmap_x *hm;
    svalue result;
};

static struct control_ret f_walk_mapping_callback(svalue, struct frame *);

struct control_ret
walk_mapping_dispatch(struct frame *fp, svalue *sp, int num_arg,
		      inter_callback cb[4], int kind)
{
    extern svalue *inter_sp;

    svalue *arg, sv, *extra;
    int extra_num;
    svalue m, ob, fp_object, func;
    struct call_cache_entry *centry;
    uint8 * funstart;
    struct walk_mapping_descriptor *wmd;

    struct hmap_x *hm;
    svalue *pointers, *data;
    mp_int i, num_values;
    struct lvalue *lvp;

    arg = sp - num_arg + 1;
    m = arg[0];
    if (SV_IS_NUMBER(m) || SV_TYPE(m) != T_MAPPING) {
	struct control_ret cntret;
	bad_efun_arg(1);
	cntret.sp = sp;
	cntret.fp = fp;
	return cntret;
    }
    if (!SV_MAPPING(m).num_values) {
	struct control_ret cntret;
	cntret.sp = sp;
	cntret.fp = fp;
	(*cb[3])(m, fp);
	return cntret;
    }
    ASSIGN_EVAL_COST(&SV_OBJECT(fp_object = fp->object));
    func = arg[1];
    if (SV_IS_NUMBER(func)) {
	bad_efun_arg(2);
    } else if (SV_TYPE(func) == T_CLOSURE) {
	ob = 0;
	extra_num = num_arg - 2;
	extra = &arg[2];
    } else if (!SV_IS_STRING(func)) {
	bad_efun_arg(2);
    } else {
	struct cache_call_ret ccret;

	if (num_arg >= 3) {
	    sv = arg[2];
	    if (SV_IS_NUMBER(sv))
		bad_efun_arg(3);
	    else if (SV_TYPE(sv) == T_OBJECT)
		ob = sv;
	    else if (!SV_IS_STRING(sv) ||
	      !(inter_sp = sp, inter_fp = fp,
		ob = find_object(sv, MAX_INHERIT_DEPTH)).p )
		bad_efun_arg(3);
	    extra_num = num_arg - 3;
	    extra = &arg[3];
	} else {
	    ob = fp_object;
	    extra_num = 0;
	}
	ccret = cache_call(ob, func, fp);
	ob = ccret.u.ob;
	centry = ccret.entry;
	if (!centry) {
	    struct control_ret cntret;
	    cntret.sp = sp;
	    cntret.fp = fp;
	    (*cb[3])(m, fp);
	    return cntret;
	}
	funstart = centry->funstart;
    }
    wmd = (struct walk_mapping_descriptor *)sp;
    wmd->extra = extra;
    wmd->num_extra = extra_num;

    num_values = SV_MAPPING(m).num_values;
    wmd->num_values = num_values;
    i = CMAP_SIZE(SV_MAPPING(m).condensed);
    hm = SV_MAPPING(m).x.hash;
    if (MAPX_TYPE(hm) == IT_X_HMAP) {
	i += hm->used - hm->condensed_deleted;
	if (!num_values) {
	    hm = 0;
	} else if (!hm->ref++) {
	    hm->deleted = 0;
	}
    } else {
	hm = 0;
    }
    wmd->hm = hm;
    wmd->result = kind < 0 ?  SV_NULL :
	allocate_mapping(i, kind ? kind : num_values, fp_object);
    if (!FUNSTART2NARGS(funstart))
	cb++;
    if (!FUNSTART2NARGS(funstart) && kind < 0) {
	wmd->num_extra = i;
    } else {
	/* allocate lvalue space: svalue[2*num_values] */
	pointers = (svalue *)x_alloc( ((i + num_values) * 2) * sizeof(svalue) );
	if (!pointers) {
	    struct control_ret cntret;
	    cntret.sp = sp;
	    cntret.fp = fp;
	    return cntret;
	}
	wmd->start = wmd->end = pointers;
	walk_mapping(m, f_walk_mapping_filter, (uint8 *)&wmd->end);
	wmd->lvalues = lvp = (struct lvalue *)wmd->end;
	data = pointers->lvalue;
	while (--num_values >= 0) {
	    lvp->type = T_LVALUE;
	    lvp->ref = 255;
	    lvp->type = LVALUE_SIMPLE;
	    lvp->pad = 0;
	    lvp->lvalue = data++;
	    lvp++;
	}
    }
/* FIXME: prune num_extra / num_values */
/* FIXME: push key & data for first element */
/* FIXME: push extra */
    if (SV_TYPE(func) == T_CLOSURE) {
	sp = (svalue *)(wmd+1);
    } else {
	struct control_ret cntret;
	struct program *prog;

	cntret = make_frame((svalue *)(wmd+1), num_arg, centry->funstart);
	cntret.fp->previous = fp;
	fp = cntret.fp;
	fp->object = ob;
	fp->variable =
	  SV_OBJECT(ob).variable + centry->cache_variable_index_offset;
	prog = centry->program;
	fp->program = prog;
	fp->shared = prog->shared;
	fp->virtual.function_8 =
	  prog->virtual.function_8 + centry->cache_virtual_index_offset;
	fp->return_mode.fun = *cb;
	return cntret;
    }
}

/*
 * Simple case: no VARARGS triggered, the key argument is accepted.
 * Ignored mapping values / extra arguments can be handled here
 * (or rather need no longer be bothered about)
 * after previous pruning of num_values / num_extra.
 */
struct control_ret f_walk_mapping_callback(svalue result, struct frame *fp) {
    int nargs;
    struct walk_mapping_descriptor *wmd;
    svalue *argp, *vp, *end;
    struct control_ret ret;
    int i;
    struct lvalue *lvp;

    FREE_SVALUE(result);
    nargs = FUNSTART2NARGS(fp->funstart);
    argp = fp->arguments - nargs;
    wmd = (struct walk_mapping_descriptor *)&argp[1] - 1;
    ++argp;
    FREE_SVALUE(*argp);
    end = wmd->end;
    end -= 2;
    if (end == wmd->start) {
	fp->return_mode.i =
	  fp >= inter_stack.external ? IR_EXTERN_XF : IR_EXTERN;
    }
    *argp = *end;
    vp = end[1].lvalue;
    lvp = wmd->lvalues;
    for (i = wmd->num_values; --i >= 0;) {
	(lvp++)->lvalue = vp++;
    }
    for (i = wmd->num_extra; --i >= 0;) {
    }
    i = FUNSTART2NLOCAL(fp->funstart);
    ret.sp = CONTROL_LOCALS(ret.fp) - 1;
    if (i) do {
        (++ret.sp)->i = 0;
    } while (--i);
    ret.fp = fp;
    return ret;
}

/* special case: no arguments accepted */
struct control_ret f_walk_mapping_zcallback(svalue result, struct frame *fp)
{
    struct walk_mapping_descriptor *wmd;
    struct control_ret ret;
    int i;

    FREE_SVALUE(result);
    wmd = (struct walk_mapping_descriptor *)&fp->arguments[1] - 1;
    if (!--wmd->num_extra) {
	fp->return_mode.i =
	  fp >= inter_stack.external ? IR_EXTERN_XF : IR_EXTERN;
    }
    i = FUNSTART2NLOCAL(fp->funstart);
    ret.sp = CONTROL_LOCALS(fp) - 1;
    if (i) do {
        (++ret.sp)->i = 0;
    } while (--i);
    ret.fp = fp;
    return ret;
}
struct control_ret f_walk_mapping_vcallback(svalue result, struct frame *fp) {
}
struct control_ret f_walk_mapping_nilcallback(svalue m, struct frame *fp) {
}

struct control_ret f_walk_mapping(struct frame *fp, svalue *sp, int num_arg) {
    static inter_callback cb[] = {
      f_walk_mapping_callback, f_walk_mapping_zcallback,
      f_walk_mapping_vcallback, f_walk_mapping_nilcallback
    };
    return walk_mapping_dispatch(fp, sp, num_arg, cb, -1);
}

svalue *f_m_indices(svalue *sp, struct frame *fp) {
    svalue m, a, *svp;
    struct hmap_x *hm;
    mp_int size;

    m = *sp;
    if (SV_TYPE(m) != T_MAPPING) {
	bad_efun_arg(1);
    } else {
	hm = SV_MAPPING(m).x.hash;
	size = CMAP_SIZE(SV_MAPPING(m).condensed) +
	  (MAPX_TYPE(hm) == IT_X_HMAP ? hm->used  - hm->condensed_deleted : 0);
	if ((a = allocate_array(size, SV_OBJECT(fp->object).x.uid->self)).p) {
	    svp = SV_ARRAY(a).member;
	    walk_mapping(m, m_indices_filter, (uint8 *)&svp);
	    *sp = a;
	}
    }
    return sp;
}

static void add_to_mapping_filter(
    svalue key, register svalue *data, uint8 *extra)
{
    register volatile union svalue (*data2);
    register int i;

    data2 = get_map_lvalue((union svalue)extra, key, 1);
    for (i = (SV_MAPPING((union svalue)extra)).num_values; --i >= 0;)
    {
	register union svalue sv;

	sv = data2[i];
	FREE_SVALUE(sv);
	sv = data[i];
	COPY_SVALUE_IN_VAR(sv);
	data2[i] = sv;
    }
}

void sub_from_mapping_filter(svalue key, svalue *data, uint8 *extra) {
    remove_mapping((/* mapping */ svalue)extra, key);
}

void add_to_mapping(svalue m1, svalue m2) {
    if (SV_MAPPING(m2).num_values != SV_MAPPING(m1).num_values) {
	svalue *cm1, *cm2;

	cm2 = SV_MAPPING(m2).condensed;
	if (!CMAP_SIZE(cm2) && !MAP_X_TYPE(&SV_MAPPING(m2)) == IT_X_HMAP) {
	    SV_MAPPING(m2).num_values = SV_MAPPING(m1).num_values;
	} else {
	    cm1 = SV_MAPPING(m1).condensed;
	    if (!CMAP_SIZE(cm1) && !MAP_X_TYPE(&SV_MAPPING(m1)) == IT_X_HMAP) {
		SV_MAPPING(m1).num_values = SV_MAPPING(m2).num_values;
	    } else {
		return;
	    }
	}
    }
    walk_mapping(m2, add_to_mapping_filter, m1.p);
}

svalue subtract_mapping(svalue minuend, svalue subtrahend, svalue ob) {
    /* this could be done faster, especially if there the mappings are
     * mainly condensed. On the other hand, the priority of fast mapping
     * subtraction is unknown.
     */
    minuend = copy_mapping(minuend, ob);
    walk_mapping(subtrahend, sub_from_mapping_filter, minuend.p);
    return minuend;
}

/*
 * Simple case: no VARARGS triggered, the key argument is accepted.
 * Ignored mapping values / extra arguments can be handled here
 * (or rather need no longer be bothered about)
 * after previous pruning of num_values / num_extra.
 */
struct control_ret f_filter_mapping_callback(svalue result, struct frame *fp) {
    int nargs;
    struct walk_mapping_descriptor *wmd;
    svalue *argp, *end;
    struct control_ret ret;
    int i;

    nargs = fp->funstart[-2];
    argp = fp->arguments - nargs;
    wmd = (struct walk_mapping_descriptor *)&argp[1] - 1;
    ++argp;
    end = wmd->end;
    do {
	svalue m, *svp, *vp;
	if (!SV_IS_NUMBER(result))
	    FREE_ALLOCED_SVALUE(result);
	else if (!result.i)
	    break;
	m = wmd->result;
	svp = get_map_lvalue(m, end[0], 1);
	vp = end[1].lvalue;
	for (i = wmd->num_values; --i >= 0;) {
	    svalue sv = *vp++;
	    COPY_SVALUE_IN_VAR(sv);
	    *svp++ = sv;
	}
    } while (0);
    FREE_SVALUE(*argp);
    end -= 2;
    if (end == wmd->start) {
	fp->return_mode.i =
	  fp >= inter_stack.external ? IR_EXTERN_XF : IR_EXTERN;
    }
    *argp = *end;
    for (i = wmd->num_extra; --i >= 0;) {
    }
    i = FUNSTART2NLOCAL(fp->funstart);
    ret.sp = CONTROL_LOCALS(ret.fp) - 1;
    if (i) do {
        (++ret.sp)->i = 0;
    } while (--i);
    ret.fp = fp;
    return ret;
}

/* special case: no arguments accepted */
struct control_ret f_filter_mapping_zcallback(svalue result, struct frame *fp)
{
    struct walk_mapping_descriptor *wmd;
    struct control_ret ret;
    int i;

    wmd = (struct walk_mapping_descriptor *)&fp->arguments[1] - 1;
/*FIXME:filter!*/
    if (0/*FIXME*/) {
	fp->return_mode.i =
	  fp >= inter_stack.external ? IR_EXTERN_XF : IR_EXTERN;
    }
    i = FUNSTART2NLOCAL(fp->funstart);
    ret.sp = CONTROL_LOCALS(fp) - 1;
    if (i) do {
        (++ret.sp)->i = 0;
    } while (--i);
    ret.fp = fp;
    return ret;
}
struct control_ret f_filter_mapping_vcallback(svalue result, struct frame *fp) {
}
struct control_ret f_filter_mapping_nilcallback(svalue m, struct frame *fp) {
}

/* leave a filtered mapping on the stack */
struct control_ret f_filter_mapping(struct frame *fp, svalue *sp, int num_arg) {
    static inter_callback cb[] = {
      f_filter_mapping_callback, f_filter_mapping_zcallback,
      f_filter_mapping_vcallback, f_filter_mapping_nilcallback
    };
    return walk_mapping_dispatch(fp, sp, num_arg, cb, 1);
}

#if 0
/* leave result mapping on the stack */
struct control_ret f_map_mapping(struct frame *fp, svalue *sp, int num_arg) {
    static inter_callback cb[] = {
      f_map_mapping_callback, f_map_mapping_zcallback,
      f_map_mapping_vcallback, f_map_mapping_nilcallback
    };
    return walk_mapping_dispatch(fp, sp, num_arg, cb, 0);
}
struct svalue *map_mapping (sp, num_arg)
    struct svalue *sp;
    int num_arg;
{
    extern struct svalue *inter_sp;

    struct svalue *arg;
    struct mapping *m;
    char *func;
    struct object *ob;
    struct svalue *extra;
    struct vector *vec;
    int num_values;
    int extra_num;
    int i;
    struct svalue *key;

    arg = sp - num_arg + 1;
    inter_sp = sp;
    if (arg[0].type != T_MAPPING)
	bad_xefun_vararg(1, sp);
    if (arg[1].type == T_CLOSURE) {
	ob = 0;
	func = (char *)(arg + 1);
	extra = &arg[2];
	extra_num = num_arg - 2;
    } else if (arg[1].type != T_STRING) {
	bad_xefun_vararg(2, sp);
    } else {
	if (num_arg < 3) {
	    ob = current_object;
	    /* let the compiler mourn: we need no extra. */
	    extra_num = 0;
	} else {
	    if (arg[2].type == T_OBJECT)
		ob = arg[2].u.ob;
	    else if (arg[2].type != T_STRING ||
		     !(inter_sp = sp, ob = find_object(arg[2].u.string)) )
		bad_xefun_vararg(3, sp);
	    extra = &arg[3];
	    extra_num = num_arg - 3;
	}
	func = arg[1].u.string;
    }
    m = arg[0].u.map;
    check_map_for_destr(m);
    assign_eval_cost();

    num_values = m->num_values;
    vec = m_indices(m); /* might cause error */
    (++sp)->type = T_POINTER;
    sp->u.vec = vec;
    m = allocate_mapping((i = VEC_SIZE(vec)), 1);
    if (!m) {
	inter_sp = sp;
	error("Out of memory\n");
    }
    (++sp)->type = T_MAPPING;
    sp->u.map = m;
    key = vec->item;
    for (; --i >= 0; key++) {
	struct svalue *v;

	assign_svalue_no_free((inter_sp = sp + 1), key);
	push_svalue_block( extra_num, extra);
	v = get_map_lvalue(m, key, 1);
	if (ob) {
	    struct svalue *data;

	    if (ob->flags & O_DESTRUCTED)
		error("Object used by map_mapping destructed");
	    data = apply( func, ob, 1 + extra_num);
	    if (data) {
		transfer_svalue_no_free(v, data);
		data->type = T_INVALID;
	    }
	} else {
	    call_lambda( (struct svalue *)func, 1 + extra_num);
	    transfer_svalue_no_free(v, inter_sp--);
	}
    }
    i = num_arg;
    do
	free_svalue(--sp);
    while (--i >= 0);
    sp->type = T_MAPPING;
    sp->u.map = m;
    return sp;
}
#endif

#if 0
/* Used mergesort to sort hashed string and misc keys, respectively.
 * Portions that won't make up the current power of two are processed
 * first, to save tests.
 * When all previously hashed keys are sorted, they are merged with the
 * already condensed part.
 */
void compact_mappings(num)
    mp_int num;
{
    extern struct svalue last_indexing_protector;
    extern int malloc_privilege;

    struct mapping *m;

    malloc_privilege = MALLOC_SYSTEM;
    if (last_indexing_protector.type == T_PROTECTOR_MAPPING) {
	/* There is a slight chance that free_protector_mapping causes
	 * remove_empty_mappings(), and thus changes num_dirty_mappings.
	 */
	free_protector_mapping(last_indexing_protector.u.map);
	last_indexing_protector.type = T_NUMBER;
    }
    if (num >= num_dirty_mappings) {
	num = num_dirty_mappings;
	last_dirty_mapping = &dirty_mapping_head;
	CLEAR_JOB(do_compact_mappings);
    }
    num_dirty_mappings -= num;
    m = dirty_mapping_head_hash.next_dirty;
    while (--num >= 0) {
	mp_int count1, count2;
	struct hash_mapping *hm;
	struct condensed_mapping *cm, *cm2;
	int num_values;
	/* hooks to hang the chains on  :-) */
	struct map_chain *string_hook1, *string_hook2,
			 *misc_hook1,   *misc_hook2;
	mp_int string_used, misc_used, runlength;
	struct map_chain **mcpp, *mcp, *next;
	struct map_chain *last_string, *last_misc;

#ifdef DEBUG
	if (!m->user)
	    fatal("No wizlist pointer for mapping\n");
#endif
	m->ref++; /* prevent freeing while using in case of recursive
		   * mappings referenced by a deleted value
		   */
	cm = m->condensed;
	hm = m->hash;
#ifdef DEBUG
	if (hm->ref) {
#if 1
	    fatal("compact_mappings(): remaining ref count %ld!\n", hm->ref);
#else
	    struct svalue *svp;
	    int i;

	    printf("compact_mappings(): remaining ref count %ld!\n", hm->ref);
#ifdef TRACE_CODE
	    {
		extern int last_instructions(int, int, struct svalue **);

		last_instructions(TOTAL_TRACE_LENGTH, 1, 0);
	    }
#endif
	    printf("compact_mappings(): remaining ref count! %ld\n", hm->ref);
	    if (hm->ref > 0) {
		for (mcp = hm->deleted; mcp; mcp = next) {
		    next = mcp->next;
		    svp = &mcp->key;
		    i = num_values;
		    do {
			free_svalue(svp++);
		    } while (--i >= 0);
		    xfree( (char *)mcp );
		}
	    }
#endif
	}
#endif /* DEBUG */
	if (!hm->used && !hm->condensed_deleted) {
	    if (!m->condensed) {
		xfree((char *)m);
		empty_mapping_load -= 2;
	    } else {
		m->hash = 0;
		/* the ref count has been incremented above; on the other
		 * hand, the last real reference might have gone with the
		 * deleted keys.
		 */
		free_mapping(m);
	    }
	    m = hm->next_dirty;
	    xfree( (char *)hm );
	    continue;
	}
	num_values = m->num_values;
	mcpp = hm->chains;
	count1 = hm->mask;
	string_hook1 = string_hook2 = 0;
	misc_hook1 = misc_hook2 = 0;
	misc_used = 0;
	last_string = last_misc = 0;
	do {
	    mcp = *mcpp++;
	    while (mcp) {
		next = mcp->next;

		if (mcp->key.type != T_STRING) {
		    if (last_misc) {
			p_int d;

			if ( !(d = (last_misc->key.u.number >> 1) -
				   (mcp->key.u.number >> 1) ) )
			  if ( !(d = last_misc->key.x.generic -
				     mcp->key.x.generic ) )
			    d = last_misc->key.type - mcp->key.type;
			if (d > 0) {
			    last_misc->next = misc_hook1;
			    mcp->next = last_misc;
			    misc_hook1 = misc_hook2;
			    misc_hook2 = mcp;
			} else {
			    mcp->next = misc_hook1;
			    last_misc->next = mcp;
			    misc_hook1 = misc_hook2;
			    misc_hook2 = last_misc;
			}
			misc_used += 2;
			last_misc = 0;
		    } else {
			last_misc = mcp;
		    }
		} else {
		    if (last_string) {
			if (last_string->key.u.string > mcp->key.u.string) {
			    last_string->next = string_hook1;
			    mcp->next = last_string;
			    string_hook1 = string_hook2;
			    string_hook2 = mcp;
			} else {
			    mcp->next = string_hook1;
			    last_string->next = mcp;
			    string_hook1 = string_hook2;
			    string_hook2 = last_string;
			}
			last_string = 0;
		    } else {
			last_string = mcp;
		    }
		}
		mcp = next;
	    }
	} while (--count1 >= 0);
	if (last_string) {
	    last_string->next = string_hook1;
	    string_hook1 = last_string;
	}
	if (last_misc) {
	    misc_used++;
	    last_misc->next = misc_hook1;
	    misc_hook1 = last_misc;
	}
	string_used = hm->used - misc_used;
	for (runlength = 2; runlength < string_used; runlength <<= 1) {
	    struct map_chain *out_hook1, *out_hook2, **out1, **out2;

	    count1 = string_used & runlength-1;
	    count2 = string_used & runlength;
	    if (!count1) {
		out2 = &out_hook1;
		*out2 = string_hook2;
		while (--count2 >= 0) {
		    out2 = &(*out2)->next;
		}
		string_hook2 = *out2;
		count1 = count2 = runlength;
		out1 = &out_hook2;
	    } else if (!count2) {
		out2 = &out_hook1;
		*out2 = string_hook1;
		do {
		    out2 = &(*out2)->next;
		} while (--count1);
		string_hook1 = *out2;
		count1 = count2 = runlength;
		out1 = &out_hook2;
	    } else {
		out1 = &out_hook1;
		out2 = &out_hook2;
	    }
	    while (string_hook1) {
		while (1) {
		    if (string_hook2->key.u.string <
			string_hook1->key.u.string)
		    {
			*out1 = string_hook2;
			out1 = &string_hook2->next;
			string_hook2 = *out1;
			if (!--count2) {
			    *out1 = string_hook1;
			    do {
				out1 = &(*out1)->next;
			    } while (--count1);
			    string_hook1 = *out1;
			    break;
			}
		    } else {
			*out1 = string_hook1;
			out1 = &string_hook1->next;
			string_hook1 = *out1;
			if (!--count1) {
			    *out1 = string_hook2;
			    do {
				out1 = &(*out1)->next;
			    } while (--count2);
			    string_hook2 = *out1;
			    break;
			}
		    }
		}
		{
		    struct map_chain **temp;

		    temp = out1;
		    out1 = out2;
		    out2 = temp;
		}
		count1 = count2 = runlength;
	    }
	    *out1 = 0;
	    *out2 = 0;
	    string_hook1 = out_hook1;
	    string_hook2 = out_hook2;
	}
	if (!string_hook1)
	    string_hook1 = string_hook2;
	for (runlength = 2; runlength < misc_used; runlength <<= 1) {
	    struct map_chain *out_hook1, *out_hook2, **out1, **out2;

	    count1 = misc_used & runlength-1;
	    count2 = misc_used & runlength;
	    if (!count1) {
		out2 = &out_hook1;
		*out2 = misc_hook2;
		while (--count2 >= 0) {
		    out2 = &(*out2)->next;
		}
		misc_hook2 = *out2;
		count1 = count2 = runlength;
		out1 = &out_hook2;
	    } else if (!count2) {
		out2 = &out_hook1;
		*out2 = misc_hook1;
		do {
		    out2 = &(*out2)->next;
		} while (--count1);
		misc_hook1 = *out2;
		count1 = count2 = runlength;
		out1 = &out_hook2;
	    } else {
		out1 = &out_hook1;
		out2 = &out_hook2;
	    }
	    while (misc_hook1) {
		while (1) {
		    p_int d;

		    if (!(d = (misc_hook2->key.u.number >> 1) -
			      (misc_hook1->key.u.number >> 1) ))
		      if (!(d = misc_hook2->key.x.generic -
				misc_hook1->key.x.generic))
			  d = misc_hook2->key.type -
			      misc_hook1->key.type;
		    if (d < 0) {
			*out1 = misc_hook2;
			out1 = &misc_hook2->next;
			misc_hook2 = *out1;
			if (!--count2) {
			    *out1 = misc_hook1;
			    do {
				out1 = &(*out1)->next;
			    } while (--count1);
			    misc_hook1 = *out1;
			    break;
			}
		    } else {
			*out1 = misc_hook1;
			out1 = &misc_hook1->next;
			misc_hook1 = *out1;
			if (!--count1) {
			    *out1 = misc_hook2;
			    do {
				out1 = &(*out1)->next;
			    } while (--count2);
			    misc_hook2 = *out1;
			    break;
			}
		    }
		}
		{
		    struct map_chain **temp;

		    temp = out1;
		    out1 = out2;
		    out2 = temp;
		}
		count1 = count2 = runlength;
	    }
	    *out1 = 0;
	    *out2 = 0;
	    misc_hook1 = out_hook1;
	    misc_hook2 = out_hook2;
	}
	if (!misc_hook1)
	    misc_hook1 = misc_hook2;
	{
	    /* merge old condensed part with sorted lists */
	    mp_int misc_deleted;
	    mp_int string_total, misc_total;
	    char *condensed_start;
	    char **str1, **str2;
	    struct svalue *key1, *key2;
	    struct svalue *data1, *data2;
	    char *cm1_end, *cm2_end;

	    misc_deleted = 0;
	    if (hm->condensed_deleted) {
		struct svalue *svp;
		mp_int size;

		svp = CM_MISC(cm);
		size = cm->misc_size;
		while ( (size -= sizeof(struct svalue)) >= 0) {
		    if ( (--svp)->type == T_INVALID )
			misc_deleted++;
		}
	    }
	    string_total = string_used + cm->string_size/sizeof(char *) -
			(hm->condensed_deleted - misc_deleted);
	    misc_total = misc_used + cm->misc_size/sizeof(struct svalue) -
			misc_deleted;
	    condensed_start = xalloc(sizeof *cm2 +
		(string_total+misc_total)*sizeof(struct svalue)*(num_values+1)-
		string_total * (sizeof(struct svalue)-sizeof(char *))
	    );
	    cm2 = (struct condensed_mapping *)
		   (condensed_start +
		    misc_total * (num_values+1) * sizeof(struct svalue) );
	    cm2->string_size = string_total * sizeof(char*);
	    cm2->misc_size = misc_total * sizeof(struct svalue);
	    str1 = CM_STRING(cm);
	    data1 = (struct svalue *)((char *)str1 + cm->string_size);
	    str2 = CM_STRING(cm2);
	    data2 = (struct svalue *)((char *)str2 + cm2->string_size);
	    count1 = cm->string_size;
	    while (count1 && (p_int)*str1 & 1) {
		int i;

		i = num_values;
		while (--i >= 0) {
		    free_svalue(data1++);
		}
		str1++;
		count1 -= sizeof(char *);
	    }
	    if (string_hook1 && count1) {
		while (1) {
		    if (string_hook1->key.u.string < *str1) {
			struct map_chain *temp;
			struct svalue *data;
			int i;

			temp = string_hook1;
			*str2++ = temp->key.u.string;
			data = temp->data;
			i = num_values;
			while (--i >= 0) {
			    *data2++ = *data++;
			}
			string_hook1 = temp->next;
			xfree( (char *)temp );
			if (!string_hook1)
			    break;
		    } else {
			int i;

			*str2++ = *str1++;
			i = num_values;
			while (--i >= 0) {
			    *data2++ = *data1++;
			}
			if ( !(count1 -= sizeof(char*)) )
			    break;
			if ((p_int)*str1 & 1) {
			    do {
				i = num_values;
				while (--i >= 0) {
				    free_svalue(data1++);
				}
				str1++;
				if ( !(count1 -= sizeof(char*)) )
				    break;
			    } while ((p_int)*str1 & 1);
			    if (!count1)
				break;
			}
		    }
		}
	    }
	    if (count1) {
		while (1) {
		    int i;

		    *str2++ = *str1++;
		    i = num_values;
		    while (--i >= 0) {
			*data2++ = *data1++;
		    }
		    if ( !(count1 -= sizeof(char*)) )
			break;
		    if ((p_int)*str1 & 1) {
			do {
			    i = num_values;
			    while (--i >= 0) {
				free_svalue(data1++);
			    }
			    str1++;
			    if ( !(count1 -= sizeof(char*)) )
				break;
			} while ((p_int)*str1 & 1);
			if (!count1)
			    break;
		    }
		}
	    } else {
		while (string_hook1) {
		    struct map_chain *temp;
		    struct svalue *data;
		    int i;

		    temp = string_hook1;
		    *str2++ = temp->key.u.string;
		    data = temp->data;
		    i = num_values;
		    while (--i >= 0) {
			*data2++ = *data++;
		    }
		    string_hook1 = temp->next;
		    xfree( (char *)temp );
		}
	    }
	    cm1_end = (char *)data1;
	    cm2_end = (char *)data2;
	    key1 = CM_MISC(cm);
	    data1 = (struct svalue *)((char *)key1 - cm->misc_size);
	    key2 = CM_MISC(cm2);
	    data2 = (struct svalue *)((char *)key2 - cm2->misc_size);
	    count1 = cm->misc_size;
	    while (count1 && key1[-1].type == T_INVALID) {
		int i;

		key1--;
		i = num_values;
		while (--i >= 0) {
		    free_svalue(--data1);
		}
		count1 -= sizeof(struct svalue);
	    }
	    if (misc_hook1 && count1) {
		while (1) {
		    p_int d;

		    if (!(d = (misc_hook1->key.u.number >> 1) -
			      (key1[-1].u.number >> 1) ))
		      if (!(d = misc_hook1->key.x.generic - key1[-1].x.generic))
			  d = misc_hook1->key.type - key1[-1].type;
		    if (d < 0) {
			struct map_chain *temp;
			struct svalue *data;
			int i;

			temp = misc_hook1;
			*--key2 = temp->key;
			data = temp->data + num_values;
			i = num_values;
			while (--i >= 0) {
			    *--data2 = *--data;
			}
			misc_hook1 = temp->next;
			xfree( (char *)temp );
			if (!misc_hook1)
			    break;
		    } else {
			int i;

			*--key2 = *--key1;
			i = num_values;
			while (--i >= 0) {
			    *--data2 = *--data1;
			}
			if (! (count1 -= sizeof(struct svalue)) )
			    break;
			if (key1[-1].type == T_INVALID) {
			    do {
				key1--;
				i = num_values;
				while (--i >= 0) {
				    free_svalue(--data1);
				}
				if (! (count1 -= sizeof(struct svalue)) )
				    break;
			    } while (key1[-1].type == T_INVALID);
			    if (!count1)
				break;
			}
		    }
		}
	    }
	    if (count1) {
		while (1) {
		    int i;

		    *--key2 = *--key1;
		    i = num_values;
		    while (--i >= 0) {
			*--data2 = *--data1;
		    }
		    if (! (count1 -= sizeof(struct svalue)) )
			break;
		    if (key1[-1].type == T_INVALID) {
			do {
			    key1--;
			    i = num_values;
			    while (--i >= 0) {
				free_svalue(--data1);
			    }
			    if (! (count1 -= sizeof(struct svalue)) )
				break;
			} while (key1[-1].type == T_INVALID);
			if (!count1)
			    break;
		    }
		}
	    } else {
		while (misc_hook1) {
		    struct map_chain *temp;
		    struct svalue *data;
		    int i;

		    temp = misc_hook1;
		    *--key2 = temp->key;
		    data = temp->data + num_values;
		    i = num_values;
		    while (--i >= 0) {
			*--data2 = *--data;
		    }
		    misc_hook1 = temp->next;
		    xfree( (char *)temp );
		}
	    }
	    m->user->mapping_total +=
		(cm2_end - (char *)data2) -
		(cm1_end - (char *)data1);
	    xfree( (char *)(data1) ); /* free old condensed mapping part */
	} /* end of merge block */
	m->condensed = cm2;
	m->hash = 0;
	free_mapping(m);
	m = hm->next_dirty;
	xfree( (char *)hm );
    }
    dirty_mapping_head_hash.next_dirty = m;
}
#endif

void do_compact_mappings() {
    malloc_privilege = MALLOC_SYSTEM;
    compact_mappings(num_dirty_mappings+80 >> 5);
    malloc_privilege = MALLOC_USER;
    /* no EXTRA_JOBS() call here because we want lazy mapping compacting */
}

#if 0
mp_int total_mapping_size() {
    extern struct wiz_list *all_wiz;

    struct wiz_list *wl;
    mp_int total;

    total = default_wizlist_entry.mapping_total;
    for (wl = all_wiz; wl; wl = wl->next) {
	total += wl->mapping_total;
    }
    return total;
}

#if 0
void check_dirty(avoid)
    int avoid;
{
    struct mapping *m;
    mp_int i;

    m = dirty_mapping_head_hash.next_dirty;
    for (i = num_dirty_mappings - avoid; --i >= 0; m = m->hash->next_dirty) {
	struct hash_mapping *hm = m->hash;
	mp_int j;
	struct map_chain **mcp, *mc;

	j = hm->mask;
	mcp = hm->chains;
	do {
	    mc = *mcp++;
	    while (mc) {
		if ((p_int)mc & 0xff000003) /* The mask is machine dependent. */
		    fatal("check_dirty\n");
		mc = mc->next;
	    }
	} while (--j >= 0);
    }
}
#endif

struct set_mapping_user_locals {
    int num_values;
    struct object *owner;
    struct svalue *hairy; /* changing keys */
};

void set_mapping_user_filter(key, data, extra)
    struct svalue *key, *data;
    char *extra;
{
    int i;
    struct set_mapping_user_locals *locals;
    struct object *owner;

    locals = (struct set_mapping_user_locals *)extra;
    owner = locals->owner;
    if (key->type == T_CLOSURE && !CLOSURE_MALLOCED(key->x.closure_type)) {
	*locals->hairy++ = *key;
    } else {
	set_svalue_user(key, owner);
    }
    for (i = locals->num_values; --i >= 0;) {
	set_svalue_user(data++, owner);
    }
}

void set_mapping_user(m, owner)
    struct mapping *m;
    struct object *owner;
{
    struct condensed_mapping *cm;
    int num_values;
    mp_int total;
    struct wiz_list *user;
    struct set_mapping_user_locals locals;
    struct svalue *first_hairy;
    mp_int i;

    num_values = m->num_values;
    cm = m->condensed;
    total =
      sizeof *m + 4 + sizeof *cm + 4 +
        ( cm->string_size * (sizeof(struct svalue)/sizeof(char*)) +
	  cm->misc_size) * (1 + num_values) -
	cm->string_size * (sizeof(struct svalue)/sizeof(char *) - 1);
    m->user->mapping_total -= total;
    user = owner->user;
    m->user = user;
    m->user->mapping_total += total;
    locals.owner = owner;
    locals.num_values = num_values;
    locals.hairy = first_hairy = (struct svalue *)alloca(cm->misc_size);
    walk_mapping(m, set_mapping_user_filter, (char *)&locals);
    for (i = locals.hairy - first_hairy; --i >= 0; first_hairy++) {
	struct svalue new_key, *dest, *source;
	mp_int j;

	assign_svalue_no_free(&new_key, first_hairy);
	set_svalue_user(&new_key, owner);
	dest = get_map_lvalue(m, &new_key, 1);
	free_svalue(&new_key);
	source = get_map_lvalue(m, first_hairy, 0);
	if (num_values)
	    memcpy((char *)dest, (char *)source, num_values * sizeof *dest);
	for (j = num_values; --j >= 0; source++)
	    source->type = T_INVALID;
	remove_mapping(m, first_hairy);
    }
}

#ifdef MALLOC_smalloc
void count_ref_in_mapping(m)
    struct mapping *m;
{
    extern struct object *gc_obj_list_destructed;

    char **str;
    struct svalue *svp, *data;
    mp_int size;
    mp_int num_values;
    int any_destructed = 0;

    num_values = m->num_values;
    str = CM_STRING(m->condensed);
    size = m->condensed->string_size;
    while ( (size -= sizeof(char *)) >= 0) {
	count_ref_from_string(*str++);
    }
    data = (struct svalue *)str;
    count_ref_in_vector(
      (struct svalue *)str,
      m->condensed->string_size / sizeof *str * num_values
    );
    svp = CM_MISC(m->condensed);
    size = m->condensed->misc_size;
    while ( (size -= sizeof(struct svalue)) >= 0) {
	--svp;
	if (svp->type == T_OBJECT && svp->u.ob->flags & O_DESTRUCTED ||
	    ( svp->type == T_CLOSURE &&
	      ( CLOSURE_MALLOCED(svp->x.closure_type) ?
		  ( svp->x.closure_type != CLOSURE_UNBOUND_LAMBDA &&
		    svp->u.lambda->ob->flags & O_DESTRUCTED ) :
		  svp->u.ob->flags & O_DESTRUCTED ) ) )
	{
	    /* This key is / is bound to a destructed object. The entry has to
	     * be deleted (later).
	     */
	    if (svp->type == T_CLOSURE &&
		svp->x.closure_type == CLOSURE_BOUND_LAMBDA)
	    {
		/* We don't want changing keys, even if they are still valid
		 * unbound closures
		 */
		struct lambda *l = svp->u.lambda;

		svp->x.closure_type = CLOSURE_LAMBDA;
		svp->u.lambda = l->function.lambda;
		if (!l->ref) {
		    l->function.lambda->ob = l->ob;
		    l->ref = -1;
		    l->ob = (struct object *)stale_misc_closures;
		    stale_misc_closures = l;
		} else {
		    l->function.lambda->ob = gc_obj_list_destructed;
		}
	    }
	    count_ref_in_vector(svp, 1);
	    if (svp->type == T_CLOSURE) {
		/* *svp has been transformed into an efun closure bound
		 * to the master
		 */
		svp->u.ob->ref--;
	    }
	    svp->type = T_INVALID;
	    if (!any_destructed) {
		any_destructed = 1;
		/* Might be a small mapping. Don't malloc, it might get too
		 * much due to the global scope of garbage_collection.
		 * Since there was a previous
		 * compact_mappings(num_dirty_mappings) , the hash field is
		 * known to be 0.
		 */
		m->hash = (struct hash_mapping *)stale_mappings;
		stale_mappings = m;
		/* We are going to use free_svalue() later to get rid of the
		 * data asscoiated with the keys. This data might reference
		 * mappings with destructed keys... Thus, we must prevent
		 * free_mapping() to look at the hash field.
		 */
		m->ref++;
	    }
	} else {
	    count_ref_in_vector(svp, 1);
	}
    }
    size = m->condensed->misc_size * num_values;
    count_ref_in_vector(
      (struct svalue *)((char *)svp - size),
      size / sizeof *svp
    );
}

void clean_stale_mappings() {
    struct mapping *m, *next;

    for (m = stale_mappings; m; m = next) {
	struct condensed_mapping *cm, *cm2;
	char *cm2_start;
	mp_int size, data_size, deleted_size, preserved_size;
	mp_int i, num_deleted = 0, num_values;
	struct svalue *svp, *svp2, *data, *data2;

	next = (struct mapping *)m->hash;
	m->hash = 0;
	num_values = m->num_values;
	cm = m->condensed;
	svp = CM_MISC(cm);
	i = size = cm->misc_size;
	while ( (i -= sizeof(struct svalue)) >= 0) {
	    if ( (--svp)->type == T_INVALID)
		num_deleted++;
	}
	data_size = size * num_values;
	deleted_size = num_deleted * sizeof(struct svalue) * (num_values + 1);
	preserved_size = sizeof(*cm2) +
	  cm->string_size *
	  (1 + (sizeof(struct svalue)/sizeof(char *)) * num_values);
	m->user->mapping_total -= deleted_size;
	cm2_start = xalloc(data_size + size - deleted_size + preserved_size);
	cm2 = (struct condensed_mapping *)
	  (cm2_start + data_size + size - deleted_size);
	memcpy((char *)cm2, (char *)cm, preserved_size);
	cm2->misc_size = size - num_deleted * sizeof(struct svalue);
	data = svp;
	svp2 = CM_MISC(cm2);
	data2 = (struct svalue *)((char *)svp2 - size) + num_deleted;
	svp = CM_MISC(cm);
	i = size;
	while ( (i -= sizeof(struct svalue)) >= 0) {
	    if ( (--svp)->type == T_INVALID) {
		mp_int j;

		for (j = num_values; --j >= 0; ) {
		    free_svalue(--data);
		}
		continue;
	    }
	    *--svp2 = *svp;
	    data -= num_values;
	    data2 -= num_values;
	    memcpy(data2, data, num_values * sizeof(struct svalue));
	}
	m->condensed = cm2;
	xfree((char *)cm - data_size - size);
	free_mapping(m);
    }
}
#endif
#endif /* MALLOC_smalloc */