/* * 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 #include #include /* * 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. */