/*
 * Copyright (c) 1995, 1996, 1997, 1998, 1999 The University of Utah and
 * the Computer Systems Laboratory at the University of Utah (CSL).
 *
 * This file is part of Flick, the Flexible IDL Compiler Kit.
 *
 * Flick is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Flick is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Flick; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place #330, Boston, MA 02111, USA.
 */

#include <assert.h>

#include <mom/c/libcast.h>
#include <mom/c/pbe.hh>

/*
 * Define the maximum length of an array that we are willing to preallocate on
 * the stack.  Essentially, this is the amount of space we are willing to waste
 * for any given array in order to avoid the overhead of a dynamic allocation.
 *
 * XXX - This number should be based on *SIZE*, not length!
 */
static const unsigned MAX_PREALLOC_LEN = 256;

/*
 * This is a utility function used to build logical-OR expressions
 * intelligently.
 *
 * To use it, initialize a `cast_expr' variable (e.g., `expr') to 0.  Each time
 * you want to add a new term to the expression, call this routine with the old
 * `expr' as `old_cond' and the new term as `new_cond', and assign the result
 * back to `expr'.  This will automatically handle the case of there being only
 * one term.  (Of course, if there are _no_ terms, then `expr' will remain null
 * --- so be careful.)
 */
cast_expr if_or(cast_expr old_cond, cast_expr new_cond)
{
	if (old_cond == 0)
		return new_cond;
	else
		return cast_new_binary_expr(CAST_BINARY_LOR,
					    old_cond, new_cond);
}

/*
 * Generate code to allocate memory to be pointed to by a pointer variable or
 * parameter, as dictated by the allocation semantics defined in the passed
 * `alloc' structure.
 *
 * `pexpr' is a C expression representing the pointer variable memory is being
 * allocated for; naturally, it must be an lvalue so that it can be assigned
 * to.
 *
 * `target_type' is the C type of whatever the pointer points to.  (The
 * pointer's type itself would be a CAST_TYPE_POINTER referring to that type.)
 *
 * `cname' is the name of the PRES_C_INLINE_ALLOCATION_CONTEXT node that is
 * associated with this pointer.
 */
