File:  [MW Coherent from dump] / coherent / b / STREAMS / conf_79 / buildobj.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Wed May 29 04:56:36 2019 UTC (7 years, 2 months ago) by root
Branches: MarkWilliams, MAIN
CVS tags: relic, HEAD
coherent

/*
 * Implementation of functions prototyped in "buildobj.h"
 */
/*
 *-IMPORTS:
 *	<sys/compat.h>
 *		USE_PROTO
 *		VOID
 *		ARGS ()
 *	<stdlib.h>
 *		NULL
 *		size_t
 *		free ()
 *		malloc ()
 *	<string.h>
 *		memcpy ()
 *		memset ()
 */

#include <sys/compat.h>
#include <stdlib.h>
#include <string.h>

#include "buildobj.h"


/*
 * Structures that are only really visible inside this compilation unit.
 *
 * Note that "o_prev" is used only for nested build contexts, which are
 * special, although it could be used for all objects if we wanted to spend
 * the space on allowing stacklike allocation (which we don't, normally).
 */


typedef	struct buildchunk	bchunk_t;
typedef	struct objinfo		obj_t;

struct objinfo {
	obj_t	      *	o_prev;		/* previous object */
	VOID	      *	o_base;		/* base of object */
	size_t		o_size;		/* size of object */
	unsigned char	o_building;	/* object being built */
};

struct builder {
	bchunk_t      *	b_first;	/* first allocated chunk */
	bchunk_t      *	b_last;		/* last allocated chunk */
	size_t		b_chunksize;	/* recommended chunk size */
	size_t		b_align;	/* allocation alignment factor */

	obj_t		b_obj;		/* current object */
	bchunk_t      *	b_chunkp;	/* chunk of last/current object */
};

struct buildchunk {
	bchunk_t      *	bc_next;	/* next memory chunk */
	char	      *	bc_base;	/* base of chunk space */
	size_t		bc_free;	/* amount of free space */
};


/*
 * This internal function allocates a new chunk of memory to be parcelled out
 * to clients. Allocate "b_chunksize" bytes of memory to give out, and copy
 * any partial allocation over to the new chunk (since we typically only
 * allocate new chunks when a request to expand a previous allocation
 * overflows the biggest available block of space).
 */

#ifdef	USE_PROTO
bchunk_t * (BUILD_CHUNK) (build_t * heap, bchunk_t * bchunkp)
#else
bchunk_t *
BUILD_CHUNK ARGS ((heap, bchunkp))
build_t	      *	heap;
bchunk_t      *	bchunkp;
#endif
{

	/*
	 * Ok, let's add a new chunk.
	 */

	if ((bchunkp = (bchunk_t *) malloc (heap->b_chunksize +
					    sizeof (bchunk_t))) == NULL)
		return NULL;

	bchunkp->bc_base = (char *) (bchunkp + 1);
	bchunkp->bc_free = heap->b_chunksize;

	/*
	 * Copy any previously existing data to the new chunk.
	 */

	if (heap->b_obj.o_size > 0) {

		memcpy (bchunkp->bc_base, heap->b_obj.o_base,
			heap->b_obj.o_size);

		bchunkp->bc_base += heap->b_obj.o_size;
		bchunkp->bc_free -= heap->b_obj.o_size;
	}


	heap->b_chunkp = bchunkp;
	heap->b_obj.o_base = (char *) (bchunkp + 1);

	return bchunkp;
}


/*
 * This internal function increases the size of an allocation. If a request
 * causes an allocation to expand beyond the end of the current chunk, try and
 * find another chunk that can hold it, and if that fails allocate a new chunk
 * which is guaranteed to be large enough.
 *
 * The client's data is copied to the new space, appended to any previously
 * allocated data in the same group.
 */

