
/* Copyright (c) 1982 Regents of the University of California */

static char sccsid[] = "@(#)runtime.c 1.9 8/14/83";

/*
 * Runtime organization dependent routines, mostly dealing with
 * activation records.
 */

#include "defs.h"
#include "runtime.h"
#include "process.h"
#include "machine.h"
#include "events.h"
#include "mappings.h"
#include "symbols.h"
#include "tree.h"
#include "eval.h"
#include "operators.h"
#include "object.h"
#include <sys/param.h>

#ifndef public
typedef struct Frame *Frame;

#include "machine.h"
#endif

#define NSAVEREG 12

struct Frame {
    Integer condition_handler;
    Integer mask;
    Address save_ap;		/* argument pointer */
    Address save_fp;		/* frame pointer */
    Address save_pc;		/* program counter */
    Word save_reg[NSAVEREG];	/* not necessarily there */
};

private Boolean walkingstack = false;

/*
 * Set a frame to the current activation record.
 */

private getcurframe(frp)
register Frame frp;
{
    register int i;

    checkref(frp);
    frp->mask = reg(NREG);
    frp->save_ap = reg(ARGP);
    frp->save_fp = reg(FRP);
    frp->save_pc = reg(PROGCTR) + 1;
    for (i = 0; i < NSAVEREG; i++) {
	frp->save_reg[i] = reg(i);
    }
}

/*
 * Return a pointer to the next activation record up the stack.
 * Return nil if there is none.
 * Writes over space pointed to by given argument.
 */

#define bis(b, n) ((b & (1 << (n))) != 0)

private Frame nextframe(frp)
Frame frp;
{
    register Frame newfrp;
    struct Frame frame;
    register Integer i, j, mask;
    Address prev_frame, callpc; 
    static Integer ntramp = 0;

    newfrp = frp;
    prev_frame = frp->save_fp;

/*
 *  The check for interrupt generated frames is taken from adb with only
 *  partial understanding.  If you're in "sub" and on a sigxxx "sigsub"
 *  gets control, then the stack does NOT look like <main, sub, sigsub>.
 *
 *  As best I can make out it looks like:
 *
 *     <main, (machine check exception block + sub), sysframe, sigsub>.
 *
 *  When the signal occurs an exception block and a frame for the routine
 *  in which it occured are pushed on the user stack.  Then another frame
 *  is pushed corresponding to a call from the kernel to sigsub.
 *
 *  The addr in sub at which the exception occured is not in sub.save_pc
 *  but in the machine check exception block.  It is at the magic address
 *  fp + 84.
 *
 *  The current approach ignores the sys_frame (what adb reports as sigtramp)
 *  and takes the pc for sub from the exception block.  This allows the
 *  "where" command to report <main, sub, sigsub>, which seems reasonable.
 */

nextf:
    dread(&frame, prev_frame, sizeof(struct Frame));
    if (ntramp == 1) {
	dread(&callpc, prev_frame + 84, sizeof(callpc));
    } else {
	callpc = frame.save_pc;
    }
    if (frame.save_fp == nil) {
	newfrp = nil;
    } else if (callpc > 0x80000000 - 0x200 * UPAGES ) {
	 ntramp++;
	 prev_frame = frame.save_fp;
	 goto nextf;
    } else {
	frame.save_pc = callpc;
        ntramp = 0;
	mask = ((frame.mask >> 16) & 0x0fff);
	j = 0;
	for (i = 0; i < NSAVEREG; i++) {
	    if (bis(mask, i)) {
		newfrp->save_reg[i] = frame.save_reg[j];
		++j;
	    }
	}
	newfrp->condition_handler = frame.condition_handler;
	newfrp->mask = mask;
	newfrp->save_ap = frame.save_ap;
	newfrp->save_fp = frame.save_fp;
	newfrp->save_pc = frame.save_pc;
    }
    return newfrp;
}

/*
 * Return the frame associated with the given function.
 * If the function is nil, return the most recently activated frame.
 *
 * Static allocation for the frame.
 */

public Frame findframe(f)
Symbol f;
{
    register Frame frp;
    static struct Frame frame;
    Symbol p;
    Boolean done;

    frp = &frame;
    getcurframe(frp);
    if (f != nil) {
	done = false;
	do {
	    p = whatblock(frp->save_pc);
	    if (p == f) {
		done = true;
	    } else if (p == program) {
		done = true;
		frp = nil;
	    } else {
		frp = nextframe(frp);
		if (frp == nil) {
		    done = true;
		}
	    }
	} while (not done);
    }
    return frp;
}

/*
 * Find the return address of the current procedure/function.
 */

public Address return_addr()
{
    Frame frp;
    Address addr;
    struct Frame frame;

    frp = &frame;
    getcurframe(frp);
    frp = nextframe(frp);
    if (frp == nil) {
	addr = 0;
    } else {
	addr = frp->save_pc;
    }
    return addr;
}