void mu_state::mu_pointer_alloc(cast_expr pexpr, cast_type target_type,
				char *cname)
{
	cast_expr alloc_expr;
	int abort_stmt = 0;
	int can_fail = 1; // True if allocation can fail (default true).
	
	/* We need to find our allocation semantics established by some parent
           INLINE_ALLOCATION_CONTEXT node. */
	mu_inline_alloc_context *iac = inline_alloc_context;
	while (iac) {
		if (strcmp(iac->name, cname) == 0)
			break;
		iac = iac->parent_context;
	}
	assert(iac);
	pres_c_allocation *palloc = iac->alloc;
	
	/* Find the appropriate allocation case, and make sure it's valid. */
	pres_c_allocation_u *ualloc = &palloc->cases[current_param_dir];
	assert(ualloc->allow != PRES_C_ALLOCATION_INVALID);
	pres_c_allocation_case *alloc = &(ualloc->pres_c_allocation_u_u.val);
	
	if (!(op & MUST_ALLOCATE))
		return;
	
	if ((alloc->flags & PRES_C_ALLOC_EVER) == PRES_C_ALLOC_NEVER)
		return;
	
	/*
	 * Special case: If we would try to allocate a `void', we change the
	 * target type to something allocable --- `char' will do.
	 */
	if (target_type->kind == CAST_TYPE_VOID) {
		target_type = cast_new_type(CAST_TYPE_PRIMITIVE);
		target_type->
			cast_type_u_u.primitive_type.kind = CAST_PRIM_CHAR;
		target_type->
			cast_type_u_u.primitive_type.mod = 0;
	}
	
	unsigned int len_min, len_max;
	cast_expr min_expr, max_expr, length_expr;
	mu_array_get_pres_bounds(&len_min, &min_expr, &len_max, &max_expr,
				 cname);
	length_expr = get_allocation_length(cname, palloc);
	
	/*
	 * If there is a typecast attached to the pointer, remove it.
	 * Though gcc handles casts-as-lvalues, standard C forbids it.
	 *
	 * XXX - This is not the place to deal with invalid C code...
	 * mu_pointer_alloc() should recieve a proper lvalue expression.
	 * The problem stems from the fact that allocation happens as a side
	 * effect to normal decoding: we are requesting to decode data into
	 * the expression, which may need to be viewed through a typecast.
	 * The typecast is determined at a high level, but allocation for
	 * the data is made just before actually decoding it, using the same
	 * expression (including typecast).
	 */
	while (pexpr->kind == CAST_EXPR_CAST) {
		pexpr = pexpr->cast_expr_u_u.cast.expr;
	}
	
	/*
	 * Generate a statement to allocate storage and assign the pointer.
	 *
	 * XXX --- We ought to replace this `switch' with some kind of
	 * allocator method dispatch function.
	 */
	switch (alloc->allocator.kind) {
	case PRES_C_ALLOCATOR_STATIC: {
		
		cast_type static_target_type;
		
		/*
		 * The static allocator can allocate only positive, constant
		 * amounts of storage!
		 */
		assert(length_expr->kind
		       == CAST_EXPR_LIT_PRIM);
		assert(length_expr->cast_expr_u_u.lit_prim.u.kind
		       == CAST_PRIM_INT);
		assert(length_expr->cast_expr_u_u.lit_prim.u.cast_lit_prim_u_u.
		       i
		       > 0);
		
		/*
		 * Declare a static variable to provide the storage and assign
		 * the pointer to point to that.
		 *
		 * XXX --- It's a little goofy to have two variables, one for
		 * static storage and one to point to it.  But this is how
		 * things must be done until the back end is smarter.  To be
		 * smarter, we need to be able to determine `pexpr' here and
		 * then pass it up.  But implementing that feature correctly
		 * would seem to mean that *all* mapping functions should be
		 * able to determine their own CAST reference expressions.
		 */
		if (length_expr->cast_expr_u_u.lit_prim.u.cast_lit_prim_u_u.i
		    != 1) {
			/* `static_target_type' will be an array. */
			static_target_type = cast_new_type(CAST_TYPE_ARRAY);
			static_target_type->
				cast_type_u_u.array_type.element_type =
					target_type;
			static_target_type->
				cast_type_u_u.array_type.length =
				length_expr;
		} else
			/* `static_target_type' will be the `target_type'. */
			static_target_type = target_type;
		
		cast_expr static_var_expr = add_temp_var("storage",
							 static_target_type,
							 0,
							 CAST_SC_STATIC);
		
		if (static_target_type != target_type)
			/* `alloc_expr' is the name of the local array. */
			alloc_expr = static_var_expr;
		else
			/* `alloc_expr' is the address of the local var. */
			alloc_expr = cast_new_unary_expr(CAST_UNARY_ADDR,
							 static_var_expr);
		can_fail = 0; // Static storage can't fail to be alloced!
		break;
	}
	
	case PRES_C_ALLOCATOR_DONTCARE:
	case PRES_C_ALLOCATOR_OUTOFLINE:
		
		/*
		 * Stack allocation
		 *   (AKA `auto' or `dontcare')
		 */
		if (length_expr->kind == CAST_EXPR_LIT_PRIM) {
			
			cast_type auto_target_type;
			
			/*
			 * The auto allocator can deal with requests for
			 * variable amounts of storage, but such requests are
			 * not handled here.  Here, we must have a positive
			 * constant request.
			 */
			assert(length_expr->cast_expr_u_u.lit_prim.u.kind
			       == CAST_PRIM_INT);
			assert(length_expr->cast_expr_u_u.lit_prim.u.
			       cast_lit_prim_u_u.i
			       > 0);
			
			/*
			 * Declare an automatic variable to provide the storage
			 * and assign the pointer to point to that.
			 *
			 * XXX --- See the comment in the ``static'' case about
			 * why we must currently have two variables (one for
			 * the pointer and a second for storage) instead of
			 * just one.
			 */
			if (length_expr->cast_expr_u_u.lit_prim.u.
			    cast_lit_prim_u_u.i != 1) {
				/* `auto_target_type' will be an array. */
				auto_target_type
					= cast_new_type(CAST_TYPE_ARRAY);
				auto_target_type->
					cast_type_u_u.array_type.element_type =
					target_type;
				auto_target_type->
					cast_type_u_u.array_type.length =
					length_expr;
			} else
				/* `auto_target_type' will be `target_type'. */
				auto_target_type = target_type;
			
			cast_expr auto_var_expr
				= add_temp_var("storage",
					       auto_target_type,
					       0,
					       CAST_SC_AUTO);
			
			if (auto_target_type != target_type)
				/* `alloc_expr' is the name of the local
                                   array. */
				alloc_expr = auto_var_expr;
			else
				/* `alloc_expr' is the address of the local
                                   var. */
				alloc_expr = cast_new_unary_expr(
					CAST_UNARY_ADDR,
					auto_var_expr);
			can_fail = 0; // Stack storage can't fail!
			break;
		}
		/* else fall through (we don't know the exact length) */
		
	case PRES_C_ALLOCATOR_NAME: {
		if( alloc->flags & PRES_C_RUN_CTOR ) {
			cast_type type;
			
			if ((length_expr->kind == CAST_EXPR_LIT_PRIM)
			    && (length_expr->cast_expr_u_u.lit_prim.u.kind ==
				CAST_PRIM_INT)
			    && (length_expr->cast_expr_u_u.lit_prim.u.
				cast_lit_prim_u_u.i
				== 1)
			    )
				type = target_type;
			else
				type = cast_new_array_type(length_expr,
							   target_type);
			alloc_expr = cast_new_expr_op_new(0, type,
							  alloc->alloc_init);
		}
		else {
			cast_expr size_expr;
			/* Call a function to allocate the storage. */
			const char *name = get_allocator(palloc);
			
			if ((length_expr->kind == CAST_EXPR_LIT_PRIM)
			    && (length_expr->cast_expr_u_u.lit_prim.u.kind ==
				CAST_PRIM_INT)
			    && (length_expr->cast_expr_u_u.lit_prim.u.
				cast_lit_prim_u_u.i
				== 1)
			    )
				/* Special case:
				     Don't multiply the `sizeof' by 1. */
				size_expr = cast_new_expr_sizeof_type(
					target_type);
			else
				/* General case: Multiply by `length_expr'. */
				size_expr = cast_new_binary_expr(
					CAST_BINARY_MUL,
					length_expr,
					cast_new_expr_sizeof_type(
						target_type));
			alloc_expr = cast_new_expr_call_1(
				cast_new_expr_name(name),
				size_expr);
			if( alloc->flags & PRES_C_DEALLOC_ON_FAIL )
				abort_stmt = 1;
		}
		can_fail = 1; // Named allocators *can* fail.
		break;
	}
	
	default:
		panic("In mu_state::mu_pointer_alloc(), "
		      "Unknown allocator kind (%d)",
		      alloc->allocator.kind);
	}
	
	cast_type alloc_type = cast_new_pointer_type(target_type);
	
	alloc_expr = cast_new_expr_cast(alloc_expr, alloc_type);
	
	/* Decide if we should set the allocated length. */
	cast_stmt set_alen_stmt = 0;
	cast_expr alen_cexpr;
	cast_type alen_ctype;
	int gotarg = arglist->getargs(cname, "alloc_len",
				      &alen_cexpr, &alen_ctype);
	assert(gotarg);
	if (alen_cexpr) {
		set_alen_stmt = cast_new_stmt_expr(
			cast_new_expr_assign(alen_cexpr, length_expr));
	}
	
	/* Decide if we should honor/set ownership (release flag). */
	cast_stmt set_release_stmt = 0;
	cast_stmt release_stmt = 0;
	cast_expr rel_cexpr;
	cast_type rel_ctype;
	gotarg = arglist->getargs(cname, "release", &rel_cexpr, &rel_ctype);
	assert(gotarg);
	if (rel_cexpr) {
		/*
		 * Honor previous ownership & free the buffer if necessary.
		 * This is only necessary if we have realloc flags set (e.g. an
		 * inout parameter on the client side).  If no realloc flags
		 * are set, it is likely the buffer has not been initialized
		 * yet, so checking the release flag or freeing the buffer
		 * would have unpredictable results.  This places the
		 * responsibility on the presentation generators to correctly
		 * identify when/if reallocation should occur for each entity.
		 *
		 * XXX - THE PGs CURRENTLY DON'T SET ANY REALLOC FLAGS!
		 */
		if (alloc->flags & PRES_C_REALLOC_EVER) {
			/*
			 * This gets kinda ugly.  What we want to do is
			 * deallocate the buffer, and we would like to use
			 * mu_pointer_free() to do so.  However, that requires
			 * us to munge ``op'' and the current alloc flags to
			 * ensure deallocation (since this branch will only be
			 * taken in valid cases; we don't want to duplicate
			 * conditionals).
			 */
			mu_state_op old_op = op;
			op = MUST_DEALLOCATE;
			pres_c_alloc_flags old_flags = alloc->flags;
			alloc->flags = PRES_C_DEALLOC_ALWAYS;
			cast_stmt old_block = c_block;
			c_block = 0;
			mu_pointer_free(pexpr, target_type, cname);
			if (c_block) {
				release_stmt = c_block;
				assert(release_stmt->kind == CAST_STMT_BLOCK);
				release_stmt->cast_stmt_u_u.block.flags
					|= CAST_BLOCK_INLINE;
			}
			c_block = old_block;
			alloc->flags = old_flags;
			op = old_op;
		}
		/* Setting ownership, since we just allocated it. */
		set_release_stmt = cast_new_stmt_expr(
			cast_new_expr_assign(rel_cexpr,
					     cast_new_expr_lit_int(can_fail,
								   0)));
	}
	
	/* Construct the allocation statement. */
	cast_expr temp_alloc_var = cast_new_expr_name("_t_alloc_ptr");
	cast_stmt alloc_block_stmt = cast_new_block(0, 0);
	cast_block *alloc_block = &alloc_block_stmt->cast_stmt_u_u.block;
	if (can_fail) {
		/*
		 * We use a temporary variable to allocate storage, so we can
		 * check for allocation errors without obliterating the
		 * original pointer.  But we ONLY do this if the allocation
		 * can fail (stack allocation just doesn't fail).
		 */
		cast_block_add_var(alloc_block,
				   (temp_alloc_var->cast_expr_u_u.
				    name.cast_scoped_name_val[0].name),
				   alloc_type, 0, CAST_SC_AUTO);
		cast_block_add_stmt(alloc_block,
				    cast_new_stmt_expr(
					    cast_new_expr_assign(
						    temp_alloc_var,
						    alloc_expr)));
		cast_block_add_stmt(
			alloc_block,
			cast_new_if(
				cast_new_unary_expr(CAST_UNARY_LNOT,
						    temp_alloc_var),
				make_error(FLICK_ERROR_NO_MEMORY),
				0));
		
		if (abort_stmt)
			abort_block->add_stmt(cast_new_stmt_expr(
				cast_new_expr_call_1(
					cast_new_expr_name(
						get_abort_deallocator(palloc)),
					pexpr)));
	} else {
		/*
		 * We don't need a temporary variable for allocation, since it
		 * can't fail (must've been static or stack).
		 */
		temp_alloc_var = alloc_expr;
	}
	if (release_stmt) cast_block_add_stmt(alloc_block, release_stmt);
	cast_block_add_stmt(alloc_block,
			    cast_new_stmt_expr(
				    cast_new_expr_assign(pexpr,
							 temp_alloc_var)));
	if (set_alen_stmt) cast_block_add_stmt(alloc_block, set_alen_stmt);
	if (set_release_stmt)
		cast_block_add_stmt(alloc_block, set_release_stmt);
	
	/* Now decide when that statement should be invoked. */
	if (alloc->flags & PRES_C_ALLOC_ALWAYS) {
		/*
		 * If there was no reason for the block statement (no temp var
		 * declared), just ``absorb'' the block into the current block.
		 * There is no particular reason for this, other than it makes
		 * the code prettier.
		 */
		assert(alloc_block_stmt->kind == CAST_STMT_BLOCK);
		if (temp_alloc_var == alloc_expr) {
			absorb_stmt(alloc_block_stmt);
		} else
			add_stmt(alloc_block_stmt);
		
	} else {
		cast_expr if_test = 0;
		
		if (alloc->flags & PRES_C_ALLOC_IF_NULL)
			if_test = if_or(if_test,
					cast_new_unary_expr(CAST_UNARY_LNOT,
							    pexpr));
		if (alloc->flags & PRES_C_ALLOC_IF_TOO_SMALL)
			assert(0); /* XXX */
		if (alloc->flags & PRES_C_ALLOC_IF_TOO_LARGE)
			assert(0); /* XXX */
		
		assert(if_test != 0);
		
		add_stmt(cast_new_if(if_test, alloc_block_stmt, 0));
		
	}
}