#ifdef	USE_PROTO
int (BUILD_ADD) (build_t * heap, size_t size, VOID * init)
#else
int
BUILD_ADD ARGS ((heap, size, init))
build_t	      *	heap;
size_t		size;
VOID          *	init;
#endif
{
	bchunk_t      *	bchunkp;
	size_t		total = size + heap->b_obj.o_size;

	if (size == 0)
		return BUILD_OK;

	if (total < size || total + sizeof (bchunk_t) < total)
		return BUILD_SIZE_OVERFLOW;

	/*
	 * First, try to fit the expanded allocation in the current chunk.
	 * If there are no chunks allocated yet, let's start.
	 */

	bchunkp = heap->b_chunkp;

	if ((bchunkp != NULL && bchunkp->bc_free < size) || bchunkp == NULL) {
		/*
		 * First, try and find an already allocated chunk that will
		 * hold the expanded allocation.
		 */

		while (bchunkp != NULL) {

			if (bchunkp->bc_free >= total) {
				/*
				 * Transfer existing object information to new
				 * chunk.
				 */

				if (heap->b_obj.o_size > 0)
					memcpy (bchunkp->bc_base,
						heap->b_obj.o_base,
						heap->b_obj.o_size);

				heap->b_obj.o_base = bchunkp->bc_base;
				heap->b_chunkp = bchunkp;

				bchunkp->bc_base += heap->b_obj.o_size;
				bchunkp->bc_free -= heap->b_obj.o_size;

				goto gotmem;
			}

			bchunkp = bchunkp->bc_next;
		}

		if (total > heap->b_chunksize)
			heap->b_chunksize = total + (total >> 1);

		if ((bchunkp = BUILD_CHUNK (heap, bchunkp)) == NULL)
			return BUILD_NO_MEMORY;
	}

gotmem:
	/*
	 * OK, let's initialise the new space.
	 */

	if (init == INIT_ZERO) {
		/*
		 * The client passed us the special pointer value that means
		 * we should zero the new space.
		 */

		memset (bchunkp->bc_base, 0, size);
	} else {
		/*
		 * The client has given us explicit initialisation values.
		 */

		memcpy (bchunkp->bc_base, init, size);
	}

	bchunkp->bc_base += size;
	bchunkp->bc_free -= size;
	heap->b_obj.o_size += size;

	return BUILD_OK;
}


/*
 * Local function which recovers a chunk number from an object address. Note
 * that we *cannot* do this by merely scanning the chunks looking for one that
 * encloses the object pointer we have, because relational comparisons between
 * distinctly-allocated objects yields an undefined result (ie, we could get
 * false positives).
 *
 * However, we can solve the problem by requiring the object pointer to be the
 * most recent allocation in a given chunk, and passing in the size of the
 * object. This way, we can test for a match via equality, which is a stronger
 * test, because two object pointers compare equal if and only if they point
 * at the same object.
 *
 * [ Note that the converse is controversial; it does not appear to be
 *   mandated by ISO C, and the notion of object identity is hotly debated
 *   in C++ circles. Since we will be using pointers that have been derived
 *   via convential means we can ignore such notions of aliasing. ]
 */

#ifdef	USE_PROTO
LOCAL bchunk_t * (FIND_CHUNK) (build_t * heap, VOID * obj, size_t size)
#else
LOCAL bchunk_t *
FIND_CHUNK ARGS ((heap, obj, size))
build_t	      *	heap;
VOID	      *	obj;
size_t		size;
#endif
{
	bchunk_t      *	scan;

	for (scan = heap->b_first ; scan != NULL ; scan = scan->bc_next)
		if (scan->bc_base - size == obj)
			return scan;

	return NULL;
}


/*
 * This function allocates a control block for building variable-sized
 * objects with. The "chunksize" is the default amount of memory to request
 * from the C library in a block which other functions will parcel out to
 * clients.
 */

#ifdef	USE_PROTO
build_t * (builder_alloc) (size_t chunksize, size_t align)
#else
build_t *
builder_alloc ARGS ((chunksize, align))
size_t		chunksize;
size_t		align;
#endif
{
	build_t	      *	buildp;

	if (chunksize < 256)
		chunksize = 256;

	if ((buildp = (build_t *) malloc (sizeof (build_t))) != NULL) {

		buildp->b_first = buildp->b_last = NULL;
		buildp->b_chunksize = chunksize;
		buildp->b_align = align == 0 ? 1 : align;

		buildp->b_obj.o_building = 0;
		buildp->b_obj.o_prev = NULL;	/* no previous object */
		buildp->b_obj.o_base = NULL;	/* not building an object */
	}

	return buildp;
}


/*
 * Free the basic object builder structure and all the chunk memory that it
 * attached to itself during its lifetime.
 */

#ifdef	USE_PROTO
void (builder_free) (build_t * heap)
#else
void
builder_free ARGS ((heap))
build_t	      *	heap;
#endif
{
	bchunk_t      *	scan;
	bchunk_t      *	next;

	if (heap == NULL)
		return;

	for (scan = heap->b_first ; scan != NULL ; scan = next) {

		next = scan->bc_next;
		free (scan);
	}

	free (heap);
}


/*
 * Simple function to allocate just a single chunk.
 */

#ifdef	USE_PROTO
VOID * (build_malloc) (build_t * heap, size_t size)
#else
VOID *
build_malloc ARGS ((heap, size))
build_t	      *	heap;
size_t		size;
#endif
{
	if (heap == NULL || heap->b_obj.o_building != 0 ||
	    (heap->b_obj.o_base != NULL && heap->b_obj.o_prev != NULL))
		return NULL;

	heap->b_obj.o_size = 0;
	heap->b_obj.o_base = NULL;

	heap->b_chunkp = heap->b_first;

#ifdef	__COHERENT__
	return BUILD_ADD (heap, size, NULL) == 0 ? heap->b_obj.o_base :
		(VOID *) NULL;
#else
	return BUILD_ADD (heap, size, NULL) == 0 ? heap->b_obj.o_base : NULL;
#endif
}