/*
 * Push the value associated with the current function.
 */

public pushretval(len, isindirect)
Integer len;
Boolean isindirect;
{
    Word r0;

    r0 = reg(0);
    if (isindirect) {
	rpush((Address) r0, len);
    } else {
	switch (len) {
	    case sizeof(char):
		push(char, r0);
		break;

	    case sizeof(short):
		push(short, r0);
		break;

	    default:
		if (len == sizeof(Word)) {
		    push(Word, r0);
		} else if (len == 2*sizeof(Word)) {
		    push(Word, r0);
		    push(Word, reg(1));
		} else {
		    panic("not indirect in pushretval?");
		}
		break;
	}
    }
}

/*
 * Return the base address for locals in the given frame.
 */

public Address locals_base(frp)
register Frame frp;
{
    return (frp == nil) ? reg(FRP) : frp->save_fp;
}

/*
 * Return the base address for arguments in the given frame.
 */

public Address args_base(frp)
register Frame frp;
{
    return (frp == nil) ? reg(ARGP) : frp->save_ap;
}

/*
 * Return saved register n from the given frame.
 */

public Word savereg(n, frp)
register Integer n;
register Frame frp;
{
    register Word w;

    if (frp == nil) {
	w = reg(n);
    } else {
	switch (n) {
	    case ARGP:
		w = frp->save_ap;
		break;

	    case FRP:
		w = frp->save_fp;
		break;

	    case STKP:
		w = reg(STKP);
		break;

	    case PROGCTR:
		w = frp->save_pc;
		break;

	    default:
		assert(n >= 0 and n < NSAVEREG);
		w = frp->save_reg[n];
		break;
	}
    }
    return w;
}

/*
 * Return the nth argument to the current procedure.
 */

public Word argn(n, frp)
Integer n;
Frame frp;
{
    Word w;

    dread(&w, args_base(frp) + (n * sizeof(Word)), sizeof(w));
    return w;
}

/*
 * Calculate the entry address for a procedure or function parameter,
 * given the address of the descriptor.
 */

public Address fparamaddr(a)
Address a;
{
    Address r;

    dread(&r, a, sizeof(r));
    return r;
}

/*
 * Print a list of currently active blocks starting with most recent.
 */

public wherecmd()
{
    walkstack(false);
}

/*
 * Dump the world to the given file.
 * Like "where", but variables are dumped also.
 */

public dump()
{
    walkstack(true);
}

/*
 * Walk the stack of active procedures printing information
 * about each active procedure.
 */

private walkstack(dumpvariables)
Boolean dumpvariables;
{
    register Frame frp;
    register Symbol f;
    register Boolean save;
    register Lineno line;
    struct Frame frame;

    if (notstarted(process)) {
	error("program is not active");
    } else {
	save = walkingstack;
	walkingstack = true;
	frp = &frame;
	getcurframe(frp);
	f = whatblock(frp->save_pc);
	do {
	    printf("%s", symname(f));
	    if (not isinline(f)) {
		printparams(f, frp);
	    }
	    line = srcline(frp->save_pc - 1);
	    if (line != 0) {
		printf(", line %d", line);
		printf(" in \"%s\"\n", srcfilename(frp->save_pc - 1));
	    } else {
		printf(" at 0x%x\n", frp->save_pc);
	    }
	    if (dumpvariables) {
		dumpvars(f, frp);
		putchar('\n');
	    }
	    if (isinline(f)) {
		f = container(f);
	    } else {
		frp = nextframe(frp);
		if (frp != nil) {
		    f = whatblock(frp->save_pc);
		}
	    }
	} while (frp != nil and f != program);
	if (dumpvariables) {
	    printf("in \"%s\":\n", symname(program));
	    dumpvars(program, nil);
	    putchar('\n');
	}
	walkingstack = save;
    }
}

/*
 * Find the entry point of a procedure or function.
 */

public findbeginning(f)
Symbol f;
{
    f->symvalue.funcv.beginaddr += 2;
}

/*
 * Return the address corresponding to the first line in a function.
 */

public Address firstline(f)
Symbol f;
{
    Address addr;

    addr = codeloc(f);
    while (linelookup(addr) == 0 and addr < objsize) {
	++addr;
    }
    if (addr == objsize) {
	addr = -1;
    }
    return addr;
}

/*
 * Catcher drops strike three ...
 */

public runtofirst()
{
    Address addr;

    addr = pc;
    while (linelookup(addr) == 0 and addr < objsize) {
	++addr;
    }
    if (addr < objsize) {
	stepto(addr);
    }
}

/*
 * Return the address corresponding to the end of the program.
 *
 * We look for the entry to "exit".
 */

public Address lastaddr()
{
    register Symbol s;

    s = lookup(identname("exit", true));
    if (s == nil) {
	panic("can't find exit");
    }
    return codeloc(s);
}