/*
 * This helper function simply returns the name of the allocator function for
 * a given `pres_c_allocation'.
 */
const char *mu_state::get_allocator(pres_c_allocation *palloc)
{
	/* Find the appropriate allocation case, and make sure it's valid. */
	pres_c_allocation_u *ualloc = &palloc->cases[current_param_dir];
	assert(ualloc->allow != PRES_C_ALLOCATION_INVALID);
	pres_c_allocation_case *alloc = &(ualloc->pres_c_allocation_u_u.val);
	
	if (alloc->flags & PRES_C_ALLOC_ALWAYS)
		switch (alloc->allocator.kind) {
		case PRES_C_ALLOCATOR_DONTCARE:
		case PRES_C_ALLOCATOR_STATIC:
			/* Name of the default (stack) allocator. */
			return "auto_flick_alloc";
		case PRES_C_ALLOCATOR_OUTOFLINE:
			return flick_asprintf(
				"%s_flick_alloc",
				alloc->allocator.pres_c_allocator_u.ool_name);
		case PRES_C_ALLOCATOR_NAME:
			return flick_asprintf(
				"%s_flick_alloc",
				alloc->allocator.pres_c_allocator_u.name);
		default:
			panic(("In `mu_state::get_allocator', "
			       "unknown allocator kind (%d)."),
			      alloc->allocator.kind);
			break;
		}
	
	return "null_flick_alloc";
}

