/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  Near heap expansion routines.
*
****************************************************************************/


//#include "dll.h"        // needs to be first
#include "variety.h"
#include <stddef.h>
#include <malloc.h>
#include "heap.h"
#include "heapacc.h"
#if defined(__DOS_EXT__)
 #include "extender.h"
#endif

#if defined(__SMALL_DATA__)

_WCRTLINK void *_expand( void *stg, size_t amount )
{
    return( _nexpand( stg, amount ) );
}

#endif

#if defined(__AXP__) || defined(__PPC__)
    #define _SEGMENT int
#else
    #define _SEGMENT __segment
#endif

int __HeapManager_expand( _SEGMENT seg,
                          unsigned offset,
                          size_t req_size,
                          size_t *growth_size )
{
    #if defined(M_I86)
        typedef struct freelistp __based(seg) *fptr;
        typedef char __based(void) *cptr;

        struct miniheapblkp __based(seg) *hblk;
    #else
        typedef struct freelistp _WCNEAR *fptr;
        typedef char _WCNEAR *cptr;

        mheapptr hblk;
    #endif
    fptr        p1;
    fptr        p2;
    fptr        pnext;
    fptr        pprev;
    size_t      new_size;
    size_t      old_size;
    size_t      free_size;

    /* round (new_size + tag) to multiple of pointer size */
    new_size = (req_size + TAG_SIZE + ROUND_SIZE) & ~ROUND_SIZE;
    if( new_size < req_size ) new_size = ~0; //go for max
    if( new_size < FRL_SIZE ) {
        new_size = FRL_SIZE;
    }
    p1 = (fptr) ((cptr)offset - TAG_SIZE);
    old_size = p1->len & ~1;
    if( new_size > old_size ) {
        /* enlarging the current allocation */
        p2 = (fptr) ((cptr)p1 + old_size);
        *growth_size = new_size - old_size;
        for(;;) {
            free_size = p2->len;
            if( p2->len == END_TAG ) {
                return( __HM_TRYGROW );
            } else if( free_size & 1 ) { /* next piece is allocated */
                break;
            } else {
                pnext = p2->next;
                pprev = p2->prev;

                if( seg == _DGroup() ) { // near heap
                    for( hblk = __nheapbeg; hblk->next; hblk = hblk->next ) {
                        if( (fptr)hblk <= (fptr)offset &&
                            (fptr)((PTR)hblk+hblk->len) > (fptr)offset ) break;
                    }
                }
                #if defined(M_I86)
                    else {      // Based heap
                        hblk = 0;
                    }
                #endif

                if( hblk->rover == p2 ) { /* 09-feb-91 */
                    hblk->rover = p2->prev;
                }
                if( free_size < *growth_size  ||
                    free_size - *growth_size < FRL_SIZE ) {
                    /* unlink small free block */
                    pprev->next = pnext;
                    pnext->prev = pprev;
                    p1->len += free_size;
                    hblk->numfree--;
                    if( free_size >= *growth_size ) {
                        return( __HM_SUCCESS );
                    }
                    *growth_size -= free_size;
                    p2 = (fptr) ((cptr)p2 + free_size);
                } else {
                    p2 = (fptr) ((cptr)p2 + *growth_size);
                    p2->len = free_size - *growth_size;
                    p2->prev = pprev;
                    p2->next = pnext;
                    pprev->next = p2;
                    pnext->prev = p2;
                    p1->len += *growth_size;
                    return( __HM_SUCCESS );
                }
            }
        }
        /* no suitable free blocks behind, have to move block */
        return( __HM_FAIL );
    } else {
        /* shrinking the current allocation */
        if( old_size - new_size >= FRL_SIZE ) {
            /* block big enough to split */
            p1->len = new_size | 1;
            p1 = (fptr) ((cptr)p1 + new_size);
            p1->len = (old_size - new_size) | 1;
            if( seg == _DGroup() ) { // near heap
                for( hblk = __nheapbeg; hblk->next; hblk = hblk->next ) {
                    if( (fptr)hblk <= (fptr)offset &&
                        (fptr)((PTR)hblk+hblk->len) > (fptr)offset ) break;
                }
            }
            #if defined(M_I86)
                else    // Based heap
                    hblk = 0;
            #endif
            /* _bfree will decrement 'numalloc' 08-jul-91 */
            hblk->numalloc++;
            #if defined(M_I86)
                _bfree( seg, (cptr)p1 + TAG_SIZE );
                /* free the top portion */
            #else
                _nfree( (cptr)p1 + TAG_SIZE );
            #endif
        }
    }
    return( __HM_SUCCESS );
}


_WCRTLINK void _WCNEAR *_nexpand( void _WCNEAR *stg, size_t req_size )
    {
        struct {
            unsigned expanded : 1;
        } flags;
        int retval;
        size_t growth_size;

        flags.expanded = 0;
        _AccessNHeap();
        for( ;; ) {
            retval = __HeapManager_expand( _DGroup(),
                                           (unsigned) stg,
                                           req_size,
                                           &growth_size );
            if( retval == __HM_SUCCESS ) {
                _ReleaseNHeap();
                return( stg );
            }
            if( retval == __HM_FAIL || !__IsCtsNHeap() ) break;
            if( retval == __HM_TRYGROW ) {
                if( flags.expanded ) break;
                if( __ExpandDGROUP( growth_size ) == 0 ) {
                    break;
                }
                flags.expanded = 1;
            }
        }
        _ReleaseNHeap();
        return( NULL );
    }