/*
 * Begin building some variable-sized object with an initial allocation of
 * "size" bytes. All previous allocations must be complete before beginning a
 * new allocation.
 */

#ifdef	USE_PROTO
int (build_begin) (build_t * heap, size_t size, VOID * init)
#else
int
build_begin ARGS ((heap, size, init))
build_t	      *	heap;
size_t		size;
VOID          *	init;
#endif
{
	if (heap == NULL)
		return BUILD_NULL_HEAP;
	if (heap->b_obj.o_building != 0)
		return BUILD_OBJECT_BEGUN;

	/*
	 * We only allow one object to be built per inner nesting level; since
	 * object descriptors are normally not needed, levels that can be
	 * unwound have to be pushed/popped manually. However, only one object
	 * can be built at an inner level or else the stack could not be
	 * unwound.
	 */

	if (heap->b_obj.o_base != NULL &&
	    heap->b_obj.o_prev != NULL)
		return BUILD_BAD_NESTING;

	heap->b_obj.o_size = 0;
	heap->b_obj.o_base = NULL;
	heap->b_obj.o_building = 1;

	heap->b_chunkp = heap->b_first;

	return BUILD_ADD (heap, size, init);
}


/*
 * Add "size" bytes to the current variable-sized object.
 */

#ifdef	USE_PROTO
int (build_add) (build_t * heap, size_t size, VOID * init)
#else
int
build_add ARGS ((heap, size, init))
build_t	      *	heap;
size_t		size;
VOID      *	init;
#endif
{
	if (heap == NULL)
		return BUILD_NULL_HEAP;
	if (heap->b_obj.o_building == 0)
		return BUILD_NO_OBJECT;

	return BUILD_ADD (heap, size, init);
}


/*
 * Special-case function to add a single character to an object efficiently.
 * This is useful for functions which must build strings on the fly without
 * any easy way of predetermining the size.
 */

#ifdef	USE_PROTO
int (build_addchar) (build_t * heap, char ch)
#else
int
build_addchar ARGS ((heap, ch))
build_t	      *	heap;
char		ch;
#endif
{
	bchunk_t      *	bchunkp;

	if (heap == NULL)
		return BUILD_NULL_HEAP;
	if (heap->b_obj.o_building == 0)
		return BUILD_NO_OBJECT;

	/*
	 * Like BUILD_ADD (), but faster in the simplest case.
	 */

	if ((bchunkp = heap->b_chunkp) == NULL || bchunkp->bc_free == 0)
		if ((bchunkp = BUILD_CHUNK (heap, bchunkp)) == NULL)
			return BUILD_NO_MEMORY;

	* bchunkp->bc_base ++ = ch;
	bchunkp->bc_free --;
	heap->b_obj.o_size ++;

	return BUILD_OK;
}


/*
 * End construction of an object, optionally returning the size in bytes
 * occupied.
 */

#ifdef	USE_PROTO
VOID * (build_end) (build_t * heap, size_t * size)
#else
VOID *
build_end ARGS ((heap, size))
build_t	      *	heap;
size_t	      *	size;
#endif
{
	size_t		adjust;

	if (heap == NULL || heap->b_obj.o_building == 0)
		return NULL;

	if (size != NULL)
		* size = heap->b_obj.o_size;

	if ((adjust = heap->b_obj.o_size & (heap->b_align - 1)) != 0) {
		/*
		 * Waste some space at the end to make the alignment happen.
		 */

		adjust = heap->b_align - adjust;

		heap->b_obj.o_size += adjust;
		heap->b_chunkp->bc_base += adjust;
		heap->b_chunkp->bc_free -= adjust;
	}

	heap->b_obj.o_building = 0;
	return heap->b_obj.o_base;
}



/*
 * Return the storage of the most recently constructed object to the free
 * pool. Note that the GNU obstack system allows an object heap to be cut back
 * to any arbitrary point in a stack-like fashion; we don't, because we try
 * and squeeze space into end-of-chunk areas that the GNU system would leave
 * alone. Perhaps later we can add options to allow this.
 */

#ifdef	USE_PROTO
int (build_release) (build_t * heap, VOID * base)
#else
int
build_release ARGS ((heap, base))
build_t	      *	heap;
VOID      *	base;
#endif
{
	if (heap == NULL)
		return BUILD_NULL_HEAP;
	if (base == NULL)
		return BUILD_NULL_BASE;
	if (heap->b_obj.o_building == 1)
		return BUILD_OBJECT_BEGUN;
	if (heap->b_obj.o_base != base)
		return BUILD_NOT_LAST;

	/*
	 * Return "heap->b_size" bytes to the chunk that the last object was
	 * constructed in. We also forget that the object was ever built,
	 * which will allow nested builds to happen.
	 */

	heap->b_chunkp->bc_base -= heap->b_obj.o_size;
	heap->b_chunkp->bc_free += heap->b_obj.o_size;

	heap->b_obj.o_base = NULL;
	heap->b_obj.o_size = 0;

	return BUILD_OK;
}


