/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2004-2017. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */
/*
 * GC support procedures
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define ERL_WANT_GC_INTERNALS__

#include "global.h"

#include "erl_gc.h"

#include "hipe_stack.h"
#include "hipe_gc.h"

Eterm *fullsweep_nstack(Process *p, Eterm *n_htop)
{
    /* known nstack walk state */
    Eterm *nsp;
    Eterm *nsp_end;
    const struct hipe_sdesc *sdesc;
    unsigned int sdesc_size;
    unsigned long ra;
    unsigned int i;
    unsigned int mask;
    /* arch-specific nstack walk state */
    struct nstack_walk_state walk_state;

    ASSERT(!p->hipe.gc_is_unsafe);

    if (!p->hipe.nstack) {
	ASSERT(!p->hipe.nsp && !p->hipe.nstend);
	return n_htop;
    }
    if (!nstack_walk_init_check(p))
	return n_htop;

    ASSERT(p->hipe.nsp && p->hipe.nstend);
    nsp = nstack_walk_nsp_begin(p);
    nsp_end = p->hipe.nstgraylim;
    if (nsp_end)
	nstack_walk_kill_trap(p, nsp_end);
    nsp_end = nstack_walk_nsp_end(p);

    sdesc = nstack_walk_init_sdesc(p, &walk_state);

    for (;;) {
	if (nstack_walk_nsp_reached_end(nsp, nsp_end)) {
	    if (nsp == nsp_end) {
		if (nsp) {
		    /* see the HIGH_WATER update in fullsweep_heap() */
		    p->hipe.nstblacklim = nsp; /* nsp == nsp_end */
		    nstack_walk_update_trap(p, walk_state.sdesc0);
		}
		return n_htop;
	    }
	    fprintf(stderr, "%s: passed end of stack\r\n", __FUNCTION__);
	    break;
	}
	sdesc_size = nstack_walk_frame_size(sdesc);
	i = 0;
	mask = sdesc->livebits[0];
	for (;;) {
	    if (mask & 1) {
		Eterm *nsp_i = nstack_walk_frame_index(nsp, i);
		Eterm gval = *nsp_i;
		if (is_boxed(gval)) {
		    Eterm *ptr = boxed_val(gval);
		    Eterm val = *ptr;
		    if (IS_MOVED_BOXED(val)) {
			ASSERT(is_boxed(val));
			*nsp_i = val;
		    } else if (!erts_is_literal(gval, ptr)) {
			move_boxed(&ptr, val, &n_htop, nsp_i);
		    }
		} else if (is_list(gval)) {
		    Eterm *ptr = list_val(gval);
		    Eterm val = *ptr;
		    if (IS_MOVED_CONS(val)) {
			*nsp_i = ptr[1];
		    } else if (!erts_is_literal(gval, ptr)) {
			ASSERT(erts_dbg_within_proc(ptr, p, NULL));
			move_cons(&ptr, val, &n_htop, nsp_i);
		    }
		}
	    }
	    if (++i >= sdesc_size)
		break;
	    if (i & 31)
		mask >>= 1;
	    else
		mask = sdesc->livebits[i >> 5];
	}
	ra = nstack_walk_frame_ra(nsp, sdesc);
	sdesc = hipe_find_sdesc(ra);
	nsp = nstack_walk_next_frame(nsp, sdesc_size);
    }
    abort();
}

