Changeset 14579


Ignore:
Timestamp:
08/18/13 22:13:57 (4 years ago)
Author:
mevenson
Message:

Lazily create the little used portions of the Lisp stack.

Aggressively cache and control the use of memory by the underlying
Lisp stack frame representation by introducing the private
LispThread?.StackFrame? and LispThread?.StackSegments? classes.

Dmitry Nadezhin contributes the following:

LispStackFrame? object are allocated on every
LispThread?.execute(...) .

However, they are seldom [accessed] ([... verify via] inspect[tion
of the] stack trace). This patch delays allocation of
LispStackFrame? objects until they are requested.

Raw information about stack frames is stored in stack. Stack is an
Object[] array (more precisely a list of [...]4 [Mib] Object[]
arrays).

Implement org.armedbear.lisp.protocol.Inspectable on StackSegment?
(incomplete). We are going to need a way to try to less agressively
grab 4Mib chunks in low memory situations.

Promote comments to javadoc in LispThread? where it makes sense
(incomplete).

Remove "first", "second", "third" accessor forms in LispStackFrame? in
favor of exclusive use of varargs. This may affect usage on pre
jre-1.5.0 JVMs. Users who have trouble with this requirement should
consider weaving pre-1.5 bytecode with appropiate thunks for vararg
calls.

