Beispiel #1
0
static void release_temp_i_array(glui32 *arr, glui32 addr, glui32 len, int passout)
{
  arrayref_t *arref = NULL;
  arrayref_t **aptr;
  glui32 ix, val, addr2;

  if (arr) {
    for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
      if ((*aptr)->array == arr)
        break;
    }
    arref = *aptr;
    if (!arref)
      fatalError("Unable to re-find array argument in Glk call.");
    if (arref->addr != addr || arref->len != len)
      fatalError("Mismatched array argument in Glk call.");

    if (arref->retained) {
      return;
    }

    *aptr = arref->next;
    arref->next = NULL;

    if (passout) {
      for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
        val = arr[ix];
        memWrite32(addr2, val);
      }
    }
    glulx_free(arr);
    glulx_free(arref);
  }
}
Beispiel #2
0
void Glulxe::release_temp_c_array(char *arr, uint addr, uint len, int passout) {
	arrayref_t *arref = nullptr;
	arrayref_t **aptr;
	uint ix, val, addr2;

	if (arr) {
		for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
			if ((*aptr)->array == arr)
				break;
		}
		arref = *aptr;
		if (!arref)
			error("Unable to re-find array argument in Glk call.");
		if (arref->addr != addr || arref->len != len)
			error("Mismatched array argument in Glk call.");

		if (arref->retained) {
			return;
		}

		*aptr = arref->next;
		arref->next = nullptr;

		if (passout) {
			for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 1) {
				val = arr[ix];
				MemW1(addr2, val);
			}
		}
		glulx_free(arr);
		glulx_free(arref);
	}
}
Beispiel #3
0
static void glulxe_retained_unregister(void *array, glui32 len,
  char *typecode, gidispatch_rock_t objrock)
{
  arrayref_t *arref = NULL;
  arrayref_t **aptr;
  glui32 ix, addr2, val;
  int elemsize = 0;

  if (typecode[4] == 'C')
    elemsize = 1;
  else if (typecode[4] == 'I')
    elemsize = 4;

  if (!elemsize || array == NULL) {
    return;
  }

  for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
    if ((*aptr)->array == array)
      break;
  }
  arref = *aptr;
  if (!arref) {
    if (objrock.num == 0)
      return;
    fatalError("Unable to re-find array argument in Glk call.");
  }
  if (arref != objrock.ptr)
    fatalError("Mismatched array reference in Glk call.");
  if (!arref->retained)
    fatalError("Unretained array reference in Glk call.");
  if (arref->elemsize != elemsize || arref->len != len)
    fatalError("Mismatched array argument in Glk call.");

  *aptr = arref->next;
  arref->next = NULL;

  if (elemsize == 1) {
    for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=1) {
      val = ((char *)array)[ix];
      memWrite8(addr2, val);
    }
  }
  else if (elemsize == 4) {
    for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) {
      val = ((glui32 *)array)[ix];
      memWrite32(addr2, val);
    }
  }

  glulx_free(array);
  glulx_free(arref);
}
Beispiel #4
0
/* final_serial():
   Clean up memory when the VM shuts down.
*/
void final_serial()
{
  if (undo_chain) {
    int ix;
    for (ix=0; ix<undo_chain_num; ix++) {
      glulx_free(undo_chain[ix]);
    }
    glulx_free(undo_chain);
  }
  undo_chain = NULL;
  undo_chain_size = 0;
  undo_chain_num = 0;
}
Beispiel #5
0
void Glulxe::glulxe_retained_unregister(void *array, uint len, const  char *typecode, gidispatch_rock_t objrock) {
	arrayref_t *arref = nullptr;
	arrayref_t **aptr;
	uint ix, addr2, val;
	uint elemsize = 0;

	// TODO: See if original GLULXE has code I'm overlooking to cleanly close everything before freeing memmap
	if (!memmap)
		return;

	if (typecode[4] == 'C')
		elemsize = 1;
	else if (typecode[4] == 'I')
		elemsize = 4;

	if (!elemsize || array == nullptr) {
		return;
	}

	for (aptr = (&arrays); (*aptr); aptr = (&((*aptr)->next))) {
		if ((*aptr)->array == array)
			break;
	}
	arref = *aptr;
	if (!arref)
		error("Unable to re-find array argument in Glk call.");
	if (arref != objrock.ptr)
		error("Mismatched array reference in Glk call.");
	if (!arref->retained)
		error("Unretained array reference in Glk call.");
	if (arref->elemsize != elemsize || arref->len != len)
		error("Mismatched array argument in Glk call.");

	*aptr = arref->next;
	arref->next = nullptr;

	if (elemsize == 1) {
		for (ix = 0, addr2 = arref->addr; ix < arref->len; ix++, addr2 += 1) {
			val = ((char *)array)[ix];
			MemW1(addr2, val);
		}
	} else if (elemsize == 4) {
		for (ix = 0, addr2 = arref->addr; ix < arref->len; ix++, addr2 += 4) {
			val = ((uint *)array)[ix];
			MemW4(addr2, val);
		}
	}

	glulx_free(array);
	glulx_free(arref);
}
Beispiel #6
0
void Glulxe::classes_remove(int classid, void *obj) {
	classtable_t *ctab;
	classref_t *cref;
	classref_t **crefp;
	gidispatch_rock_t objrock;
	if (classid < 0 || classid >= num_classes)
		return;
	ctab = classes[classid];
	objrock = gidispatch_get_objrock(obj, classid);
	cref = (classref_t *)objrock.ptr;
	if (!cref)
		return;
	crefp = &(ctab->bucket[cref->bucknum]);
	for (; *crefp; crefp = &((*crefp)->next)) {
		if ((*crefp) == cref) {
			*crefp = cref->next;
			if (!cref->obj) {
				nonfatal_warning("attempt to free nullptr object!");
			}
			cref->obj = nullptr;
			cref->id = 0;
			cref->next = nullptr;
			glulx_free(cref);
			return;
		}
	}
	return;
}
Beispiel #7
0
/* Delete a Glk object from the appropriate hash table. */
static void classes_remove(int classid, void *obj)
{
  classtable_t *ctab;
  classref_t *cref;
  classref_t **crefp;
  gidispatch_rock_t objrock;
  if (classid < 0 || classid >= num_classes)
    return;
  ctab = git_classes[classid];
  objrock = gidispatch_get_objrock(obj, classid);
  cref = objrock.ptr;
  if (!cref)
    return;
  crefp = &(ctab->bucket[cref->bucknum]);
  for (; *crefp; crefp = &((*crefp)->next)) {
    if ((*crefp) == cref) {
      *crefp = cref->next;
      cref->obj = NULL;
      cref->id = 0;
      cref->next = NULL;
      glulx_free(cref);
      return;
    }
  }
  return;
}
Beispiel #8
0
/* perform_restoreundo():
   Pull a state pointer from the undo chain. This returns 0 on success,
   1 on failure. Note that if it succeeds, the frameptr, localsbase,
   and valstackbase registers are invalid; they must be rebuilt from
   the stack.
*/
glui32 perform_restoreundo()
{
  dest_t dest;
  glui32 res, val;
  glui32 heapsumlen = 0;
  glui32 *heapsumarr = NULL;

  if (undo_chain_size == 0 || undo_chain_num == 0)
    return 1;

  dest.ismem = TRUE;
  dest.size = 0;
  dest.pos = 0;
  dest.ptr = undo_chain[0];
  dest.str = NULL;

  res = 0;
  if (res == 0) {
    res = read_long(&dest, &val);
  }
  if (res == 0) {
    res = read_memstate(&dest, val);
  }
  if (res == 0) {
    res = read_long(&dest, &val);
  }
  if (res == 0) {
    res = read_heapstate(&dest, val, FALSE, &heapsumlen, &heapsumarr);
  }
  if (res == 0) {
    res = read_long(&dest, &val);
  }
  if (res == 0) {
    res = read_stackstate(&dest, val, FALSE);
  }
  /* ### really, many of the failure modes of those calls ought to
     cause fatal errors. The stack or main memory may be damaged now. */

  if (res == 0) {
    if (heapsumarr)
      res = heap_apply_summary(heapsumlen, heapsumarr);
  }

  if (res == 0) {
    /* It worked. */
    if (undo_chain_size > 1)
      memmove(undo_chain, undo_chain+1,
        (undo_chain_size-1) * sizeof(unsigned char *));
    undo_chain_num -= 1;
    glulx_free(dest.ptr);
    dest.ptr = NULL;
  }
  else {
    /* It didn't work. */
    dest.ptr = NULL;
  }

  return res;
}
Beispiel #9
0
void profile_out(glui32 stackuse)
{
  frame_t *fra;
  function_t *func;
  struct timeval now, runtime;
  glui32 runops;

  if (!profiling_active)
    return;

  /* printf("### OUT\n"); */

  if (!current_frame) 
    fatal_error("Profiler: stack underflow.");

  gettimeofday(&now, NULL);

  fra = current_frame;
  func = fra->func;

  timersub(&now, &fra->entry_time, &runtime);
  runops = profile_opcount - fra->entry_op;

  timeradd(&runtime, &func->self_time, &func->self_time);
  timersub(&func->self_time, &fra->children_time, &func->self_time);
  func->self_ops += runops;
  func->self_ops -= fra->children_ops;

  if (func->max_depth < fra->depth)
    func->max_depth = fra->depth;
  if (func->max_stack_use < stackuse)
    func->max_stack_use = stackuse;

  if (fra->parent) {
    timeradd(&runtime, &fra->parent->children_time, &fra->parent->children_time);
    fra->parent->children_ops += runops;
  }

  if (!func->entry_depth) 
    fatal_error("Profiler: function entry underflow.");
  
  func->entry_depth -= 1;
  if (!func->entry_depth) {
    timersub(&now, &func->entry_start_time, &runtime);
    timerclear(&func->entry_start_time);

    runops = profile_opcount - func->entry_start_op;
    func->entry_start_op = 0;

    timeradd(&runtime, &func->total_time, &func->total_time);
    func->total_ops += runops;
  }

  current_frame = fra->parent;
  fra->parent = NULL;

  glulx_free(fra);
}
Beispiel #10
0
static void release_temp_ptr_array(void **arr, glui32 addr, glui32 len, int objclass, int passout)
{
  arrayref_t *arref = NULL;
  arrayref_t **aptr;
  glui32 ix, val, addr2;

  if (arr) {
    for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
      if ((*aptr)->array == arr)
        break;
    }
    arref = *aptr;
    if (!arref)
      fatalError("Unable to re-find array argument in Glk call.");
    if (arref->addr != addr || arref->len != len)
      fatalError("Mismatched array argument in Glk call.");

    if (arref->retained) {
      return;
    }

    *aptr = arref->next;
    arref->next = NULL;

    if (passout) {
      for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
        void *opref = arr[ix];
        if (opref) {
          gidispatch_rock_t objrock = 
            gidispatch_get_objrock(opref, objclass);
          val = ((classref_t *)objrock.ptr)->id;
        }
        else {
          val = 0;
        }
        memWrite32(addr2, val);
      }
    }
    glulx_free(arr);
    glulx_free(arref);
  }
}
Beispiel #11
0
/* final_serial():
   Clean up memory when the VM shuts down.
*/
void final_serial()
{
  if (undo_chain) {
    int ix;
    for (ix=0; ix<undo_chain_num; ix++) {
      glulx_free(undo_chain[ix]);
    }
    glulx_free(undo_chain);
  }
  undo_chain = NULL;
  undo_chain_size = 0;
  undo_chain_num = 0;

#ifdef SERIALIZE_CACHE_RAM
  if (ramcache) {
    glulx_free(ramcache);
    ramcache = NULL;
  }
#endif /* SERIALIZE_CACHE_RAM */
}
Beispiel #12
0
void profile_quit()
{
  int bucknum;
  function_t *func;
  char linebuf[512];
  frefid_t profref;
  strid_t profstr;

  while (current_frame) {
    profile_out();
  }

  profref = glk_fileref_create_by_name(fileusage_BinaryMode|fileusage_Data, "profile-raw", 0);
  if (!profref)
    fatal_error("Profiler: unable to create profile-raw file");

  profstr = glk_stream_open_file(profref, filemode_Write, 0);

  glk_put_string_stream(profstr, "<profile>\n");

  for (bucknum=0; bucknum<FUNC_HASH_SIZE; bucknum++) {
    char total_buf[20], self_buf[20];

    for (func = functions[bucknum]; func; func=func->hash_next) {
      /* ###
      sprintf(linebuf, "function %lx called %ld times, total ops %ld, total time %s, self ops %ld,  self time %s\n",
        func->addr, func->call_count,
        func->total_ops,
        timeprint(&func->total_time, total_buf),
        func->self_ops,
        timeprint(&func->self_time, self_buf));
      ### */
      sprintf(linebuf, "  <function addr=\"%lx\" call_count=\"%ld\" accel_count=\"%ld\" total_ops=\"%ld\" total_time=\"%s\" self_ops=\"%ld\" self_time=\"%s\" />\n",
        func->addr, func->call_count, func->accel_count,
        func->total_ops,
        timeprint(&func->total_time, total_buf),
        func->self_ops,
        timeprint(&func->self_time, self_buf));
      glk_put_string_stream(profstr, linebuf);
    }
  }

  glk_put_string_stream(profstr, "</profile>\n");

  glk_stream_close(profstr, NULL);

  glulx_free(functions);
  functions = NULL;
}
Beispiel #13
0
static glui32 write_heapstate(dest_t *dest, int portable)
{
  glui32 res;
  glui32 sumlen;
  glui32 *sumarray;

  res = heap_get_summary(&sumlen, &sumarray);
  if (res)
    return res;

  if (!sumarray)
    return 0; /* no heap */

  res = write_heapstate_sub(sumlen, sumarray, dest, portable);

  glulx_free(sumarray);
  return res;
}
Beispiel #14
0
/* heap_clear():
   Set the heap state to inactive, and free the block lists. This is
   called when the game starts or restarts.
*/
void heap_clear()
{
    while (heap_head) {
        heapblock_t *blo = heap_head;
        heap_head = blo->next;
        blo->next = NULL;
        blo->prev = NULL;
        glulx_free(blo);
    }
    heap_tail = NULL;

    if (heap_start) {
        glui32 res = resizeMemory(heap_start, 1);
        if (res)
            fatalError("Unable to revert memory size when deactivating heap.");
    }

    heap_start = 0;
    alloc_count = 0;
    /* heap_sanity_check(); */
}
Beispiel #15
0
/* perform_saveundo():
   Add a state pointer to the undo chain. This returns 0 on success,
   1 on failure.
*/
glui32 perform_saveundo()
{
  dest_t dest;
  glui32 res;
  glui32 memstart, memlen, heapstart, heaplen, stackstart, stacklen;

  /* The format for undo-saves is simpler than for saves on disk. We
     just have a memory chunk, a heap chunk, and a stack chunk, in
     that order. We skip the IFF chunk headers (although the size
     fields are still there.) We also don't bother with IFF's 16-bit
     alignment. */

  if (undo_chain_size == 0)
    return 1;

  dest.ismem = TRUE;
  dest.size = 0;
  dest.pos = 0;
  dest.ptr = NULL;
  dest.str = NULL;

  res = 0;
  if (res == 0) {
    res = write_long(&dest, 0); /* space for chunk length */
  }
  if (res == 0) {
    memstart = dest.pos;
    res = write_memstate(&dest);
    memlen = dest.pos - memstart;
  }
  if (res == 0) {
    res = write_long(&dest, 0); /* space for chunk length */
  }
  if (res == 0) {
    heapstart = dest.pos;
    res = write_heapstate(&dest, FALSE);
    heaplen = dest.pos - heapstart;
  }
  if (res == 0) {
    res = write_long(&dest, 0); /* space for chunk length */
  }
  if (res == 0) {
    stackstart = dest.pos;
    res = write_stackstate(&dest, FALSE);
    stacklen = dest.pos - stackstart;
  }

  if (res == 0) {
    /* Trim it down to the perfect size. */
    dest.ptr = glulx_realloc(dest.ptr, dest.pos);
    if (!dest.ptr)
      res = 1;
  }
  if (res == 0) {
    res = reposition_write(&dest, memstart-4);
  }
  if (res == 0) {
    res = write_long(&dest, memlen);
  }
  if (res == 0) {
    res = reposition_write(&dest, heapstart-4);
  }
  if (res == 0) {
    res = write_long(&dest, heaplen);
  }
  if (res == 0) {
    res = reposition_write(&dest, stackstart-4);
  }
  if (res == 0) {
    res = write_long(&dest, stacklen);
  }

  if (res == 0) {
    /* It worked. */
    if (undo_chain_num >= undo_chain_size) {
      glulx_free(undo_chain[undo_chain_num-1]);
      undo_chain[undo_chain_num-1] = NULL;
    }
    if (undo_chain_size > 1)
      memmove(undo_chain+1, undo_chain, 
        (undo_chain_size-1) * sizeof(unsigned char *));
    undo_chain[0] = dest.ptr;
    if (undo_chain_num < undo_chain_size)
      undo_chain_num += 1;
    dest.ptr = NULL;
  }
  else {
    /* It didn't work. */
    if (dest.ptr) {
      glulx_free(dest.ptr);
      dest.ptr = NULL;
    }
  }
    
  return res;
}
Beispiel #16
0
static void ReleaseVMString (char * ptr)
{
    glulx_free (ptr);
}
Beispiel #17
0
static void ReleaseVMUstring (glui32 * ptr)
{
    glulx_free (ptr);
}
Beispiel #18
0
void Glulxe::prepare_glk_args(const char *proto, dispatch_splot_t *splot) {
	static gluniversal_t *garglist = nullptr;
	static int garglist_size = 0;

	int ix;
	int numwanted, numvargswanted, maxargs;
	const char *cx;

	cx = proto;
	numwanted = 0;
	while (*cx >= '0' && *cx <= '9') {
		numwanted = 10 * numwanted + (*cx - '0');
		cx++;
	}
	splot->numwanted = numwanted;

	maxargs = 0;
	numvargswanted = 0;
	for (ix = 0; ix < numwanted; ix++) {
		int isref, passin, passout, nullok, isarray, isretained, isreturn;
		cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
		                 &isretained, &isreturn);
		if (isref) {
			maxargs += 2;
		} else {
			maxargs += 1;
		}
		if (!isreturn) {
			if (isarray) {
				numvargswanted += 2;
			} else {
				numvargswanted += 1;
			}
		}

		if (*cx == 'I' || *cx == 'C') {
			cx += 2;
		} else if (*cx == 'Q') {
			cx += 2;
		} else if (*cx == 'S' || *cx == 'U') {
			cx += 1;
		} else if (*cx == '[') {
			int refdepth, nwx;
			cx++;
			nwx = 0;
			while (*cx >= '0' && *cx <= '9') {
				nwx = 10 * nwx + (*cx - '0');
				cx++;
			}
			maxargs += nwx; /* This is *only* correct because all structs contain
                         plain values. */
			refdepth = 1;
			while (refdepth > 0) {
				if (*cx == '[')
					refdepth++;
				else if (*cx == ']')
					refdepth--;
				cx++;
			}
		} else {
			error("Illegal format string.");
		}
	}

	if (*cx != ':' && *cx != '\0')
		error("Illegal format string.");

	splot->maxargs = maxargs;

	if (splot->numvargs != numvargswanted)
		error("Wrong number of arguments to Glk function.");

	if (garglist && garglist_size < maxargs) {
		glulx_free(garglist);
		garglist = nullptr;
		garglist_size = 0;
	}
	if (!garglist) {
		garglist_size = maxargs + 16;
		garglist = (gluniversal_t *)glulx_malloc(garglist_size
		           * sizeof(gluniversal_t));
	}
	if (!garglist)
		error("Unable to allocate storage for Glk arguments.");

	splot->garglist = garglist;
}
Beispiel #19
0
void profile_quit()
{
  int bucknum;
  function_t *func;
  char linebuf[512];
  strid_t profstr;

  if (!profiling_active)
    return;

  while (current_frame) {
    profile_out(0);
  }

  if (profiling_stream) {
    profstr = profiling_stream;
  }
  else if (profiling_filename) {
    frefid_t profref = glk_fileref_create_by_name(fileusage_BinaryMode|fileusage_Data, profiling_filename, 0);
    if (!profref)
        fatal_error_2("Profiler: unable to create profile output fileref", profiling_filename);

    profstr = glk_stream_open_file(profref, filemode_Write, 0);
  }
  else {
    fatal_error("Profiler: no profile output handle!");
  }

  glk_put_string_stream(profstr, "<profile>\n");

  for (bucknum=0; bucknum<FUNC_HASH_SIZE; bucknum++) {
    char total_buf[20], self_buf[20];
    callcount_t *cc;

    for (func = functions[bucknum]; func; func=func->hash_next) {
      /* ###
      sprintf(linebuf, "function %lx called %ld times, total ops %ld, total time %s, self ops %ld,  self time %s\n",
        func->addr, func->call_count,
        func->total_ops,
        timeprint(&func->total_time, total_buf),
        func->self_ops,
        timeprint(&func->self_time, self_buf));
      ### */
      sprintf(linebuf, "  <function addr=\"%lx\" call_count=\"%ld\" accel_count=\"%ld\" total_ops=\"%ld\" total_time=\"%s\" self_ops=\"%ld\" self_time=\"%s\" max_depth=\"%ld\" max_stack_use=\"%ld\" />\n",
        (unsigned long)func->addr, (long)func->call_count, (long)func->accel_count,
        (long)func->total_ops,
        timeprint(&func->total_time, total_buf),
        (long)func->self_ops,
        timeprint(&func->self_time, self_buf),
        (long)func->max_depth, (long)func->max_stack_use);
      glk_put_string_stream(profstr, linebuf);
      for (cc = func->outcalls; cc; cc = cc->next) {
        sprintf(linebuf, "  <calls fromaddr=\"%lx\" toaddr=\"%lx\" count=\"%ld\" />\n",
          (unsigned long)func->addr, (unsigned long)cc->toaddr, (long)cc->count);
        glk_put_string_stream(profstr, linebuf);
      }
    }
  }

  glk_put_string_stream(profstr, "</profile>\n");

  glk_stream_close(profstr, NULL);

  /* ### Ought to free the function structures, not just the hash array. */
  glulx_free(functions);
  functions = NULL;
}
Beispiel #20
0
/* heap_alloc():
   Allocate a block. If necessary, activate the heap and/or extend memory.
   Returns the memory address of the block, or 0 if the operation failed.
*/
glui32 heap_alloc(glui32 len)
{
    heapblock_t *blo, *newblo;

    if (len <= 0)
        fatalError("Heap allocation length must be positive.");

    blo = heap_head;
    while (blo) {
        if (blo->isfree && blo->len >= len)
            break;

        if (!blo->isfree) {
            blo = blo->next;
            continue;
        }

        if (!blo->next || !blo->next->isfree) {
            blo = blo->next;
            continue;
        }

        /* This is a free block, but the next block in the list is also
           free, so we "advance" by merging rather than by going to
           blo->next. */
        newblo = blo->next;
        blo->len += newblo->len;
        if (newblo->next) {
            blo->next = newblo->next;
            newblo->next->prev = blo;
        }
        else {
            blo->next = NULL;
            heap_tail = blo;
        }
        newblo->next = NULL;
        newblo->prev = NULL;
        glulx_free(newblo);
        newblo = NULL;
        continue;
    }

    if (!blo) {
        /* No free area is visible on the list. Try extending memory. How
           much? Double the heap size, or by 256 bytes, or by the memory
           length requested -- whichever is greatest. */
        glui32 res;
        glui32 extension;
        glui32 oldendmem = gEndMem;

        extension = 0;
        if (heap_start)
            extension = gEndMem - heap_start;
        if (extension < len)
            extension = len;
        if (extension < 256)
            extension = 256;
        /* And it must be rounded up to a multiple of 256. */
        extension = (extension + 0xFF) & (~(glui32)0xFF);

        res = resizeMemory(gEndMem+extension, 1);
        if (res)
            return 0;

        /* If we just started the heap, note that. */
        if (heap_start == 0)
            heap_start = oldendmem;

        if (heap_tail && heap_tail->isfree) {
            /* Append the new space to the last block. */
            blo = heap_tail;
            blo->len += extension;
        }
        else {
            /* Append the new space to the block list, as a new block. */
            newblo = glulx_malloc(sizeof(heapblock_t));
            if (!newblo)
                fatalError("Unable to allocate record for heap block.");
            newblo->addr = oldendmem;
            newblo->len = extension;
            newblo->isfree = TRUE;
            newblo->next = NULL;
            newblo->prev = NULL;

            if (!heap_tail) {
                heap_head = newblo;
                heap_tail = newblo;
            }
            else {
                blo = heap_tail;
                heap_tail = newblo;
                blo->next = newblo;
                newblo->prev = blo;
            }

            blo = newblo;
            newblo = NULL;
        }

        /* and continue forwards, using this new block (blo). */
    }

    /* Something strange happened. */
    if (!blo || !blo->isfree || blo->len < len)
        return 0;

    /* We now have a free block of size len or longer. */

    if (blo->len == len) {
        blo->isfree = FALSE;
    }
    else {
        newblo = glulx_malloc(sizeof(heapblock_t));
        if (!newblo)
            fatalError("Unable to allocate record for heap block.");
        newblo->isfree = TRUE;
        newblo->addr = blo->addr + len;
        newblo->len = blo->len - len;
        blo->len = len;
        blo->isfree = FALSE;
        newblo->next = blo->next;
        if (newblo->next)
            newblo->next->prev = newblo;
        newblo->prev = blo;
        blo->next = newblo;
        if (heap_tail == blo)
            heap_tail = newblo;
    }

    alloc_count++;
    /* heap_sanity_check(); */
    return blo->addr;
}