/*
 * This helper function simply returns the name of the allocator function for
 * a given `pres_c_allocation'.
 */
const char *mu_state::get_abort_deallocator(pres_c_allocation *palloc)
{
	/* Find the appropriate allocation case, and make sure it's valid. */
	pres_c_allocation_u *ualloc = &palloc->cases[current_param_dir];
	assert(ualloc->allow != PRES_C_ALLOCATION_INVALID);
	pres_c_allocation_case *alloc = &(ualloc->pres_c_allocation_u_u.val);
	
	if (alloc->flags & PRES_C_ALLOC_ALWAYS)
		switch (alloc->allocator.kind) {
		case PRES_C_ALLOCATOR_DONTCARE:
		case PRES_C_ALLOCATOR_STATIC:
			/* Name of the default (stack) deallocator. */
			return "auto_flick_free";
		case PRES_C_ALLOCATOR_OUTOFLINE:
			return flick_asprintf(
				"%s_flick_free",
				alloc->allocator.pres_c_allocator_u.ool_name);
		case PRES_C_ALLOCATOR_NAME:
			return flick_asprintf(
				"%s_flick_free",
				alloc->allocator.pres_c_allocator_u.name);
		default:
			panic(("In `mu_state::get_abort_deallocator', "
			       "unknown allocator kind (%d)."),
			      alloc->allocator.kind);
			break;
		}
	
	return "null_flick_free";
}