void gensweep_nstack(Process *p, Eterm **ptr_old_htop, Eterm **ptr_n_htop)
{
    /* known nstack walk state */
    Eterm *nsp;
    Eterm *nsp_end;
    const struct hipe_sdesc *sdesc;
    unsigned int sdesc_size;
    unsigned long ra;
    unsigned int i;
    unsigned int mask;
    /* arch-specific nstack walk state */
    struct nstack_walk_state walk_state;
    char *oh;
    Uint oh_size;

    /* gensweep-specific state */
    Eterm *old_htop, *n_htop;
    char *mature;
    Uint mature_size;

    ASSERT(!p->hipe.gc_is_unsafe);

    if (!p->hipe.nstack) {
	ASSERT(!p->hipe.nsp && !p->hipe.nstend);
	return;
    }
    if (!nstack_walk_init_check(p))
	return;

    ASSERT(p->hipe.nsp && p->hipe.nstend);
    nsp = nstack_walk_nsp_begin(p);
    nsp_end = p->hipe.nstgraylim;
    if (nsp_end) {
	/* if gray limit passed black limit, reset black limit */
	if (nstack_walk_gray_passed_black(nsp_end, p->hipe.nstblacklim))
	    p->hipe.nstblacklim = nsp_end;
	nstack_walk_kill_trap(p, nsp_end);
	nsp_end = p->hipe.nstblacklim;
    } else
	nsp_end = nstack_walk_nsp_end(p);

    sdesc = nstack_walk_init_sdesc(p, &walk_state);

    old_htop = *ptr_old_htop;
    n_htop = *ptr_n_htop;
    mature = (char *) (p->abandoned_heap ? p->abandoned_heap : p->heap);
    mature_size = (char*)HIGH_WATER(p) - mature;
    oh = (char*)OLD_HEAP(p);
    oh_size = (char*)OLD_HTOP(p) - oh;

    for (;;) {
	if (nstack_walk_nsp_reached_end(nsp, nsp_end)) {
	    if (nsp == nsp_end) {
		*ptr_old_htop = old_htop;
		*ptr_n_htop = n_htop;
		if (nsp) {
		    /* see the HIGH_WATER update in gen_gc() */
		    if (HEAP_START(p) != HIGH_WATER(p)) {
			p->hipe.nstblacklim =
			    p->hipe.nstgraylim
			    ? p->hipe.nstgraylim
			    : nsp; /* nsp == nsp_end */
		    } else {
			/* blacklim = graylim ? blacklim : end */
			if (!p->hipe.nstgraylim)
			    p->hipe.nstblacklim = nsp; /* nsp == nsp_end */
		    }
		    nstack_walk_update_trap(p, walk_state.sdesc0);
		}
		return;
	    }
	    fprintf(stderr, "%s: passed end of stack\r\n", __FUNCTION__);
	    break;
	}
	sdesc_size = nstack_walk_frame_size(sdesc);
	i = 0;
	mask = sdesc->livebits[0];
	for (;;) {
	    if (mask & 1) {
		Eterm *nsp_i = nstack_walk_frame_index(nsp, i);
		Eterm gval = *nsp_i;
		if (is_boxed(gval)) {
		    Eterm *ptr = boxed_val(gval);
		    Eterm val = *ptr;
		    if (IS_MOVED_BOXED(val)) {
			ASSERT(is_boxed(val));
			*nsp_i = val;
		    } else if (ErtsInArea(ptr, mature, mature_size)) {
			move_boxed(&ptr, val, &old_htop, nsp_i);
		    } else if (ErtsInYoungGen(gval, ptr, oh, oh_size)) {
			ASSERT(erts_dbg_within_proc(ptr, p, NULL));
			move_boxed(&ptr, val, &n_htop, nsp_i);
		    }
		} else if (is_list(gval)) {
		    Eterm *ptr = list_val(gval);
		    Eterm val = *ptr;
		    if (IS_MOVED_CONS(val)) {
			*nsp_i = ptr[1];
		    } else if (ErtsInArea(ptr, mature, mature_size)) {
			move_cons(&ptr, val, &old_htop, nsp_i);
		    } else if (ErtsInYoungGen(gval, ptr, oh, oh_size)) {
			ASSERT(erts_dbg_within_proc(ptr, p, NULL));
			move_cons(&ptr, val, &n_htop, nsp_i);
		    }
		}
	    }
	    if (++i >= sdesc_size)
		break;
	    if (i & 31)
		mask >>= 1;
	    else
		mask = sdesc->livebits[i >> 5];
	}
	ra = nstack_walk_frame_ra(nsp, sdesc);
	sdesc = hipe_find_sdesc(ra);
	nsp = nstack_walk_next_frame(nsp, sdesc_size);
    }
    abort();
}