Location:
trunk/abcl/src/org/armedbear/lisp
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/abcl/src/org/armedbear/lisp/LispStackFrame.java

    r14572 r14579  
    33 *
    44 * Copyright (C) 2009 Mark Evenson
    5  * $Id$
     5 * $Id: LispStackFrame.java 14572 2013-08-10 08:24:46Z mevenson $
    66 *
    77 * This program is free software; you can redistribute it and/or
     
    4040{
    4141  public final LispObject operator;
    42   private final LispObject first;
    43   private final LispObject second;
    44   private final LispObject third;
    4542  private final LispObject[] args;
    4643
     
    5653  private final static LispObject UNAVAILABLE_ARG = new UnavailableArgument();
    5754
    58   public LispStackFrame(LispObject operator)
     55  public LispStackFrame(Object[] stack, int framePos, int numArgs)
    5956  {
    60     this.operator = operator;
    61     first = null;
    62     second = null;
    63     third = null;
    64     args = null;
    65   }
    66 
    67   public LispStackFrame(LispObject operator, LispObject arg)
    68   {
    69     this.operator = operator;
    70     first = arg;
    71     second = null;
    72     third = null;
    73     args = null;
    74   }
    75 
    76   public LispStackFrame(LispObject operator, LispObject first,
    77       LispObject second)
    78   {
    79     this.operator = operator;
    80     this.first = first;
    81     this.second = second;
    82     third = null;
    83     args = null;
    84   }
    85 
    86   public LispStackFrame(LispObject operator, LispObject first,
    87       LispObject second, LispObject third)
    88 
    89   {
    90     this.operator = operator;
    91     this.first = first;
    92     this.second = second;
    93     this.third = third;
    94     args = null;
    95   }
    96 
    97   public LispStackFrame(LispObject operator, LispObject... args)
    98   {
    99     this.operator = operator;
    100     first = null;
    101     second = null;
    102     third = null;
    103     this.args = args;
     57    operator = (LispObject) stack[framePos];
     58    args = new LispObject[numArgs];
     59    for (int i = 0; i < numArgs; i++)
     60    {
     61      args[i] = (LispObject) stack[framePos + 1 + i];
     62    }
    10463  }
    10564
     
    152111  }
    153112
    154   private LispObject argsToLispList() 
     113  private LispObject argsToLispList()
    155114
    156115  {
    157116    LispObject result = Lisp.NIL;
    158     if (args != null) {
    159       for (int i = 0; i < args.length; i++)
    160         // `args' come here from LispThread.execute. I don't know
    161         // how it comes that some callers pass NULL ptrs around but
    162         // we better do not create conses with their CAR being NULL;
    163         // it'll horribly break printing such a cons; and probably
    164         // other bad things may happen, too. --TCR, 2009-09-17.
    165         if (args[i] == null)
    166           result = result.push(UNAVAILABLE_ARG);
    167         else
    168           result = result.push(args[i]);
    169     } else {
    170       do {
    171   if (first != null)
    172     result = result.push(first);
    173   else
    174     break;
    175   if (second != null)
    176     result = result.push(second);
    177   else
    178     break;
    179   if (third != null)
    180     result = result.push(third);
    181   else
    182     break;
    183       } while (false);
    184     }
     117    for (int i = 0; i < args.length; i++)
     118      // `args' come here from LispThread.execute. I don't know
     119      // how it comes that some callers pass NULL ptrs around but
     120      // we better do not create conses with their CAR being NULL;
     121      // it'll horribly break printing such a cons; and probably
     122      // other bad things may happen, too. --TCR, 2009-09-17.
     123      if (args[i] == null)
     124        result = result.push(UNAVAILABLE_ARG);
     125      else
     126        result = result.push(args[i]);
    185127    return result.nreverse();
    186128  }
     
    198140    }
    199141    return new SimpleString(result);
     142  }
     143
     144  public int getNumArgs()
     145  {
     146    return args.length;
    200147  }
    201148
  • trunk/abcl/src/org/armedbear/lisp/LispThread.java

    r14465 r14579  
    33 *
    44 * Copyright (C) 2003-2007 Peter Graves
    5  * $Id$
     5 * $Id: LispThread.java 14465 2013-04-24 12:50:37Z rschlatte $
    66 *
    77 * This program is free software; you can redistribute it and/or
     
    215215    }
    216216
    217     // Used by the JVM compiler for MULTIPLE-VALUE-CALL.
     217    /** Used by the JVM compiler for MULTIPLE-VALUE-CALL. */
    218218    public final LispObject[] accumulateValues(LispObject result,
    219219                                               LispObject[] oldValues)
     
    316316    }
    317317
    318     // Forces a single value, for situations where multiple values should be
    319     // ignored.
     318   /**
     319    * Force a single value, for situations where multiple values should be
     320    * ignored.
     321    */
    320322    public final LispObject value(LispObject obj)
    321323    {
     
    598600
    599601
    600     private StackFrame stack = null;
    601 
    602    public final void pushStackFrame(StackFrame frame)
    603     {
    604         frame.setNext(stack);
    605         stack = frame;
    606     }
    607 
    608     public final void popStackFrame()
    609     {
     602    private static class StackMarker {
     603
     604        final int numArgs;
     605
     606        StackMarker(int numArgs) {
     607            this.numArgs = numArgs;
     608        }
     609
     610        int getNumArgs() {
     611            return numArgs;
     612        }
     613    }
     614
     615    // markers for args
     616    private final static StackMarker STACK_MARKER_0 = new StackMarker(0);
     617    private final static StackMarker STACK_MARKER_1 = new StackMarker(1);
     618    private final static StackMarker STACK_MARKER_2 = new StackMarker(2);
     619    private final static StackMarker STACK_MARKER_3 = new StackMarker(3);
     620    private final static StackMarker STACK_MARKER_4 = new StackMarker(4);
     621    private final static StackMarker STACK_MARKER_5 = new StackMarker(5);
     622    private final static StackMarker STACK_MARKER_6 = new StackMarker(6);
     623    private final static StackMarker STACK_MARKER_7 = new StackMarker(7);
     624    private final static StackMarker STACK_MARKER_8 = new StackMarker(8);
     625
     626    private final int STACK_FRAME_EXTRA = 2;
     627    // a LispStackFrame with n arguments occupies n + STACK_FRAME_EXTRA elements
     628    // in {@code stack} array.
     629    // stack[framePos] == operation
     630    // stack[framePos + 1 + i] == arg[i]
     631    // stack[framePos + 1 + n] == initially SrackMarker(n)
     632    // LispStackFrame object may be lazily allocated later.
     633    // In this case it is stored in stack framePos + 1 + n]
     634    //
     635    // Java stack frame occupies 1 element
     636    // stack[framePos] == JavaStackFrame
     637    //
     638    // Stack consists of a list of StackSegments.
     639    // Top StackSegment is cached in variables stack and stackPtr.
     640    private StackSegment topStackSegment = new StackSegment(INITIAL_SEGMENT_SIZE, null);
     641    private Object[] stack = topStackSegment.stack;
     642    private int stackPtr = 0;
     643    private StackSegment spareStackSegment;
     644   
     645    private static class StackSegment
     646      implements org.armedbear.lisp.protocol.Inspectable
     647    {
     648        final Object[] stack;
     649        final StackSegment next;
     650        int stackPtr;
     651       
     652        StackSegment(int size, StackSegment next) {
     653            stack = new Object[size];
     654            this.next = next;
     655        }
     656        public LispObject getParts() {
     657      Cons result = new Cons(NIL);
     658      return result
     659    .push(new Symbol("INITIAL-SEGMENT-SIZE"))
     660                    .push(LispInteger.getInstance(LispThread.INITIAL_SEGMENT_SIZE))
     661    .push(new Symbol("SEGMENT-SIZE"))
     662                    .push(LispInteger.getInstance(LispThread.SEGMENT_SIZE)).nreverse();
     663        }
     664    }
     665   
     666    private void ensureStackCapacity(int itemsToPush) {
     667        if (stackPtr + (itemsToPush - 1) >= stack.length)
     668            grow(itemsToPush);
     669    }
     670
     671    private static final int INITIAL_SEGMENT_SIZE = 1 << 10;
     672    private static final int SEGMENT_SIZE = (1 << 19) - 4; // 4 MiB page on x86_64
     673
     674    private void grow(int numEntries) {
     675        topStackSegment.stackPtr = stackPtr;
     676        if (spareStackSegment != null) {
     677            // Use spare segement if available
     678            if (stackPtr > 0 && spareStackSegment.stack.length >= numEntries) {
     679                topStackSegment = spareStackSegment;
     680                stack = topStackSegment.stack;
     681                spareStackSegment = null;
     682                stackPtr = 0;
     683                return;
     684            }
     685            spareStackSegment = null;
     686        }
     687        int newSize = stackPtr + numEntries;
     688        if (topStackSegment.stack.length < SEGMENT_SIZE || stackPtr == 0) {
     689            // grow initial segment from initial size to standard size
     690            int newLength = Math.max(newSize, Math.min(SEGMENT_SIZE, stack.length * 2));
     691            StackSegment newSegment = new StackSegment(newLength, topStackSegment.next);
     692            System.arraycopy(stack, 0, newSegment.stack, 0, stackPtr);
     693            topStackSegment = newSegment;
     694            stack = topStackSegment.stack;
     695            return;
     696        }
     697        // Allocate new segment
     698        topStackSegment = new StackSegment(Math.max(SEGMENT_SIZE, numEntries), topStackSegment);
     699        stack = topStackSegment.stack;
     700        stackPtr = 0;
     701    }
     702
     703    private StackFrame getStackTop() {
     704        topStackSegment.stackPtr = stackPtr;
     705        if (stackPtr == 0) {
     706            assert topStackSegment.next == null;
     707            return null;
     708        }
     709        StackFrame prev = null;
     710        for (StackSegment segment = topStackSegment; segment != null; segment = segment.next) {
     711            Object[] stk = segment.stack;
     712            int framePos = segment.stackPtr;
     713            while (framePos > 0) {
     714                Object stackObj = stk[framePos - 1];
     715                if (stackObj instanceof StackFrame) {
     716                    if (prev != null) {
     717                        prev.setNext((StackFrame) stackObj);
     718                    }
     719                    return (StackFrame) stack[stackPtr - 1];
     720                }
     721                StackMarker marker = (StackMarker) stackObj;
     722                int numArgs = marker.getNumArgs();
     723                LispStackFrame frame = new LispStackFrame(stk, framePos - numArgs - STACK_FRAME_EXTRA, numArgs);
     724                stk[framePos - 1] = frame;
     725                if (prev != null) {
     726                    prev.setNext(frame);
     727                }
     728                prev = frame;
     729                framePos -= numArgs + STACK_FRAME_EXTRA;
     730            }
     731        }
     732        return (StackFrame) stack[stackPtr - 1];
     733    }
     734   
     735    public final void pushStackFrame(JavaStackFrame frame) {
     736        frame.setNext(getStackTop());
     737        ensureStackCapacity(1);
     738        stack[stackPtr] = frame;
     739        stackPtr += 1;
     740    }
     741
     742    private void popStackFrame(int numArgs) {
    610743        // Pop off intervening JavaFrames until we get back to a LispFrame
    611         while (stack != null && stack instanceof JavaStackFrame) {
    612             stack = stack.getNext();
    613         }
    614         if (stack != null)
    615             stack = stack.getNext();
     744        Object stackObj = stack[stackPtr - 1];
     745        if (stackObj instanceof StackMarker) {
     746            assert numArgs == ((StackMarker) stackObj).getNumArgs();
     747        } else {
     748            while (stackObj instanceof JavaStackFrame) {
     749                stack[--stackPtr] = null;
     750                stackObj = stack[stackPtr - 1];
     751            }
     752            if (stackObj instanceof StackMarker) {
     753                assert numArgs == ((StackMarker) stackObj).getNumArgs();
     754            } else {
     755                assert numArgs == ((LispStackFrame) stackObj).getNumArgs();
     756            }
     757        }
     758        stackPtr -= numArgs + STACK_FRAME_EXTRA;
     759        for (int i = 0; i < numArgs + STACK_FRAME_EXTRA; i++) {
     760            stack[stackPtr + i] = null;
     761        }
     762        if (stackPtr == 0) {
     763            popStackSegment();
     764        }
     765    }
     766   
     767    private void popStackSegment() {
     768        topStackSegment.stackPtr = 0;
     769        if (topStackSegment.next != null) {
     770            spareStackSegment = topStackSegment;
     771            topStackSegment = topStackSegment.next;
     772            stack = topStackSegment.stack;
     773        }
     774        stackPtr = topStackSegment.stackPtr;
    616775    }
    617776
    618777    public final Environment setEnv(Environment env) {
    619         return (stack != null) ? stack.setEnv(env) : null;
     778        StackFrame stackTop = getStackTop();
     779        return (stackTop != null) ? stackTop.setEnv(env) : null;
    620780    }
    621781
    622782    public void resetStack()
    623783    {
    624         stack = null;
     784        topStackSegment = new StackSegment(INITIAL_SEGMENT_SIZE, null);
     785        stack = topStackSegment.stack;
     786        spareStackSegment = null;
     787        stackPtr = 0;
    625788    }
    626789
     
    628791    public LispObject execute(LispObject function)
    629792    {
    630         pushStackFrame(new LispStackFrame(function));
     793        ensureStackCapacity(STACK_FRAME_EXTRA);
     794        stack[stackPtr] = function;
     795        stack[stackPtr + 1] = STACK_MARKER_0;
     796        stackPtr += STACK_FRAME_EXTRA;
    631797        try {
    632798            return function.execute();
    633799        }
    634800        finally {
    635             popStackFrame();
     801            popStackFrame(0);
    636802        }
    637803    }
     
    640806    public LispObject execute(LispObject function, LispObject arg)
    641807    {
    642         pushStackFrame(new LispStackFrame(function, arg));
     808        ensureStackCapacity(1 + STACK_FRAME_EXTRA);
     809        stack[stackPtr] = function;
     810        stack[stackPtr + 1] = arg;
     811        stack[stackPtr + 2] = STACK_MARKER_1;
     812        stackPtr += 1 + STACK_FRAME_EXTRA;
    643813        try {
    644814            return function.execute(arg);
    645815        }
    646816        finally {
    647             popStackFrame();
     817            popStackFrame(1);
    648818        }
    649819    }
     
    653823                              LispObject second)
    654824    {
    655         pushStackFrame(new LispStackFrame(function, first, second));
     825        ensureStackCapacity(2 + STACK_FRAME_EXTRA);
     826        stack[stackPtr] = function;
     827        stack[stackPtr + 1] = first;
     828        stack[stackPtr + 2] = second;
     829        stack[stackPtr + 3] = STACK_MARKER_2;
     830        stackPtr += 2 + STACK_FRAME_EXTRA;
    656831        try {
    657832            return function.execute(first, second);
    658833        }
    659834        finally {
    660             popStackFrame();
     835            popStackFrame(2);
    661836        }
    662837    }
     
    666841                              LispObject second, LispObject third)
    667842    {
    668         pushStackFrame(new LispStackFrame(function, first, second, third));
     843        ensureStackCapacity(3 + STACK_FRAME_EXTRA);
     844        stack[stackPtr] = function;
     845        stack[stackPtr + 1] = first;
     846        stack[stackPtr + 2] = second;
     847        stack[stackPtr + 3] = third;
     848        stack[stackPtr + 4] = STACK_MARKER_3;
     849        stackPtr += 3 + STACK_FRAME_EXTRA;
    669850        try {
    670851            return function.execute(first, second, third);
    671852        }
    672853        finally {
    673             popStackFrame();
     854            popStackFrame(3);
    674855        }
    675856    }
     
    680861                              LispObject fourth)
    681862    {
    682         pushStackFrame(new LispStackFrame(function, first, second, third, fourth));
     863        ensureStackCapacity(4 + STACK_FRAME_EXTRA);
     864        stack[stackPtr] = function;
     865        stack[stackPtr + 1] = first;
     866        stack[stackPtr + 2] = second;
     867        stack[stackPtr + 3] = third;
     868        stack[stackPtr + 4] = fourth;
     869        stack[stackPtr + 5] = STACK_MARKER_4;
     870        stackPtr += 4 + STACK_FRAME_EXTRA;
    683871        try {
    684872            return function.execute(first, second, third, fourth);
    685873        }
    686874        finally {
    687             popStackFrame();
     875            popStackFrame(4);
    688876        }
    689877    }
     
    694882                              LispObject fourth, LispObject fifth)
    695883    {
    696         pushStackFrame(new LispStackFrame(function, first, second, third, fourth, fifth));
     884        ensureStackCapacity(5 + STACK_FRAME_EXTRA);
     885        stack[stackPtr] = function;
     886        stack[stackPtr + 1] = first;
     887        stack[stackPtr + 2] = second;
     888        stack[stackPtr + 3] = third;
     889        stack[stackPtr + 4] = fourth;
     890        stack[stackPtr + 5] = fifth;
     891        stack[stackPtr + 6] = STACK_MARKER_5;
     892        stackPtr += 5 + STACK_FRAME_EXTRA;
    697893        try {
    698894            return function.execute(first, second, third, fourth, fifth);
    699895        }
    700896        finally {
    701             popStackFrame();
     897            popStackFrame(5);
    702898        }
    703899    }
     
    709905                              LispObject sixth)
    710906    {
    711         pushStackFrame(new LispStackFrame(function, first, second,
    712             third, fourth, fifth, sixth));
     907        ensureStackCapacity(6 + STACK_FRAME_EXTRA);
     908        stack[stackPtr] = function;
     909        stack[stackPtr + 1] = first;
     910        stack[stackPtr + 2] = second;
     911        stack[stackPtr + 3] = third;
     912        stack[stackPtr + 4] = fourth;
     913        stack[stackPtr + 5] = fifth;
     914        stack[stackPtr + 6] = sixth;
     915        stack[stackPtr + 7] = STACK_MARKER_6;
     916        stackPtr += 6 + STACK_FRAME_EXTRA;
    713917        try {
    714918            return function.execute(first, second, third, fourth, fifth, sixth);
    715919        }
    716920        finally {
    717             popStackFrame();
     921            popStackFrame(6);
    718922        }
    719923    }
     
    725929                              LispObject sixth, LispObject seventh)
    726930    {
    727         pushStackFrame(new LispStackFrame(function, first, second, third,
    728             fourth, fifth, sixth, seventh));
     931        ensureStackCapacity(7 + STACK_FRAME_EXTRA);
     932        stack[stackPtr] = function;
     933        stack[stackPtr + 1] = first;
     934        stack[stackPtr + 2] = second;
     935        stack[stackPtr + 3] = third;
     936        stack[stackPtr + 4] = fourth;
     937        stack[stackPtr + 5] = fifth;
     938        stack[stackPtr + 6] = sixth;
     939        stack[stackPtr + 7] = seventh;
     940        stack[stackPtr + 8] = STACK_MARKER_7;
     941        stackPtr += 7 + STACK_FRAME_EXTRA;
    729942        try {
    730943            return function.execute(first, second, third, fourth, fifth, sixth,
     
    732945        }
    733946        finally {
    734             popStackFrame();
     947            popStackFrame(7);
    735948        }
    736949    }
     
    742955                              LispObject eighth)
    743956    {
    744         pushStackFrame(new LispStackFrame(function, first, second, third,
    745             fourth, fifth, sixth, seventh, eighth));
     957        ensureStackCapacity(8 + STACK_FRAME_EXTRA);
     958        stack[stackPtr] = function;
     959        stack[stackPtr + 1] = first;
     960        stack[stackPtr + 2] = second;
     961        stack[stackPtr + 3] = third;
     962        stack[stackPtr + 4] = fourth;
     963        stack[stackPtr + 5] = fifth;
     964        stack[stackPtr + 6] = sixth;
     965        stack[stackPtr + 7] = seventh;
     966        stack[stackPtr + 8] = eighth;
     967        stack[stackPtr + 9] = STACK_MARKER_8;
     968        stackPtr += 8 + STACK_FRAME_EXTRA;
    746969        try {
    747970            return function.execute(first, second, third, fourth, fifth, sixth,
     
    749972        }
    750973        finally {
    751             popStackFrame();
     974            popStackFrame(8);
    752975        }
    753976    }
     
    755978    public LispObject execute(LispObject function, LispObject[] args)
    756979    {
    757         pushStackFrame(new LispStackFrame(function, args));
     980        ensureStackCapacity(args.length + STACK_FRAME_EXTRA);
     981        stack[stackPtr] = function;
     982        System.arraycopy(args, 0, stack, stackPtr + 1, args.length);
     983        stack[stackPtr + args.length + 1] = new StackMarker(args.length);
     984        stackPtr += args.length + STACK_FRAME_EXTRA;
    758985        try {
    759986            return function.execute(args);
    760987        }
    761988        finally {
    762             popStackFrame();
     989            popStackFrame(args.length);
    763990        }
    764991    }
     
    771998    public void printBacktrace(int limit)
    772999    {
    773         if (stack != null) {
     1000        StackFrame stackTop = getStackTop();
     1001        if (stackTop != null) {
    7741002            int count = 0;
    7751003            Stream out =
     
    7781006            out._finishOutput();
    7791007
    780             StackFrame s = stack;
     1008            StackFrame s = stackTop;
    7811009            while (s != null) {
    7821010                out._writeString("  ");
     
    7961024    public LispObject backtrace(int limit)
    7971025    {
     1026        StackFrame stackTop = getStackTop();
    7981027        LispObject result = NIL;
    799         if (stack != null) {
     1028        if (stackTop != null) {
    8001029            int count = 0;
    801             StackFrame s = stack;
     1030            StackFrame s = stackTop;
    8021031            while (s != null) {
    8031032                result = result.push(s);
     
    8121041    public void incrementCallCounts()
    8131042    {
    814         StackFrame s = stack;
    815 
    816         for (int i = 0; i < 8; i++) {
    817             if (s == null)
    818                 break;
    819       if (s instanceof LispStackFrame) {
    820     LispObject operator = ((LispStackFrame)s).getOperator();
    821     if (operator != null) {
    822         operator.incrementHotCount();
    823         operator.incrementCallCount();
    824     }
    825     s = s.getNext();
    826       }
    827         }
    828 
    829         while (s != null) {
    830       if (s instanceof LispStackFrame) {
    831     LispObject operator = ((LispStackFrame)s).getOperator();
    832     if (operator != null)
    833         operator.incrementCallCount();
    834       }
    835       s = s.getNext();
     1043        topStackSegment.stackPtr = stackPtr;
     1044        int depth = 0;
     1045        for (StackSegment segment = topStackSegment; segment != null; segment = segment.next) {
     1046            Object[] stk = segment.stack;
     1047            int framePos = segment.stackPtr;
     1048            while (framePos > 0) {
     1049                depth++;
     1050                Object stackObj = stk[framePos - 1];
     1051                int numArgs;
     1052                if (stackObj instanceof StackMarker) {
     1053                    numArgs = ((StackMarker) stackObj).getNumArgs();
     1054                } else if (stackObj instanceof LispStackFrame) {
     1055                    numArgs = ((LispStackFrame) stackObj).getNumArgs();
     1056                } else {
     1057                    assert stackObj instanceof JavaStackFrame;
     1058                    framePos--;
     1059                    continue;
     1060                }
     1061                // lisp stack frame
     1062                framePos -= numArgs + STACK_FRAME_EXTRA;
     1063                LispObject operator = (LispObject) stack[framePos];
     1064                if (operator != null) {
     1065                    if (depth <= 8) {
     1066                        operator.incrementHotCount();
     1067                    }
     1068                    operator.incrementCallCount();
     1069                }
     1070            }
    8361071        }
    8371072    }
Note: See TracChangeset for help on using the changeset viewer.