/*
 * This helper function simply returns the appropriate allocator for
 * a given `pres_c_allocation' (as opposed to the allocator _function_).
 */
pres_c_allocator mu_state::get_allocator_kind(pres_c_allocation *alloc)
{
	assert(alloc->cases[current_param_dir].allow
	       == PRES_C_ALLOCATION_ALLOW);
	return alloc->cases[current_param_dir].pres_c_allocation_u_u.val.
		allocator;
}

/*
 * This helper function simply returns the appropriate allocation flags for
 * a given `pres_c_allocation'.
 */
pres_c_alloc_flags mu_state::get_allocator_flags(pres_c_allocation *alloc)
{
	assert(alloc->cases[current_param_dir].allow
	       == PRES_C_ALLOCATION_ALLOW);
	return alloc->cases[current_param_dir].pres_c_allocation_u_u.val.flags;
}

/*
 * Find the legnth we will use to allocate or deallocate the data.
 */
cast_expr mu_state::get_allocation_length(char *cname,
					  pres_c_allocation *palloc)
{
	/* Find the appropriate allocation case, and make sure it's valid. */
	pres_c_allocation_u *ualloc = &palloc->cases[current_param_dir];
	assert(ualloc->allow != PRES_C_ALLOCATION_INVALID);
	pres_c_allocation_case *alloc = &(ualloc->pres_c_allocation_u_u.val);
	
	/*
	 * The argument `min_alloc_len' specifies the minimum allocated length
	 * for the array.  We guarantee that we will never make an allocation
	 * for fewer elements than this.  If it doesn't exist, it is assumed to
	 * be zero.
	 *
	 * The argument `max_alloc_len' specifies the maximum allocated length
	 * for the array.  We guarantee that we will never make an allocation
	 * for more elements than this.  If it doesn't exist, it is assumed to
	 * be the maximum bound of the array.
	 */
	cast_expr length_expr;
	cast_type length_type;
	cast_expr allocmin_expr;
	cast_type allocmin_type;
	cast_expr allocmax_expr;
	cast_type allocmax_type;
	cast_expr min_expr;
	cast_expr max_expr;
	mu_array_get_pres_length(cname, &length_expr, &length_type);
	
	int gotarg;
	unsigned int len_min, len_max;
	mu_array_get_pres_bounds(&len_min, &min_expr, &len_max, &max_expr,
				 cname);
	
	gotarg = arglist->getargs(cname, "min_alloc_len",
				  &allocmin_expr, &allocmin_type);
	assert(gotarg);
	gotarg = arglist->getargs(cname, "max_alloc_len",
				  &allocmax_expr, &allocmax_type);
	assert(gotarg);
	/*
	 * XXX - The `max_alloc_len' slot is not yet supported.  The exact
	 * semantic details of this value haven't been nailed down yet.
	 */
	assert(!allocmax_expr);assert(!allocmax_type);
	
	if (allocmin_expr) {
		if (max_expr
		    && cast_cmp_expr(allocmin_expr, max_expr) == 0) {
			/*
			 * Here, `min_alloc_len' is the same as the array's
			 * maximum bound.  Since the length can never be larger
			 * than the bound, we never need to allocate more than
			 * `allocmin_expr' elements.  Note, however, that it
			 * could still be a variable value.
			 */
			length_expr = allocmin_expr;
		} else {
			/*
			 * We need to figure out which length we should use.
			 */
			assert(0);
			length_expr
				= cast_new_expr_cond(
					cast_new_binary_expr(
						CAST_BINARY_LT,
						length_expr,
						allocmin_expr),
					allocmin_expr,
					length_expr);
		}
	}
	
	/* Check for possible stack allocation. */
	if (alloc->allocator.kind == PRES_C_ALLOCATOR_DONTCARE
	    || alloc->allocator.kind == PRES_C_ALLOCATOR_OUTOFLINE) {
		/*
		 * Optimization: If we have low variance in the length (for a
		 * bounded array), it's probably not bad to over-allocate some
		 * space on the stack, versus wasting time allocating the exact
		 * length required.  Non-stack allocation is not affected by
		 * this optimization.
		 *
		 * XXX - This check for MAX_PREALLOC_LEN should probably be
		 * based on *SIZE* rather than *LENGTH*; an array of 1MB
		 * structs just isn't going to fit on the stack!
		 */
		if ((len_max - len_min) <= MAX_PREALLOC_LEN) {
			assert(max_expr);
			length_expr = max_expr;
		}
	}
	
	assert(length_expr);
	return length_expr;
}

/* End of file. */



syntax highlighted by Code2HTML, v. 0.9.1