/*
 * Decide if the given function is currently active.
 *
 * We avoid calls to "findframe" during a stack trace for efficiency.
 * Presumably information evaluated while walking the stack is active.
 */

public Boolean isactive(f)
Symbol f;
{
    register Boolean b;

    if (isfinished(process)) {
	b = false;
    } else {
	if (walkingstack or f == program or
	  (ismodule(f) and isactive(container(f)))) {
	    b = true;
	} else {
	    b = (Boolean) (findframe(f) != nil);
	}
    }
    return b;
}

/*
 * Evaluate a call to a procedure.
 */

public callproc(procnode, arglist)
Node procnode;
Node arglist;
{
    Symbol proc;
    Integer argc;

    if (procnode->op != O_SYM) {
	beginerrmsg();
	fprintf(stderr, "can't call \"");
	prtree(stderr, procnode);
	fprintf(stderr, "\"");
	enderrmsg();
    }
    assert(procnode->op == O_SYM);
    proc = procnode->value.sym;
    if (not isblock(proc)) {
	error("\"%s\" is not a procedure or function", symname(proc));
    }
    pushenv();
    pc = codeloc(proc);
    argc = pushargs(proc, arglist);
    beginproc(proc, argc);
    isstopped = true;
    event_once(build(O_EQ, build(O_SYM, pcsym), build(O_SYM, retaddrsym)),
	buildcmdlist(build(O_PROCRTN, proc)));
    cont();
    /* NOTREACHED */
}

/*
 * Push the arguments on the process' stack.  We do this by first
 * evaluating them on the "eval" stack, then copying into the process'
 * space.
 */

private Integer pushargs(proc, arglist)
Symbol proc;
Node arglist;
{
    Stack *savesp;
    int argc, args_size;

    savesp = sp;
    argc = evalargs(proc, arglist);
    args_size = sp - savesp;
    setreg(STKP, reg(STKP) - args_size);
    dwrite(savesp, reg(STKP), args_size);
    sp = savesp;
    return argc;
}

/*
 * Evaluate arguments left-to-right.
 */

private Integer evalargs(proc, arglist)
Symbol proc;
Node arglist;
{
    Node p, exp;
    Symbol arg;
    Stack *savesp;
    Address addr;
    Integer count;

    savesp = sp;
    count = 0;
    arg = proc->chain;
    for (p = arglist; p != nil; p = p->value.arg[1]) {
	if (p->op != O_COMMA) {
	    panic("evalargs: arglist missing comma");
	}
	if (arg == nil) {
	    sp = savesp;
	    error("too many parameters to %s", symname(proc));
	}
	exp = p->value.arg[0];
	if (not compatible(arg->type, exp->nodetype)) {
	    sp = savesp;
	    error("expression for parameter %s is of wrong type", symname(arg));
	}
	if (arg->class == REF) {
	    if (exp->op != O_RVAL) {
		sp = savesp;
		error("variable expected for parameter \"%s\"", symname(arg));
	    }
	    addr = lval(exp->value.arg[0]);
	    push(Address, addr);
	} else {
	    eval(exp);
	}
	arg = arg->chain;
	++count;
    }
    if (arg != nil) {
	sp = savesp;
	error("not enough parameters to %s", symname(proc));
    }
    return count;
}

public procreturn(f)
Symbol f;
{
    flushoutput();
    putchar('\n');
    printname(stdout, f);
    printf(" returns successfully\n", symname(f));
    popenv();
    erecover();
}

/*
 * Push the current environment.
 */

private pushenv()
{
    push(Address, pc);
    push(Lineno, curline);
    push(String, cursource);
    push(Boolean, isstopped);
    push(Symbol, curfunc);
    push(Word, reg(PROGCTR));
    push(Word, reg(STKP));
}

/*
 * Pop back to the real world.
 */

public popenv()
{
    register String filename;

    setreg(STKP, pop(Word));
    setreg(PROGCTR, pop(Word));
    curfunc = pop(Symbol);
    isstopped = pop(Boolean);
    filename = pop(String);
    curline = pop(Lineno);
    pc = pop(Address);
    setsource(filename);
}

/*
 * Flush the debuggee's standard output.
 *
 * This is VERY dependent on the use of stdio.
 */

public flushoutput()
{
    register Symbol p, iob;
    register Stack *savesp;

    p = lookup(identname("fflush", true));
    while (p != nil and not isblock(p)) {
	p = p->next_sym;
    }
    if (p != nil) {
	iob = lookup(identname("_iob", true));
	if (iob != nil) {
	    pushenv();
	    pc = codeloc(p);
	    savesp = sp;
	    push(long, address(iob, nil) + sizeof(struct _iobuf));
	    setreg(STKP, reg(STKP) - sizeof(long));
	    dwrite(savesp, reg(STKP), sizeof(long));
	    sp = savesp;
	    beginproc(p, 1);
	    stepto(return_addr());
	    popenv();
	}
    }
}
