/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim; import java.lang.reflect.Field; /** * This class serves as a context to manage and store the continuation stack. * The actual capture of the closure is done in the Weaver-transformed code. */ public final class Fiber { // public boolean debug = false; /** * The current frame's state (local vars and elements of the operand stack * that will be needed when the Fiber is resumed. It is always kept equal * to stateStack[iStack] if iStack is in the (0..stateStack.length-1) range, * and null otherwise. This is used by the generated code to avoid * having to manipulate stateStack in the generated code, and to isolate * all stack manipulations to up() and down(). */ public State curState; /** * The "program counter", kept equal to stateStack[iStack].pc and is used to * jump to the appropriate place in the code while rewinding the code, and * also to inform the weaved code inside an exception handler which pausable * method (if at all) was being invoked when that exception was thrown. The * value 0 refers to normal operation; control transfers to the beginning of * the original (pre-weaved) code. A value of n indicates a direct jump into * the nth pausable method (after restoring the appropriate state). * Accessed by generated code (hence public). */ public int pc; /* * One State object for each activation frame in the call hierarchy. */ private State[] stateStack = new State[10]; /* * Index into stateStack and equal to depth of call hierarchy - 1 */ private int iStack = -1; boolean isPausing; boolean isDone; /* * The task to which this Fiber belongs */ public Task task; /* * Special marker state used by pause */ private static final State PAUSE_STATE = new State(); /* * Status indicators returned by down() * * normal return, nothing to restore */ public static final int NOT_PAUSING__NO_STATE = 0; /* * Normal return, have saved state to restore before resuming */ public static final int NOT_PAUSING__HAS_STATE = 1; /* * Pausing, and need to save state before returning */ public static final int PAUSING__NO_STATE = 2; /* * Pausing, and have saved state from an earlier invocation, * so nothing left to do. */ public static final int PAUSING__HAS_STATE = 3; static { PAUSE_STATE.pc = 1; } public Fiber(Task t) { task = t; } public Task task() { return task; } public boolean isDone() { return isDone; } public static void pause() throws Pausable { throw new IllegalStateException("pause() called without weaving"); } /* * The user calls pause(), but the weaver changes the * call to pause(Fiber), which alternates between * pausing and not pausing in successive invocations. * @param f */ public static void pause(Fiber f) { f.togglePause(); } /* * Indication from the caller that the invoked function has returned. * * If it is a normal return, it needs to read the state into curState * and nullify it in stateStack. This accomplishes the equivalent of: * move up * get state() * restore from state * delete state() * If it is pausing, it merely needs to return a combined status * * @return a combined status of PAUSING/NOT_PAUSING and HAS_STATE/NO_STATE. */ public int up() { int d = iStack; iStack = --d; if (isPausing) { // if (debug) System.out.println("\nup(pausing)" + this);; // if (debug) ds(); return (stateStack[d] == null) ? PAUSING__NO_STATE : PAUSING__HAS_STATE; // not setting curState because the generated code is only // interested in knowing whether we have state or not. } else { // move up to caller's level State[] stack = stateStack; State cs = curState = stack[d]; if (cs == null) { pc = 0; // if (debug) System.out.println("\nup(not pausing)" + this);; // if (debug) ds(); return NOT_PAUSING__NO_STATE; } else { stack[d] = null; // clean up pc = cs.pc; // if (debug) System.out.println("\nup(not pausing)" + this);; // if (debug) ds(); return NOT_PAUSING__HAS_STATE; } } } public final Fiber begin() { return down(); } /** * end() is the last up(). returns true if the fiber is not pausing. */ public final boolean end() { assert iStack == 0 : "Reset: Expected iStack == 0, not " + iStack + "\n" + this; boolean isDone = !isPausing; if (isDone) { // clean up callee's state stateStack[0] = null; } // reset pausing for next round. isPausing = false; iStack = -1; // if (debug) System.out.println("lastUp() " + this); // if (debug) ds(); return isDone; } /* * Called by the generated code to indicate that it is moving on to the next * pausable method in the hierarchy Adjust iStack, set curState and pc for * convenience * * @return this Fiber. */ public Fiber down() { int d = ++iStack; if (d >= stateStack.length) { // System.out.println("size == " + d); ensureSize(d * 2); pc = 0; curState = null; } else { State s = stateStack[d]; curState = s; pc = (s == null) ? 0 : s.pc; } // if (debug) System.out.println("down:\n" + this); // if (debug) ds(); return this; } static void ds() { for (StackTraceElement ste: new Exception().getStackTrace()) { String cl = ste.getClassName(); String meth = ste.getMethodName(); if (cl.startsWith("kilim.Worker") || meth.equals("go") || meth.equals("ds")) continue; String line = ste.getLineNumber() < 0 ? "" : ":" + ste.getLineNumber(); System.out.println('\t' + cl + '.' + ste.getMethodName() + '(' + ste.getFileName() + line + ')'); } } /** * In the normal (non-exception) scheme of things, the iStack is incremented * by down() on the way down and decremented by a corresponding up() when returning * or pausing. If, however, an exception is thrown, we lose track of where we * are in the hierarchy. We recalibrate iStack by creating a dummy exception * and comparing it to the stack depth of an exception taken earlier. * This is done in scheduler.getStackDepth(); * A sample stack trace of the dummy exception looks as follows * <pre> * at kilim.Fiber.upEx(Fiber.java:250) * at kilim.test.ex.ExCatch.normalCatch(ExCatch.java) * at kilim.test.ex.ExCatch.test(ExCatch.java) * at kilim.test.ex.ExCatch.execute(ExCatch.java) * at kilim.Task.runExecute(Task.java) * at kilim.WorkerThread.run(WorkerThread.java:11) * </pre> * We have to figure out the stack depth (iStack) of the method * that caught the exception and called upEx ("normalCatch" here). * The call stack below runExecute may be owned by the scheduler, which * may permit more than one task to build up on the stack. For this reason, * we let the scheduler tell us the depth of upEx below the task's execute(). * @return Fiber.pc (note: in contrast up() returns status) */ public int upEx() { // compute new iStack. int is = task.getStackDepth() - 2; // remove upEx and convert to 0-based index. State cs = stateStack[is]; for (int i = iStack; i >= is; i--) { stateStack[i] = null; // release state } iStack = is; curState = cs; return (cs == null) ? 0 : cs.pc; } /** * Called by the weaved code while rewinding the stack. If we are about to * call a virtual pausable method, we need an object reference on which to * call that method. The next state has that information in state.self */ public Object getCallee() { assert stateStack[iStack] != PAUSE_STATE : "No callee: this state is the pause state"; assert stateStack[iStack] != null : "Callee is null"; return stateStack[iStack + 1].self; } private State[] ensureSize(int newsize) { // System.out.println("ENSURE SIZE = " + newsize); State[] newStack = new State[newsize]; System.arraycopy(stateStack, 0, newStack, 0, stateStack.length); stateStack = newStack; return newStack; } /** * Called by the generated code before pausing and unwinding its stack * frame. * * @param state */ public void setState(State state) { stateStack[iStack] = state; isPausing = true; // System.out.println("setState[" + + iStack + "] = " + this); } public State getState() { return stateStack[iStack]; } void togglePause() { // The client code would have called fiber.down() // before calling Task.pause. curStatus would be // upto date. if (curState == null) { setState(PAUSE_STATE); } else { assert curState == PAUSE_STATE : "togglePause: Expected PAUSE_STATE, instead got: iStack == " + iStack + ", state = " + curState; stateStack[iStack] = null; isPausing = false; } } public String toString() { StringBuilder sb = new StringBuilder(40); sb.append("iStack = ").append(iStack).append(", pc = ").append(pc); if (isPausing) { sb.append(" pausing"); } sb.append('\n'); for (int i = 0; i < stateStack.length; i++) { State st = stateStack[i]; if (st != null) { sb.append(st.getClass().getName()).append('[').append(i).append("]: "); stateToString(sb, stateStack[i]); } } return sb.toString(); } public void wrongPC() { throw new IllegalStateException("Wrong pc: " + pc); } static private void stateToString(StringBuilder sb, State s) { if (s == PAUSE_STATE) { sb.append("PAUSE\n"); return; } Field[] fs = s.getClass().getFields(); for (int i = 0; i < fs.length; i++) { Field f = fs[i]; sb.append(f.getName()).append(" = "); Object v; try { v = f.get(s); } catch (IllegalAccessException iae) { v = "?"; } sb.append(' ').append(v).append(' '); } sb.append('\n'); } void clearPausing() { isPausing = false; } }