/*
 * LispThread.java
 *
 * Copyright (C) 2003-2007 Peter Graves
 * $Id: LispThread.java 14465 2013-04-24 12:50:37Z rschlatte $
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on this library.  If you modify this library, you may extend
 * this exception to your version of the library, but you are not
 * obligated to do so.  If you do not wish to do so, delete this
 * exception statement from your version.
 */

package org.armedbear.lisp;

import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import static org.armedbear.lisp.Lisp.*;

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Stack; 
import java.text.MessageFormat;

import java.util.concurrent.ThreadFactory;

public final class LispThread
  extends LispObject
  implements org.armedbear.lisp.protocol.Inspectable
{
    // use a concurrent hashmap: we may want to add threads
    // while at the same time iterating the hash
    final static ConcurrentHashMap<Thread,LispThread> map =
       new ConcurrentHashMap<Thread,LispThread>();

    LispObject threadValue = NIL;

    private static ThreadLocal<LispThread> threads = new ThreadLocal<LispThread>(){
        @Override
        public LispThread initialValue() {
            Thread thisThread = Thread.currentThread();
            LispThread thread = LispThread.map.get(thisThread);
            if (thread == null) {
                thread = new LispThread(thisThread);
                LispThread.map.put(thisThread,thread);
            }
            return thread;
        }
    };

    public static final LispThread currentThread()
    {
        return threads.get();
    }

    final Thread javaThread;
    private boolean destroyed;
    final LispObject name;
    public LispObject[] _values;
    private boolean threadInterrupted;
    private LispObject pending = NIL;
    private Symbol wrapper =
        PACKAGE_THREADS.intern("THREAD-FUNCTION-WRAPPER");

    /** Whether the wrapped thread is virtual */
    boolean isVirtual;

    public LispObject getParts() {
      return NIL
        .push(new Cons("Wrapped Java thread",
                       JavaObject.getInstance(javaThread)));
    }

    /** Stack holding bindings for evaluated functions */
    public Stack<Environment>envStack = new Stack<Environment>();

    LispThread(Thread javaThread)
    {
        this.javaThread = javaThread;
        name = new SimpleString(javaThread.getName());
    }

  public static boolean virtualThreadingAvailable() {
    try {
      Method m 
        = java.lang.Thread.class
        .getDeclaredMethod("startVirtualThread", java.lang.Runnable.class);
      return true;
    } catch (NoSuchMethodException e2) {
      // This is the case in non-Loom JVMs
    } catch (SecurityException e3) {
      Debug.trace("SecurityException caught introspecting threading interface: " + e3.toString());
    }
    return false;
  }

  static ThreadFactory virtualThreadFactory;
  static Method newThread;

  static {
    try {
      Object ofVirtual
        = Thread.class.getMethod("ofVirtual").invoke(null);
      Method factoryMethod
        = Class.forName("java.lang.Thread$Builder").getMethod("factory");
      virtualThreadFactory
        = (ThreadFactory) factoryMethod.invoke(ofVirtual);
      newThread  
        = virtualThreadFactory.getClass()
        .getMethod("newThread", java.lang.Runnable.class);
      newThread.setAccessible(true);
    } catch (Exception e) {
      if (virtualThreadingAvailable()) {
        Debug.trace("Failed to introspect virtual threading methods: " + e);
      }
    }
  }

  public static Symbol NATIVE_THREADS = internKeyword("NATIVE");
  public static Symbol VIRTUAL_THREADS = internKeyword("VIRTUAL");

  static {
    Symbol._THREADING_MODEL_.initializeSpecial(NATIVE_THREADS);
    final String summary
      = "The current type of threads created via MAKE-THREAD";
    final String doc
      = "Meaningful values are either :NATIVE, the default, or the keyword :VIRTUAL."
      + " The ability to invoke virtual threads at runtime is indicated by the presence of :VIRTUAL-THREADS"
      + " in CL:*FEATURES*.";
    Symbol._THREADING_MODEL_.setDocumentation(new SimpleString(summary + "\n" + doc),
                                              Symbol.VARIABLE);
  }

    LispThread(final Function fun, LispObject name)
    {
        Runnable r = new Runnable() {
            public void run()
            {
                try {
                    threadValue = funcall(wrapper,
                            new LispObject[] { fun },
                            LispThread.this);
                }
                catch (ThreadDestroyed ignored) {
                      // Might happen.
                }
                catch (ProcessingTerminated e) {
                    System.exit(e.getStatus());
                }
                catch (Throwable t) { // any error: process thread interrupts
                    if (isInterrupted()) {
                        processThreadInterrupts();
                    }
                    String msg 
                        = MessageFormat.format("Ignoring uncaught exception {0}.",
                                               t.toString());
                    Debug.warn(msg);
                }
                finally {
                    // make sure the thread is *always* removed from the hash again
                    map.remove(Thread.currentThread());
                }
            }
        };
        this.name = name;

        Thread thread = null;
        
        if (Symbol._THREADING_MODEL_.getSymbolValue().equals(NATIVE_THREADS)) {
          thread = new Thread(r);
          isVirtual = false;
        } else {
          try {
            thread = (Thread)newThread.invoke(virtualThreadFactory, r);
            isVirtual = true;
          } catch (Exception e) {
            thread = new Thread(r);
            isVirtual =  false;
            Debug.trace("Falling back to native thread creation after virtual thread failed:", e);
          }
        }
        if (thread == null) {
          Debug.trace("Failed to create java.lang.Thread");
          javaThread = null;
        } else {
          if (name != NIL) {
            thread.setName(name.getStringValue());
          }
          thread.setDaemon(true);

          javaThread = thread;
          map.put(javaThread, this);

          javaThread.start();
        }
    }

    public StackTraceElement[] getJavaStackTrace() {
        return javaThread.getStackTrace();
    }

    @Override
    public LispObject typeOf()
    {
        return Symbol.THREAD;
    }

    @Override
    public LispObject classOf()
    {
        return BuiltInClass.THREAD;
    }

    @Override
    public LispObject typep(LispObject typeSpecifier)
    {
        if (typeSpecifier == Symbol.THREAD)
            return T;
        if (typeSpecifier == BuiltInClass.THREAD)
            return T;
        return super.typep(typeSpecifier);
    }

    public final synchronized boolean isDestroyed()
    {
        return destroyed;
    }

    final synchronized boolean isInterrupted()
    {
        return threadInterrupted;
    }

    final synchronized void setDestroyed(boolean b)
    {
        destroyed = b;
    }

    final synchronized void interrupt(LispObject function, LispObject args)
    {
        pending = new Cons(args, pending);
        pending = new Cons(function, pending);
        threadInterrupted = true;
        javaThread.interrupt();
    }

    final synchronized void processThreadInterrupts()

    {
        while (pending != NIL) {
            LispObject function = pending.car();
            LispObject args = pending.cadr();
            pending = pending.cddr();
            Primitives.APPLY.execute(function, args);
        }
        threadInterrupted = false;
    }

    public final LispObject[] getValues()
    {
        return _values;
    }

    public final LispObject[] getValues(LispObject result, int count)
    {
        if (_values == null) {
            LispObject[] values = new LispObject[count];
            if (count > 0)
                values[0] = result;
            for (int i = 1; i < count; i++)
                values[i] = NIL;
            return values;
        }
        // If the caller doesn't want any extra values, just return the ones
        // we've got.
        if (count <= _values.length)
            return _values;
        // The caller wants more values than we have. Pad with NILs.
        LispObject[] values = new LispObject[count];
        for (int i = _values.length; i-- > 0;)
            values[i] = _values[i];
        for (int i = _values.length; i < count; i++)
            values[i] = NIL;
        return values;
    }

    /** Used by the JVM compiler for MULTIPLE-VALUE-CALL. */
    public final LispObject[] accumulateValues(LispObject result,
                                               LispObject[] oldValues)
    {
        if (oldValues == null) {
            if (_values != null)
                return _values;
            LispObject[] values = new LispObject[1];
            values[0] = result;
            return values;
        }
        if (_values != null) {
            if (_values.length == 0)
                return oldValues;
            final int totalLength = oldValues.length + _values.length;
            LispObject[] values = new LispObject[totalLength];
            System.arraycopy(oldValues, 0,
                             values, 0,
                             oldValues.length);
            System.arraycopy(_values, 0,
                             values, oldValues.length,
                             _values.length);
            return values;
        }
        // _values is null.
        final int totalLength = oldValues.length + 1;
        LispObject[] values = new LispObject[totalLength];
        System.arraycopy(oldValues, 0,
                         values, 0,
                         oldValues.length);
        values[totalLength - 1] = result;
        return values;
    }

    public final LispObject setValues()
    {
        _values = new LispObject[0];
        return NIL;
    }

    public final LispObject setValues(LispObject value1)
    {
        _values = null;
        return value1;
    }

    public final LispObject setValues(LispObject value1, LispObject value2)
    {
        _values = new LispObject[2];
        _values[0] = value1;
        _values[1] = value2;
        return value1;
    }

    public final LispObject setValues(LispObject value1, LispObject value2,
                                      LispObject value3)
    {
        _values = new LispObject[3];
        _values[0] = value1;
        _values[1] = value2;
        _values[2] = value3;
        return value1;
    }

    public final LispObject setValues(LispObject value1, LispObject value2,
                                      LispObject value3, LispObject value4)
    {
        _values = new LispObject[4];
        _values[0] = value1;
        _values[1] = value2;
        _values[2] = value3;
        _values[3] = value4;
        return value1;
    }

    public final LispObject setValues(LispObject[] values)
    {
        switch (values.length) {
            case 0:
                _values = values;
                return NIL;
            case 1:
                _values = null;
                return values[0];
            default:
                _values = values;
                return values[0];
        }
    }

    public final void clearValues()
    {
        _values = null;
    }

    public final LispObject nothing()
    {
        _values = new LispObject[0];
        return NIL;
    }

   /** 
    * Force a single value, for situations where multiple values should be
    * ignored.
    */
    public final LispObject value(LispObject obj)
    {
        _values = null;
        return obj;
    }



    final static int UNASSIGNED_SPECIAL_INDEX = 0;

    /** Indicates the last special slot which has been assigned.
     * Symbols which don't have a special slot assigned use a slot
     * index of 0 for efficiency reasons: it eliminates the need to
     * check for index validity before accessing the specials array.
     *
     */
    final static AtomicInteger lastSpecial
        = new AtomicInteger(UNASSIGNED_SPECIAL_INDEX);

    /** A list of indices which can be (re)used for symbols to
     * be assigned a special slot index.
     */
    final static ConcurrentLinkedQueue<Integer> freeSpecialIndices
        = new ConcurrentLinkedQueue<Integer>();

    final static int specialsInitialSize
        = Integer.valueOf(System.getProperty("abcl.specials.initialSize","4096"));

    /** This array stores the current special binding for every symbol
     * which has been globally or locally declared special.
     *
     * If the array element has a null value, this means there currently
     * is no active binding. If the array element contains a valid
     * SpecialBinding object, but the value field of it is null, that
     * indicates an "UNBOUND VARIABLE" situation.
     */
    SpecialBinding[] specials
        = new SpecialBinding[specialsInitialSize + 1];

    final static ConcurrentHashMap<Integer, WeakReference<Symbol>> specialNames
        = new ConcurrentHashMap<Integer, WeakReference<Symbol>>();

    /** The number of slots to grow the specials table in
     * case of insufficient storage.
     */
    final static int specialsDelta
        = Integer.valueOf(System.getProperty("abcl.specials.grow.delta","1024"));

    /** This variable points to the head of a linked list of saved
     * special bindings. Its main purpose is to allow a mark/reset
     * interface to special binding and unbinding.
     */
    private SpecialBindingsMark savedSpecials = null;

    /** Marks the state of the special bindings,
     * for later rewinding by resetSpecialBindings().
     */
    public final SpecialBindingsMark markSpecialBindings() {
        return savedSpecials;
    }

    /** Restores the state of the special bindings to what
     * was captured in the marker 'mark' by a call to markSpecialBindings().
     */
    public final void resetSpecialBindings(SpecialBindingsMark mark) {
        SpecialBindingsMark c = savedSpecials;
        while (mark != c) {
            specials[c.idx] = c.binding;
            c = c.next;
        }
        savedSpecials = c;
    }

    /** Clears out all active special bindings including any marks
     * previously set. Invoking resetSpecialBindings() with marks
     * set before this call results in undefined behaviour.
     */
    // Package level access: only for Interpreter.run()
    final void clearSpecialBindings() {
        resetSpecialBindings(null);
    }

    /** Assigns a specials array index number to the symbol,
     * if it doesn't already have one.
     */
    private void assignSpecialIndex(Symbol sym)
    {
        if (sym.specialIndex != 0)
            return;

        synchronized (sym) {
            // Don't use an atomic access: we'll be swapping values only once.
            if (sym.specialIndex == 0) {
                Integer next = freeSpecialIndices.poll();
                if (next == null
                        && specials.length < lastSpecial.get()
                        && null == System.getProperty("abcl.specials.grow.slowly")) {
                    // free slots are exhausted; in the middle and at the end.
                    System.gc();
                    next = freeSpecialIndices.poll();
                }
                if (next == null)
                    sym.specialIndex = lastSpecial.incrementAndGet();
                else
                    sym.specialIndex = next.intValue();
            }
        }
    }

    /** Frees up an index previously assigned to a symbol for re-assignment
     * to another symbol. Returns without effect if the symbol has the
     * default UNASSIGNED_SPECIAL_INDEX special index.
     */
    protected static void releaseSpecialIndex(Symbol sym)
    {
        int index = sym.specialIndex;
        if (index != UNASSIGNED_SPECIAL_INDEX) {
            // clear out the values in the
            Iterator<LispThread> it = map.values().iterator();
            while (it.hasNext()) {
                LispThread thread = it.next();

                // clear out the values in the saved specials list
                SpecialBindingsMark savedSpecial = thread.savedSpecials;
                while (savedSpecial != null) {
                    if (savedSpecial.idx == index) {
                        savedSpecial.idx = 0;
                        savedSpecial.binding = null;
                    }
                    savedSpecial = savedSpecial.next;
                }

                thread.specials[index] = null;
            }

            freeSpecialIndices.add(Integer.valueOf(index));
        }
    }

    private void growSpecials() {
        SpecialBinding[] newSpecials
                = new SpecialBinding[specials.length + specialsDelta];
        System.arraycopy(specials, 0, newSpecials, 0, specials.length);
        specials = newSpecials;
    }

    private SpecialBinding ensureSpecialBinding(int idx) {
        SpecialBinding binding;
        boolean assigned;
        do {
            try {
                binding = specials[idx];
                assigned = true;
            }
            catch (ArrayIndexOutOfBoundsException e) {
                assigned = false;
                binding = null;  // suppresses 'unassigned' error
                growSpecials();
            }
        } while (! assigned);
        return binding;
    }

    public final SpecialBinding bindSpecial(Symbol name, LispObject value)
    {
        int idx;

        assignSpecialIndex(name);
        SpecialBinding binding = ensureSpecialBinding(idx = name.specialIndex);
        savedSpecials = new SpecialBindingsMark(idx, binding, savedSpecials);
        return specials[idx] = new SpecialBinding(idx, value);
    }

    public final SpecialBinding bindSpecialToCurrentValue(Symbol name)
    {
        int idx;

        assignSpecialIndex(name);
        SpecialBinding binding = ensureSpecialBinding(idx = name.specialIndex);
        savedSpecials = new SpecialBindingsMark(idx, binding, savedSpecials);
        return specials[idx]
            = new SpecialBinding(idx,
                                 (binding == null) ?
                                 name.getSymbolValue() : binding.value);
    }

    /** Looks up the value of a special binding in the context of the
     * given thread.
     *
     * In order to find the value of a special variable (in general),
     * use {@link Symbol#symbolValue}.
     *
     * @param name The name of the special variable, normally a symbol
     * @return The inner most binding of the special, or null if unbound
     *
     * @see Symbol#symbolValue
     */
    public final LispObject lookupSpecial(Symbol name)
    {
        SpecialBinding binding = ensureSpecialBinding(name.specialIndex);
        return (binding == null) ? null : binding.value;
    }

    public final SpecialBinding getSpecialBinding(Symbol name)
    {
        return ensureSpecialBinding(name.specialIndex);
    }

    public final LispObject setSpecialVariable(Symbol name, LispObject value)
    {
        SpecialBinding binding = ensureSpecialBinding(name.specialIndex);
        if (binding != null)
            return binding.value = value;

        name.setSymbolValue(value);
        return value;
    }

    public final LispObject pushSpecial(Symbol name, LispObject thing)

    {
        SpecialBinding binding = ensureSpecialBinding(name.specialIndex);
        if (binding != null)
            return binding.value = new Cons(thing, binding.value);

        LispObject value = name.getSymbolValue();
        if (value != null) {
            LispObject newValue = new Cons(thing, value);
            name.setSymbolValue(newValue);
            return newValue;
        } else
            return error(new UnboundVariable(name));
    }

    // Returns symbol value or NIL if unbound.
    public final LispObject safeSymbolValue(Symbol name)
    {
        SpecialBinding binding = ensureSpecialBinding(name.specialIndex);
        if (binding != null)
            return binding.value;

        LispObject value = name.getSymbolValue();
        return value != null ? value : NIL;
    }

    public final void rebindSpecial(Symbol name, LispObject value)
    {
        SpecialBinding binding = getSpecialBinding(name);
        binding.value = value;
    }

    private LispObject catchTags = NIL;

    public void pushCatchTag(LispObject tag)
    {
        catchTags = new Cons(tag, catchTags);
    }

    public void popCatchTag()
    {
        if (catchTags != NIL)
            catchTags = catchTags.cdr();
        else
            Debug.assertTrue(false);
    }

    public void throwToTag(LispObject tag, LispObject result)

    {
        LispObject rest = catchTags;
        while (rest != NIL) {
            if (rest.car() == tag)
                throw new Throw(tag, result, this);
            rest = rest.cdr();
        }
        error(new ControlError("Attempt to throw to the nonexistent tag " +
                                tag.princToString() + "."));
    }


    private static class StackMarker {

        final int numArgs;

        StackMarker(int numArgs) {
            this.numArgs = numArgs;
        }

        int getNumArgs() {
            return numArgs;
        }
    }

    // markers for args
    private final static StackMarker STACK_MARKER_0 = new StackMarker(0);
    private final static StackMarker STACK_MARKER_1 = new StackMarker(1);
    private final static StackMarker STACK_MARKER_2 = new StackMarker(2);
    private final static StackMarker STACK_MARKER_3 = new StackMarker(3);
    private final static StackMarker STACK_MARKER_4 = new StackMarker(4);
    private final static StackMarker STACK_MARKER_5 = new StackMarker(5);
    private final static StackMarker STACK_MARKER_6 = new StackMarker(6);
    private final static StackMarker STACK_MARKER_7 = new StackMarker(7);
    private final static StackMarker STACK_MARKER_8 = new StackMarker(8);

    private final int STACK_FRAME_EXTRA = 2;
    // a LispStackFrame with n arguments occupies n + STACK_FRAME_EXTRA elements
    // in {@code stack} array.
    // stack[framePos] == operation
    // stack[framePos + 1 + i] == arg[i]
    // stack[framePos + 1 + n] == initially SrackMarker(n)
    // LispStackFrame object may be lazily allocated later.
    // In this case it is stored in stack framePos + 1 + n]
    //
    // Java stack frame occupies 1 element
    // stack[framePos] == JavaStackFrame
    //
    // Stack consists of a list of StackSegments.
    // Top StackSegment is cached in variables stack and stackPtr.
    private StackSegment topStackSegment = new StackSegment(INITIAL_SEGMENT_SIZE, null);
    private Object[] stack = topStackSegment.stack;
    private int stackPtr = 0;
    private StackSegment spareStackSegment;
    
    private static class StackSegment 
      implements org.armedbear.lisp.protocol.Inspectable
    {
        final Object[] stack;
        final StackSegment next;
        int stackPtr;
        
        StackSegment(int size, StackSegment next) {
            stack = new Object[size];
            this.next = next;
        }
        public LispObject getParts() {
        return NIL
          .push(new Cons(new Symbol("INITIAL-SEGMENT-SIZE"),
                         LispInteger.getInstance(LispThread.INITIAL_SEGMENT_SIZE)))
          .push(new Cons(new Symbol("SEGMENT-SIZE"),
                         LispInteger.getInstance(LispThread.SEGMENT_SIZE)));
        }
    }
    
    private void ensureStackCapacity(int itemsToPush) {
        if (stackPtr + (itemsToPush - 1) >= stack.length)
            grow(itemsToPush);
    }

    private static final int INITIAL_SEGMENT_SIZE = 1 << 10;
    private static final int SEGMENT_SIZE = (1 << 19) - 4; // 4 MiB page on x86_64

    private void grow(int numEntries) {
        topStackSegment.stackPtr = stackPtr;
        if (spareStackSegment != null) {
            // Use spare segement if available
            if (stackPtr > 0 && spareStackSegment.stack.length >= numEntries) {
                topStackSegment = spareStackSegment;
                stack = topStackSegment.stack;
                spareStackSegment = null;
                stackPtr = 0;
                return;
            }
            spareStackSegment = null;
        }
        int newSize = stackPtr + numEntries;
        if (topStackSegment.stack.length < SEGMENT_SIZE || stackPtr == 0) {
            // grow initial segment from initial size to standard size
            int newLength = Math.max(newSize, Math.min(SEGMENT_SIZE, stack.length * 2));
            StackSegment newSegment = new StackSegment(newLength, topStackSegment.next);
            System.arraycopy(stack, 0, newSegment.stack, 0, stackPtr);
            topStackSegment = newSegment;
            stack = topStackSegment.stack;
            return;
        }
        // Allocate new segment
        topStackSegment = new StackSegment(Math.max(SEGMENT_SIZE, numEntries), topStackSegment);
        stack = topStackSegment.stack;
        stackPtr = 0;
    }

    private StackFrame getStackTop() {
        topStackSegment.stackPtr = stackPtr;
        if (stackPtr == 0) {
            assert topStackSegment.next == null;
            return null;
        }
        StackFrame prev = null;
        for (StackSegment segment = topStackSegment; segment != null; segment = segment.next) {
            Object[] stk = segment.stack;
            int framePos = segment.stackPtr;
            while (framePos > 0) {
                Object stackObj = stk[framePos - 1];
                if (stackObj instanceof StackFrame) {
                    if (prev != null) {
                        prev.setNext((StackFrame) stackObj);
                    }
                    return (StackFrame) stack[stackPtr - 1];
                }
                StackMarker marker = (StackMarker) stackObj;
                int numArgs = marker.getNumArgs();
                LispStackFrame frame = new LispStackFrame(stk, framePos - numArgs - STACK_FRAME_EXTRA, numArgs);
                frame.thread = this;
                stk[framePos - 1] = frame;
                if (prev != null) {
                    prev.setNext(frame);
                }
                prev = frame;
                framePos -= numArgs + STACK_FRAME_EXTRA;
            }
        }
        return (StackFrame) stack[stackPtr - 1];
    }
    
    public final void pushStackFrame(JavaStackFrame frame) {
        frame.setNext(getStackTop());
        ensureStackCapacity(1);
        stack[stackPtr] = frame;
        stackPtr += 1;
    }

    private void popStackFrame(int numArgs) {
        // Pop off intervening JavaFrames until we get back to a LispFrame
        Object stackObj = stack[stackPtr - 1];
        if (stackObj instanceof StackMarker) {
            assert numArgs == ((StackMarker) stackObj).getNumArgs();
        } else {
            while (stackObj instanceof JavaStackFrame) {
                stack[--stackPtr] = null;
                stackObj = stack[stackPtr - 1];
            }
            if (stackObj instanceof StackMarker) {
                assert numArgs == ((StackMarker) stackObj).getNumArgs();
            } else {
                assert numArgs == ((LispStackFrame) stackObj).getNumArgs();
            }
        }
        stackPtr -= numArgs + STACK_FRAME_EXTRA;
        for (int i = 0; i < numArgs + STACK_FRAME_EXTRA; i++) {
            stack[stackPtr + i] = null;
        }
        if (stackPtr == 0) {
            popStackSegment();
        }
    }
    
    private void popStackSegment() {
        topStackSegment.stackPtr = 0;
        if (topStackSegment.next != null) {
            spareStackSegment = topStackSegment;
            topStackSegment = topStackSegment.next;
            stack = topStackSegment.stack;
        }
        stackPtr = topStackSegment.stackPtr;
    }

    public final Environment setEnv(Environment env) {
        StackFrame stackTop = getStackTop();
        return (stackTop != null) ? stackTop.setEnv(env) : null;
    }

    public void resetStack()
    {
        topStackSegment = new StackSegment(INITIAL_SEGMENT_SIZE, null);
        stack = topStackSegment.stack;
        spareStackSegment = null;
        stackPtr = 0;
    }

    @Override
    public LispObject execute(LispObject function)
    {
        ensureStackCapacity(STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = STACK_MARKER_0;
        stackPtr += STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute();
        }
        finally {
            envStack.pop() ; 
            popStackFrame(0);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject arg)
    {
        ensureStackCapacity(1 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = arg;
        stack[stackPtr + 2] = STACK_MARKER_1;
        stackPtr += 1 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(arg);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(1);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject first,
                              LispObject second)
    {
        ensureStackCapacity(2 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = STACK_MARKER_2;
        stackPtr += 2 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(2);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject first,
                              LispObject second, LispObject third)
    {
        ensureStackCapacity(3 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = third;
        stack[stackPtr + 4] = STACK_MARKER_3;
        stackPtr += 3 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second, third);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(3);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject first,
                              LispObject second, LispObject third,
                              LispObject fourth)
    {
        ensureStackCapacity(4 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = third;
        stack[stackPtr + 4] = fourth;
        stack[stackPtr + 5] = STACK_MARKER_4;
        stackPtr += 4 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second, third, fourth);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(4);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject first,
                              LispObject second, LispObject third,
                              LispObject fourth, LispObject fifth)
    {
        ensureStackCapacity(5 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = third;
        stack[stackPtr + 4] = fourth;
        stack[stackPtr + 5] = fifth;
        stack[stackPtr + 6] = STACK_MARKER_5;
        stackPtr += 5 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second, third, fourth, fifth);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(5);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject first,
                              LispObject second, LispObject third,
                              LispObject fourth, LispObject fifth,
                              LispObject sixth)
    {
        ensureStackCapacity(6 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = third;
        stack[stackPtr + 4] = fourth;
        stack[stackPtr + 5] = fifth;
        stack[stackPtr + 6] = sixth;
        stack[stackPtr + 7] = STACK_MARKER_6;
        stackPtr += 6 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second, third, fourth, fifth, sixth);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(6);
        }
    }

    @Override
    public LispObject execute(LispObject function, LispObject first,
                              LispObject second, LispObject third,
                              LispObject fourth, LispObject fifth,
                              LispObject sixth, LispObject seventh)
    {
        ensureStackCapacity(7 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = third;
        stack[stackPtr + 4] = fourth;
        stack[stackPtr + 5] = fifth;
        stack[stackPtr + 6] = sixth;
        stack[stackPtr + 7] = seventh;
        stack[stackPtr + 8] = STACK_MARKER_7;
        stackPtr += 7 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second, third, fourth, fifth, sixth,
                                    seventh);
        }
        finally {
            envStack.pop();
            popStackFrame(7);
        }
    }

    public LispObject execute(LispObject function, LispObject first,
                              LispObject second, LispObject third,
                              LispObject fourth, LispObject fifth,
                              LispObject sixth, LispObject seventh,
                              LispObject eighth)
    {
        ensureStackCapacity(8 + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        stack[stackPtr + 1] = first;
        stack[stackPtr + 2] = second;
        stack[stackPtr + 3] = third;
        stack[stackPtr + 4] = fourth;
        stack[stackPtr + 5] = fifth;
        stack[stackPtr + 6] = sixth;
        stack[stackPtr + 7] = seventh;
        stack[stackPtr + 8] = eighth;
        stack[stackPtr + 9] = STACK_MARKER_8;
        stackPtr += 8 + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(first, second, third, fourth, fifth, sixth,
                                    seventh, eighth);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(8);
        }
    }

    public LispObject execute(LispObject function, LispObject[] args)
    {
        ensureStackCapacity(args.length + STACK_FRAME_EXTRA);
        stack[stackPtr] = function;
        System.arraycopy(args, 0, stack, stackPtr + 1, args.length);
        stack[stackPtr + args.length + 1] = new StackMarker(args.length);
        stackPtr += args.length + STACK_FRAME_EXTRA;
        try {
            envStack.push(new Environment(null,NIL,function));
            return function.execute(args);
        }
        finally {
            envStack.pop() ; 
            popStackFrame(args.length);
        }
    }

    public void printBacktrace()
    {
        printBacktrace(0);
    }

    public void printBacktrace(int limit)
    {
        StackFrame stackTop = getStackTop();
        if (stackTop != null) {
            int count = 0;
            Stream out =
                checkCharacterOutputStream(Symbol.TRACE_OUTPUT.symbolValue());
            out._writeLine("Evaluation stack:");
            out._finishOutput();

            StackFrame s = stackTop;
            while (s != null) {
                out._writeString("  ");
                out._writeString(String.valueOf(count));
                out._writeString(": ");

                pprint(s.toLispList(), out.getCharPos(), out);
                out.terpri();
                out._finishOutput();
                if (limit > 0 && ++count == limit)
                    break;
                s = s.next;
            }
        }
    }

    public LispObject backtrace(int limit)
    {
        StackFrame stackTop = getStackTop();
        LispObject result = NIL;
        if (stackTop != null) {
            int count = 0;
            StackFrame s = stackTop;
            while (s != null) {
                result = result.push(s);
                if (limit > 0 && ++count == limit)
                    break;
                s = s.getNext();
            }
        }
        return result.nreverse();
    }

    public void incrementCallCounts()
    {
        topStackSegment.stackPtr = stackPtr;
        int depth = 0;
        for (StackSegment segment = topStackSegment; segment != null; segment = segment.next) {
            Object[] stk = segment.stack;
            int framePos = segment.stackPtr;
            while (framePos > 0) {
                depth++;
                Object stackObj = stk[framePos - 1];
                int numArgs;
                if (stackObj instanceof StackMarker) {
                    numArgs = ((StackMarker) stackObj).getNumArgs();
                } else if (stackObj instanceof LispStackFrame) {
                    numArgs = ((LispStackFrame) stackObj).getNumArgs();
                } else {
                    assert stackObj instanceof JavaStackFrame;
                    framePos--;
                    continue;
                }
                // lisp stack frame
                framePos -= numArgs + STACK_FRAME_EXTRA;
                LispObject operator = (LispObject) stack[framePos];
                if (operator != null) {
                    if (depth <= 8) {
                        operator.incrementHotCount();
                    }
                    operator.incrementCallCount();
                }
            }
        }
    }

    private static void pprint(LispObject obj, int indentBy, Stream stream)

    {
        if (stream.getCharPos() == 0) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < indentBy; i++)
                sb.append(' ');
            stream._writeString(sb.toString());
        }
        String raw = obj.printObject();
        if (stream.getCharPos() + raw.length() < 80) {
            // It fits.
            stream._writeString(raw);
            return;
        }
        // Object doesn't fit.
        if (obj instanceof Cons) {
            boolean newlineBefore = false;
            LispObject[] array = obj.copyToArray();
            if (array.length > 0) {
                LispObject first = array[0];
                if (first == Symbol.LET) {
                    newlineBefore = true;
                }
            }
            int charPos = stream.getCharPos();
            if (newlineBefore && charPos != indentBy) {
                stream.terpri();
                charPos = stream.getCharPos();
            }
            if (charPos < indentBy) {
                StringBuffer sb = new StringBuffer();
                for (int i = charPos; i < indentBy; i++)
                    sb.append(' ');
                stream._writeString(sb.toString());
            }
            stream.print('(');
            for (int i = 0; i < array.length; i++) {
                pprint(array[i], indentBy + 2, stream);
                if (i < array.length - 1)
                   stream.print(' ');
            }
            stream.print(')');
        } else {
            stream.terpri();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < indentBy; i++)
                sb.append(' ');
            stream._writeString(sb.toString());
            stream._writeString(raw);
            return;
        }
    }

    @Override
    public String printObject()
    {
        StringBuffer sb = new StringBuffer("THREAD");
        if (name != NIL) {
            sb.append(" \"");
            sb.append(name.getStringValue());
            sb.append("\"");
        }
        if (isVirtual) {
          sb.append(" virtual");
        } else {
          sb.append(" native");
        }
        return unreadableString(sb.toString());
    }

    @DocString(name="make-thread",
               args="function &key name",
               doc="Create a thread of execution running FUNCTION possibly named NAME")
    private static final Primitive MAKE_THREAD =
        new Primitive("make-thread", PACKAGE_THREADS, true, "function &key name")
    {
        @Override
        public LispObject execute(LispObject[] args)
        {
            final int length = args.length;
            if (length == 0)
                error(new WrongNumberOfArgumentsException(this, 1, -1));
            LispObject name = NIL;
            if (length > 1) {
                if ((length - 1) % 2 != 0)
                    program_error("Odd number of keyword arguments.");
                if (length > 3)
                    error(new WrongNumberOfArgumentsException(this, -1, 2)); // don't count the keyword itself as an argument
                if (args[1] == Keyword.NAME)
                    name = args[2].STRING();
                else
                    program_error("Unrecognized keyword argument "
                                  + args[1].princToString() + ".");
            }
            return new LispThread(checkFunction(args[0]), name);
        }
    };

    @DocString(name="threadp",
               args="object",
               doc="Boolean predicate returning non-nil if OBJECT is a lisp thread")
    private static final Primitive THREADP =
        new Primitive("threadp", PACKAGE_THREADS, true)
    {
        @Override
        public LispObject execute(LispObject arg)
        {
            return arg instanceof LispThread ? T : NIL;
        }
    };

    @DocString(name="thread-alive-p",
               args="thread",
               doc="Returns T if THREAD is alive.")
    private static final Primitive THREAD_ALIVE_P =
      new Primitive("thread-alive-p", PACKAGE_THREADS, true, "thread",
                    "Boolean predicate whether THREAD is alive.")
    {
        @Override
        public LispObject execute(LispObject arg)
        {
            final LispThread lispThread;
            if (arg instanceof LispThread) {
                lispThread = (LispThread) arg;
            }
            else {
                return type_error(arg, Symbol.THREAD);
            }
            return lispThread.javaThread.isAlive() ? T : NIL;
        }
    };

    @DocString(name="thread-name",
               args="thread",
               doc="Return the name of THREAD, if it has one.")
    private static final Primitive THREAD_NAME =
        new Primitive("thread-name", PACKAGE_THREADS, true)
    {
        @Override
        public LispObject execute(LispObject arg)
        {
                if (arg instanceof LispThread) {
                return ((LispThread)arg).name;
            }
                 return type_error(arg, Symbol.THREAD);
        }
    };

    private static final Primitive THREAD_JOIN =
      new Primitive("thread-join", PACKAGE_THREADS, true, "thread",
                    "Waits for THREAD to die before resuming execution\n"
                    + "Returns the result of the joined thread as its primary value.\n"
                    + "Returns T if the joined thread finishes normally or NIL if it was interrupted.")
    {
        @Override
        public LispObject execute(LispObject arg)
        {
            // join the thread, and returns its value.  The second return
            // value is T if the thread finishes normally, NIL if its 
            // interrupted. 
            if (arg instanceof LispThread) {                
                final LispThread joinedThread = (LispThread) arg;
                final LispThread waitingThread = currentThread();
                try {
                    joinedThread.javaThread.join();
                    return 
                        waitingThread.setValues(joinedThread.threadValue, T);
                } catch (InterruptedException e) {
                    waitingThread.processThreadInterrupts();
                    return 
                        waitingThread.setValues(joinedThread.threadValue, NIL);
                }
            } else {
                return type_error(arg, Symbol.THREAD);
            } 
        }
    };
    
    final static DoubleFloat THOUSAND = new DoubleFloat(1000);

    static final long sleepMillisPart(LispObject seconds) {
      double d 
        = checkDoubleFloat(seconds.multiplyBy(THOUSAND)).getValue();
      if (d < 0) {
        type_error(seconds, list(Symbol.REAL, Fixnum.ZERO));
      }
      return (d < Long.MAX_VALUE ? (long) d : Long.MAX_VALUE);
    }

    static final int sleepNanosPart(LispObject seconds) {
      double d  // d contains millis
        = checkDoubleFloat(seconds.multiplyBy(THOUSAND)).getValue();
      double n = d * 1000000; // sleep interval in nanoseconds
      d = 1.0e6 * ((long)d); //  sleep interval to millisecond precision
      n = n - d; 

      return (n < Integer.MAX_VALUE ? (int) n : Integer.MAX_VALUE);
    }


    @DocString(name="sleep", args="seconds",
    doc="Causes the invoking thread to sleep for an interveral expressed in SECONDS.\n"
      + "SECONDS may be specified as a fraction of a second, with intervals\n"
      + "less than or equal to a nanosecond resulting in a yield of execution\n"
      + "to other waiting threads rather than an actual sleep.\n"
      + "A zero value of SECONDS *may* result in the JVM sleeping indefinitely,\n"
      + "depending on the implementation.")
    private static final Primitive SLEEP = new Primitive("sleep", PACKAGE_CL, true)
    {
        @Override
        public LispObject execute(LispObject arg)
        {
          long millis = sleepMillisPart(arg);
          int nanos = sleepNanosPart(arg);
          boolean zeroArgP = arg.ZEROP() != NIL;

          try {
            if (millis == 0 && nanos == 0) { 
              if (zeroArgP) {
                Thread.sleep(0, 0);
              } else { 
                Thread.sleep(0, 1);
              }
            } else {
              Thread.sleep(millis, nanos);
            } 
          } catch (InterruptedException e) {
            currentThread().processThreadInterrupts();
          }
          return NIL;
        }
    };

    @DocString(name="mapcar-threads", args= "function",
    doc="Applies FUNCTION to all existing threads.")
    private static final Primitive MAPCAR_THREADS =
        new Primitive("mapcar-threads", PACKAGE_THREADS, true)
    {
        @Override
        public LispObject execute(LispObject arg)
        {
            Function fun = checkFunction(arg);
            final LispThread thread = LispThread.currentThread();
            LispObject result = NIL;
            Iterator it = map.values().iterator();
            while (it.hasNext()) {
                LispObject[] args = new LispObject[1];
                args[0] = (LispThread) it.next();
                result = new Cons(funcall(fun, args, thread), result);
            }
            return result;
        }
    };

    @DocString(name="destroy-thread", args="thread", doc="Mark THREAD as destroyed")
    private static final Primitive DESTROY_THREAD =
        new Primitive("destroy-thread", PACKAGE_THREADS, true)
    {
        @Override
        public LispObject execute(LispObject arg)
        {
            final LispThread thread;
            if (arg instanceof LispThread) {
                thread = (LispThread) arg;
            }
            else {
                return type_error(arg, Symbol.THREAD);
            }
            thread.setDestroyed(true);
            return T;
        }
    };

    // => T
    @DocString(name="interrupt-thread", args="thread function &rest args",
    doc="Interrupts thread and forces it to apply function to args. When the\n"+
        "function returns, the thread's original computation continues. If\n"+
        "multiple interrupts are queued for a thread, they are all run, but the\n"+
        "order is not guaranteed.")
    private static final Primitive INTERRUPT_THREAD =
        new Primitive("interrupt-thread", PACKAGE_THREADS, true,
              "thread function &rest args",
              "Interrupts THREAD and forces it to apply FUNCTION to ARGS.\nWhen the function returns, the thread's original computation continues. If  multiple interrupts are queued for a thread, they are all run, but the order is not guaranteed.")
    {
        @Override
        public LispObject execute(LispObject[] args)
        {
            if (args.length < 2)
                return error(new WrongNumberOfArgumentsException(this, 2, -1));
            final LispThread thread;
            if (args[0] instanceof LispThread) {
                thread = (LispThread) args[0];
            }
            else {
                return type_error(args[0], Symbol.THREAD);
            }
            LispObject fun = args[1];
            LispObject funArgs = NIL;
            for (int i = args.length; i-- > 2;)
                funArgs = new Cons(args[i], funArgs);
            thread.interrupt(fun, funArgs);
            setInterrupted(thread,true);
            return T;
        }
    };

    public static final Primitive CURRENT_THREAD 
      = new pf_current_thread();
    @DocString(name="current-thread",
               doc="Returns a reference to invoking thread.")
    private static final class pf_current_thread extends Primitive {
      pf_current_thread() {
        super("current-thread", PACKAGE_THREADS, true);
      }
      @Override
      public LispObject execute() {
        return currentThread();
      }
    };

    public static final Primitive BACKTRACE
      = new pf_backtrace();
    @DocString(name="backtrace",
               doc="Returns a Java backtrace of the invoking thread.")
    private static final class pf_backtrace extends Primitive {
      pf_backtrace() {
        super("backtrace", PACKAGE_SYS, true);
      }
      @Override
      public LispObject execute(LispObject[] args) {
        if (args.length > 1)
          return error(new WrongNumberOfArgumentsException(this, -1, 1));
        int limit = args.length > 0 ? Fixnum.getValue(args[0]) : 0;
        return currentThread().backtrace(limit);
      }
    };

    public static final Primitive FRAME_TO_STRING
      = new pf_frame_to_string();
    @DocString(name="frame-to-string", 
               args="frame",
               doc="Convert stack FRAME to a (potentially) readable string.")
    private static final class pf_frame_to_string extends Primitive {
      pf_frame_to_string() {
        super("frame-to-string", PACKAGE_SYS, true);
      }
      @Override
      public LispObject execute(LispObject[] args) {
        if (args.length != 1)
          return error(new WrongNumberOfArgumentsException(this, 1));
        return checkStackFrame(args[0]).toLispString();
      }
    };

    public static final Primitive FRAME_TO_LIST
      = new pf_frame_to_list();
    @DocString(name="frame-to-list", args="frame")
    private static final class pf_frame_to_list extends Primitive {
      pf_frame_to_list() {
        super("frame-to-list", PACKAGE_SYS, true);
      }
      @Override
      public LispObject execute(LispObject[] args) {
        if (args.length != 1)
          return error(new WrongNumberOfArgumentsException(this, 1));

        return checkStackFrame(args[0]).toLispList();
      }
    };


    public static final SpecialOperator SYNCHRONIZED_ON 
      = new so_synchronized_on();
    @DocString(name="synchronized-on", args="form &body body")
    private static final class so_synchronized_on extends SpecialOperator {
      so_synchronized_on() {
        super("synchronized-on", PACKAGE_THREADS, true, "form &body body");
      }
      @Override
      public LispObject execute(LispObject args, Environment env) {
        if (args == NIL)
          return error(new WrongNumberOfArgumentsException(this, 1));
        
        LispThread thread = LispThread.currentThread();
        synchronized (eval(args.car(), env, thread).lockableInstance()) {
          return progn(args.cdr(), env, thread);
        }
      }
    };

  
    public static final Primitive OBJECT_WAIT
      = new pf_object_wait();
    @DocString(
    name="object-wait", args="object &optional timeout", 
    doc="Causes the current thread to block until object-notify or object-notify-all is called on OBJECT.\n"
       + "Optionally unblock execution after TIMEOUT seconds.  A TIMEOUT of zero\n"
       + "means to wait indefinitely.\n"
       + "A non-zero TIMEOUT of less than a nanosecond is interpolated as a nanosecond wait."
       + "\n"
       + "See the documentation of java.lang.Object.wait() for further\n"
       + "information.\n"
    )
    private static final class pf_object_wait extends Primitive {
      pf_object_wait() {
        super("object-wait", PACKAGE_THREADS, true);
      }
      @Override
      public LispObject execute(LispObject object) {
        try {
          object.lockableInstance().wait();
        } catch (InterruptedException e) {
          currentThread().processThreadInterrupts();
        } catch (IllegalMonitorStateException e) {
          return error(new IllegalMonitorState(e.getMessage()));
        }
        return NIL;
      }

      @Override
      public LispObject execute(LispObject object, LispObject timeout) {
        long millis = sleepMillisPart(timeout);
        int nanos = sleepNanosPart(timeout);
        boolean zeroArgP = timeout.ZEROP() != NIL;
          
        try {
          if (millis == 0 && nanos == 0) { 
            if (zeroArgP) {
              object.lockableInstance().wait(0, 0);
            } else {
              object.lockableInstance().wait(0, 1);
            }
          } else {
            object.lockableInstance().wait(millis, nanos);
          }
        } catch (InterruptedException e) {
          currentThread().processThreadInterrupts();
        } catch (IllegalMonitorStateException e) {
          return error(new IllegalMonitorState(e.getMessage()));
        }
        return NIL;
      }
    };

    public static final Primitive OBJECT_NOTIFY
      = new pf_object_notify();
    @DocString(name="object-notify", 
               args="object",
               doc="Wakes up a single thread that is waiting on OBJECT's monitor."
+ "\nIf any threads are waiting on this object, one of them is chosen to be"
+ " awakened. The choice is arbitrary and occurs at the discretion of the"
+ " implementation. A thread waits on an object's monitor by calling one"
+ " of the wait methods.")
    private static final class pf_object_notify extends Primitive {
      pf_object_notify() {
        super("object-notify", PACKAGE_THREADS, true, "object");
      }
      @Override
      public LispObject execute(LispObject object) {
        try {
          object.lockableInstance().notify();
        } catch (IllegalMonitorStateException e) {
          return error(new IllegalMonitorState(e.getMessage()));
        }
        return NIL;
      }
    };

    public static final Primitive OBJECT_NOTIFY_ALL
      = new pf_object_notify_all();
    @DocString(name="object-notify-all", 
               args="object",
               doc="Wakes up all threads that are waiting on this OBJECT's monitor."
+ "\nA thread waits on an object's monitor by calling one of the wait methods.")
    private static final class pf_object_notify_all extends Primitive {
      pf_object_notify_all() {
        super("object-notify-all", PACKAGE_THREADS, true);
      }
      @Override
      public LispObject execute(LispObject object) {
        try {
          object.lockableInstance().notifyAll();
        } catch (IllegalMonitorStateException e) {
          return error(new IllegalMonitorState(e.getMessage()));
        }
        return NIL;
      }
    };
}
