Shop OBEX P1 Docs P2 Docs Learn Events
How do I measure stack length for cog 0? — Parallax Forums

How do I measure stack length for cog 0?

I use the stack length object to measure the stack length of cogs that I launch. I would like to use the stack length object to measure how much free memory that cog 0 uses. It seems that by the time the stack length code executes, some free memory is already in use. I don't know where to point the stack length object. Should I just add code to look for the first unused portion of free memory and point the stack length object there? I'm not getting good results doing this. Any ideas?

Comments

  • The stack pointer for the main cog is stored in the 16-bit word at location 10/11 and can be accessed with WORD[ 10 ]. Rather than use stack length, save the value in WORD[ 10 ] during your program's initialization (say in a global word initialWord10). At appropriate points in your program, just do: length := WORD[ 10 ] - initialWord10.

    If you try to use the stack length object on the main stack, you'll have to allow room above the stack pointer for the stack length object's local and temporary variables since it'll overwrite them during initialization otherwise. You'll have to limit the length of the stack area that's checked because the default stack runs to the end of memory and that would slow things down badly.

    I'd add 64 to the address from WORD[ 10 ] before passing it to Init and limit the stack area tested to at most 128 longs more than you expect to use.
  • I looked at the source of the Spin interpreter, and as far as I can tell, WORD[10] is never updated. Besides, how would that work with multiple cogs? I'm pretty sure it's only read once when the program starts. However, @result + (8, maybe?) should do the same thing.

    The bottom of cog 0's stack is at the boundary from yellow to blue. However, cog 0's stack pointer begins at the end of the stuff in free memory (that's what WORD[10] is). The stuff between the beginning of free/stack RAM and WORD[10] is a stack frame (the stuff that gets pushed when you call a method) so that if your main method returns, it returns to a function in ROM that does cogstop(cogid). However, as Mike Green said, you'll clobber the stack if you give it that pointer, so give it a pointer somewhat after that.
  • WORD[10] is the initial value for DBASE, which is identical to the address of the initial method's RESULT variable. WORD[14] is the initial value for DCURR, which is the starting value for the stack pointer. The stack pointer is maintained in cog RAM and is normally not accessible from a Spin program. It is possible to patch in a bytecode in a Spin program to read the value of the stack pointer, but it's easier to get an approximate value of the stack pointer by using the address of a local variable.
  • Mike GreenMike Green Posts: 23,101
    edited 2015-10-06 03:33
    Sorry for the misleading advice. You'd use WORD[ 10 ] for the initial value of the stack pointer. When you want to check the amount of space used, you'd call
    pri stackAddr | aVariable
       return @aVariable
    

    This pushes the return address and a long for the result variable and the local variable "aVariable", then returns the address of "aVariable" in the stack. Subtract the initial value of the stack pointer to get the amount of stack used including the 8 bytes for the return address and result variable.
  • I will attempt to merge advice provided in this thread so far: At appropriate points in the spin code, execute something like the following to display the cog 0 stack size in bytes:
    pst.dec(stacksize)
    
    pri stackSize
      return @result - word[10] - 8
    

    This seems to work IF I put pst.dec(stacksize) in the BEST location.

    I would prefer to go back after a period of time and determine how much stack space WAS used so I don't have to place these statements in the exact right method that uses the most stack space.

    What do you think will work?

    p.s. For those worried about other cogs, the stack length method works great. This is only about cog 0.
  • ElectrodudeElectrodude Posts: 1,621
    edited 2015-10-07 02:47
    Try the Stack Length object from cog 0. Pass it (word[10] + 32) or so for StackAddr, (make 32 bigger if it doesn't work), and pass 128 or so for the Longs argument. The number it returns will be short by as many bytes (not longs!) as the number you added to @result, and I think it will be relative to the first long that's all $00 (at address $00F0 in your original picture).
  • With your help, I'm still working on fully understanding Cog 0 stack.

    Help me understand word[10]. Let's say word[10] = $4848. That gives a stack/free space of ($7FFF - $4848)/4 = 3565 (decimal) longs. View Info displays 3,568 longs.

    What's up with that?
  • ElectrodudeElectrodude Posts: 1,621
    edited 2015-10-11 22:09
    Two of those longs are the initial stack frame that's in free space. The other is because there are $8000 longs of RAM, not $7FFF, although the highest address is $7FFF.

    ($8000 - $4848) / 4 + 2= 3568
  • That makes sense. Why does F8 "Show Info" display the initial stack frame memory and word[10] show a different location? Are there other situations when this discrepancy is important?
  • word[10] points to where cog 0's stack should start. Cog 0's Spin interpreter isn't started as smartly as when you call cognew yourself - there's a loader program that expects to find all the values it needs at hardcoded addresses in the bottom 16 bytes of hubram. A cognew in your own code calls a helper function that calculates all the neccesary values based on the arguments you pass it and writes the initial stack frame for the new cog and calculates initial pbase, pcurr, vbase, dbase, and dcurr variables for that cog.
  • Are the first two longs in stack/free space preceding the word[10] location available for use?
  • ElectrodudeElectrodude Posts: 1,621
    edited 2015-10-12 15:27
    They are available for use as long as cog 0's main function never returns (i.e. it loops forever or stops itself). If you reuse them and cog 0 returns, bad things will happen.

    I wouldn't recommend using unless you want to play stack tricks. There are many better things you can do if you need more space, like BST's or OpenSpin's unused method removal features and manual optimization.
  • What is wrong with the following silly method?
    VAR
      long StackAddr,UsedLongs,Seed
    
    PUB Start_up | i
      i := 0
      Seed := $55555555
      StackAddr := word[10]
      Long[StackAddr][i++] := Seed
      Long[StackAddr][i++] := Seed
      Long[StackAddr][i++] := Seed
      Long[StackAddr][i++] := Seed
      ... (as many as 64 additional Long[StackAddr][i++] := Seed statements)
      UsedLongs := $8000
      UsedLongs -= StackAddr
      UsedLongs /= 4
      UsedLongs -= i
      i := 0
      repeat UsedLongs
        Long[StackAddr][i] := Long[StackAddr][i++] 
      i--
    

    I am trying to write to Long[StackAddr] the same value that already exists at Long[StackAddr]. This causes irratic behavior.

    Before the repeat statement, my intent is to avoid pushing values on the stack for method calling, parameter passing, and expression evaluation. I have used as many as 64 Long[StackAddr][i++] := Seed, but erratic behavior continues.

    My goal is to better understand the Cog 0 stack.

    What am I doing wrong?

  • another way to do this is to create a new main object, include you original main object as sub object, start the main program in Cog1 and use the regular stack object.

    then you know how much stack will be used.

    alas you need a free cog

    Mike
  • ElectrodudeElectrodude Posts: 1,621
    edited 2015-10-14 02:23
    dbpage wrote: »
    What is wrong with the following silly method?
    VAR
      long StackAddr,UsedLongs,Seed
    
    PUB Start_up | i
      i := 0
      Seed := $55555555
      StackAddr := word[10]
      Long[StackAddr][i++] := Seed
      Long[StackAddr][i++] := Seed
      Long[StackAddr][i++] := Seed
      Long[StackAddr][i++] := Seed
      ... (as many as 64 additional Long[StackAddr][i++] := Seed statements)
      UsedLongs := $8000
      UsedLongs -= StackAddr
      UsedLongs /= 4
      UsedLongs -= i
      i := 0
      repeat UsedLongs
        Long[StackAddr][i] := Long[StackAddr][i++] 
      i--
    

    I am trying to write to Long[StackAddr] the same value that already exists at Long[StackAddr]. This causes irratic behavior.

    Before the repeat statement, my intent is to avoid pushing values on the stack for method calling, parameter passing, and expression evaluation. I have used as many as 64 Long[StackAddr][i++] := Seed, but erratic behavior continues.

    My goal is to better understand the Cog 0 stack.

    What am I doing wrong?


    Doing that will clobber Start_up's stack. Try initializing Seed to at least word[10] + 24. That might not even be big enough - make it bigger if it doesn't work.

    You can't not use the stack at all, even by avoiding function calls. The Spin Interpreter is a stack-based machine - all intermediate calculation results and local variables (including result, which you can't get rid of) live on the stack.

    Try using the Stack Length object. All you should need to do is to offset the initial pointer by the smallest value that doesn't crash the program.
  • Yes. I understand. But there doesn't seem to be a large enough word[10] + x value that works

    The stack length object is not designed for Cog 0. The stack length object works great for other cogs. There is no guidance about how to use the stack length object for Cog 0. This is where my investigation began.

    I should have mentioned that if I omit the repeat statement, there is no issue. Everything works fine without the repeat statement.

    There is something about the repeat statement that clobbers the system, even after 64 Long[StackAddr][i++] := Seed statements. Remove the repeat statement, and the system works as expected.
  • msrobots,
    I like your idea. I will try that. I'm not sure it will help me understand what's going on with the real cog 0, but all I need is predictable and stable behavior.
    Thanks.
  • Put stacklength.init(@result[10], 128) at the beginning of a simple testing program. Does the propeller crash? If it crashes, what if you make the 10 a 20?

    If that doesn't crash it, what does stacklength.GetLength return?
  • ElectrodudeElectrodude Posts: 1,621
    edited 2015-10-14 03:34
    This gives me perfectly reasonable values:
    CON
    
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
    OBJ
    
      stacklength   : "Stack Length"
      pst           : "Parallax Serial Terminal"
    
    PUB Main | x
    
      stacklength.init(@result[16], 512) ' values smaller than 16 might work, but 10 is too small
    
      waitcnt(clkfreq*2 + cnt) ' wait for terminal
    
      pst.start(115200)
    
      pst.newline
      pst.str(string("Starting!"))
      pst.newline
    
      pst.dec(fibo(16)) ' call recursive function, print output to prove it
      pst.newline
    
      pst.dec(stacklength.getlength(0,0) + 16) ' add back in the 16 we skipped
      pst.newline
    
    PUB fibo(x) ' recursive function that uses a bunch of stack
    
      if x == 0 or x == 1
        return 1
    
      return fibo(x-1) + fibo(x-2)
    

    Terminal output:
    Starting!
    1597
    176
    
  • Your suggestion works perfectly. Thanks.

    I find it interesting that @result is the same address as word[10]. What's up with that? Shouldn't word[10] allow space for @result?
  • WORD[10] is the initial value for DBASE. DBASE is the address of the first local variable on the stack, which happens to be RESULT.
Sign In or Register to comment.