/*
 * Temporarily suspend construction of an object and begin nested construction
 * of another object. Once a nested build context has been entered, only one
 * object may be allowed to be built (although it may be thrown away and
 * another one begun).
 */

#ifdef	USE_PROTO
int (build_push) (build_t * heap)
#else
int
build_push ARGS ((heap))
build_t	      *	heap;
#endif
{
	obj_t		temp;
	int		err;

	if (heap == NULL)
		return BUILD_NULL_HEAP;

	/*
	 * We will need to find some space in the heap for the saved build
	 * context. We take a snapshot of the allocation context in a local
	 * variable for eventual storage in the heap, and start a "new"
	 * allocation. Then, calling BUILD_ADD () will find space for the
	 * snapshot and copy it.
	 */

	temp = heap->b_obj;

	heap->b_obj.o_size = 0;

	if ((err = BUILD_ADD (heap, sizeof (temp), & temp)) != 0) {
		/*
		 * We couldn't create space for the saved context, so restore
		 * the state and return the error to the caller.
		 */

		heap->b_obj = temp;
	} else {
		/*
		 * Record the location where the saved snapshot was stored as
		 * the back-link in the snapshot chain. Then, we clear the
		 * top object record for use in at most one nested allocation.
		 */

		heap->b_obj.o_prev = (obj_t *) heap->b_obj.o_base;

		heap->b_obj.o_size = 0;
		heap->b_obj.o_base = NULL;
	}

	return err;
}


/*
 * Undo the actions of build_push (). If there is an allocation in the current
 * top-level object record, discard it implicitly.
 */

#ifdef	USE_PROTO
int (build_pop) (build_t * heap)
#else
int
build_pop ARGS ((heap))
build_t	      *	heap;
#endif
{
	bchunk_t      *	temp;

	if (heap == NULL)
		return BUILD_NULL_HEAP;
	if (heap->b_obj.o_prev == NULL)
		return BUILD_STACK_EMPTY;

	/*
	 * Before we retrieve the previous object context, we have to find
	 * the chunk it is stored in (so that we can discard the saved
	 * context record).
	 */

	if ((temp = FIND_CHUNK (heap, heap->b_obj.o_prev,
				sizeof (obj_t))) == NULL)
		return BUILD_CORRUPT;

	/*
	 * Implicitly discard top object.
	 */

	if (heap->b_obj.o_base != NULL) {

		heap->b_chunkp->bc_base -= heap->b_obj.o_size;
		heap->b_chunkp->bc_free += heap->b_obj.o_size;
	}


	/*
	 * Free saved record and recover the data stored there. We do it in
	 * that backwards order to avoid storing too many temporary pointers,
	 * and because it is safe for us (we won't be interrupted, and our
	 * code doesn't overwrite newly-freed memory).
	 */

	temp->bc_base -= sizeof (obj_t);
	temp->bc_free += sizeof (obj_t);

	heap->b_obj = * heap->b_obj.o_prev;


	/*
	 * We don't store the previous chunk pointer in the object record
	 * because we can recover it easily if necessary (and it often isn't).
	 */

	if (heap->b_obj.o_base != NULL &&
	    (heap->b_chunkp = FIND_CHUNK (heap, heap->b_obj.o_base,
					  heap->b_obj.o_size)) == NULL)
		return BUILD_CORRUPT;

	return 0;
}


/*
 * Return a human-readable string from an error code.
 */

#ifdef	USE_PROTO
CONST char * (build_error) (int errcode)
#else
CONST char *
build_error ARGS ((errcode))
int		errcode;
#endif
{
	static CONST char * errors [] = {
		/*
		 * Error strings to match the error enumeration, from 0 to
		 * the maximum negative error code, plus an extra code for
		 * invalid error codes.
		 */

		"no error",
		"\"heap\" parameter is NULL",
		"\"base\" parameter is NULL",
		"object already under construction",
		"object construction not begun",
		"insufficient memory to satisfy request",
		"overflow of \"size_t\" would result",
		"not most recently built object",
		"can build only one object in inner nesting level",
		"pop of empty saved object stack",
		"heap appears to be corrupt",
		"<not a valid error>"
	};

#define	ARRAY_LEN(a)	(sizeof (a) / sizeof (* (a)))

	return (errcode > 0 || - errcode >= ARRAY_LEN (errors) ?
			errors [ARRAY_LEN (errors) - 1] : errors [- errcode]);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.