Eterm *sweep_literals_nstack(Process *p, Eterm *old_htop, char *area,
			     Uint area_size)
{
    /* known nstack walk state */
    Eterm *nsp;
    Eterm *nsp_end;
    const struct hipe_sdesc *sdesc;
    /* arch-specific nstack walk state */
    struct nstack_walk_state walk_state;

    ASSERT(!p->hipe.gc_is_unsafe);

    if (!p->hipe.nstack) {
	ASSERT(!p->hipe.nsp && !p->hipe.nstend);
	return old_htop;
    }
    if (!nstack_walk_init_check(p))
	return old_htop;

    ASSERT(p->hipe.nsp && p->hipe.nstend);
    nsp = nstack_walk_nsp_begin(p);
    nsp_end = nstack_walk_nsp_end(p);
    sdesc = nstack_walk_init_sdesc_ignore_trap(p, &walk_state);

    while (!nstack_walk_nsp_reached_end(nsp, nsp_end)) {
	unsigned long ra;
	unsigned sdesc_size = nstack_walk_frame_size(sdesc);
	unsigned i = 0;
	unsigned mask = sdesc->livebits[0];
	for (;;) {
	    if (mask & 1) {
		Eterm *nsp_i = nstack_walk_frame_index(nsp, i);
		Eterm gval = *nsp_i;
		if (is_boxed(gval)) {
		    Eterm *ptr = boxed_val(gval);
		    Eterm val = *ptr;
		    if (IS_MOVED_BOXED(val)) {
			ASSERT(is_boxed(val));
			*nsp_i = val;
		    } else if (ErtsInArea(ptr, area, area_size)) {
			move_boxed(&ptr, val, &old_htop, nsp_i);
		    }
		} else if (is_list(gval)) {
		    Eterm *ptr = list_val(gval);
		    Eterm val = *ptr;
		    if (IS_MOVED_CONS(val)) {
			*nsp_i = ptr[1];
		    } else if (ErtsInArea(ptr, area, area_size)) {
			move_cons(&ptr, val, &old_htop, nsp_i);
		    }
		}
	    }
	    if (++i >= sdesc_size)
		break;
	    if (i & 31)
		mask >>= 1;
	    else
		mask = sdesc->livebits[i >> 5];
	}
	ra = nstack_walk_frame_ra(nsp, sdesc);
	if (ra == (unsigned long)nbif_stack_trap_ra)
	    ra = (unsigned long)p->hipe.ngra;
	sdesc = hipe_find_sdesc(ra);
	nsp = nstack_walk_next_frame(nsp, sdesc_size);
    }
    return old_htop;
}

int
nstack_any_heap_ref_ptrs(Process *rp, char* mod_start, Uint mod_size)
{
    Eterm *nsp;
    Eterm *nsp_end;
    const struct hipe_sdesc *sdesc;
    /* arch-specific nstack walk state */
    struct nstack_walk_state walk_state;

    ASSERT(!rp->hipe.gc_is_unsafe);

    if (!rp->hipe.nstack || !nstack_walk_init_check(rp)) return 0;
    ASSERT(rp->hipe.nsp && rp->hipe.nstend);
    nsp = nstack_walk_nsp_begin(rp);
    nsp_end = nstack_walk_nsp_end(rp);
    sdesc = nstack_walk_init_sdesc_ignore_trap(rp, &walk_state);

    while (!nstack_walk_nsp_reached_end(nsp, nsp_end)) {
	unsigned long ra;
	unsigned sdesc_size = nstack_walk_frame_size(sdesc);
	unsigned i = 0;
	unsigned mask = sdesc->livebits[0];
	for (;;) {
	    if (mask & 1) {
		Eterm *nsp_i = nstack_walk_frame_index(nsp, i);
		Eterm val = *nsp_i;
		switch (primary_tag(val)) {
		case TAG_PRIMARY_BOXED:
		case TAG_PRIMARY_LIST:
		    if (ErtsInArea(val, mod_start, mod_size)) {
			return 1;
		    }
		    break;
		}
	    }
	    if (++i >= sdesc_size)
		break;
	    if (i & 31)
		mask >>= 1;
	    else
		mask = sdesc->livebits[i >> 5];
	}
	ra = nstack_walk_frame_ra(nsp, sdesc);
	if (ra == (unsigned long)nbif_stack_trap_ra)
	    ra = (unsigned long)rp->hipe.ngra;
	sdesc = hipe_find_sdesc(ra);
	nsp = nstack_walk_next_frame(nsp, sdesc_size);
    }
    return 0;
}

int
nstack_any_cps_in_segment(Process *p, char* seg_start, Uint seg_size)
{
    Eterm *nsp;
    Eterm *nsp_end;
    const struct hipe_sdesc *sdesc;
    /* arch-specific nstack walk state */
    struct nstack_walk_state walk_state;

    if (!p->hipe.nstack || !nstack_walk_init_check(p))
	return 0;
    ASSERT(p->hipe.nsp && p->hipe.nstend);
    nsp = nstack_walk_nsp_begin(p);
    nsp_end = nstack_walk_nsp_end(p);
    sdesc = nstack_walk_init_sdesc_ignore_trap(p, &walk_state);

    /* Check the topmost frame */
    if (ErtsInArea(sdesc->bucket.hvalue, seg_start, seg_size))
	return 1;

    while (!nstack_walk_nsp_reached_end(nsp, nsp_end)) {
	unsigned sdesc_size = nstack_walk_frame_size(sdesc);
	unsigned long ra = nstack_walk_frame_ra(nsp, sdesc);
	if (ra == (unsigned long)nbif_stack_trap_ra)
	    ra = (unsigned long)p->hipe.ngra;
	if (ErtsInArea(ra, seg_start, seg_size))
            return 1;
        sdesc = hipe_find_sdesc(ra);
        nsp = nstack_walk_next_frame(nsp, sdesc_size);
    }
    return 0;
}
