How about comparing Factor to the execution of a C program.  If only  
C used a stack...but it does!  The callstack in C keeps a stack frame  
for each function, which consists of the arguments to the function,  
the return address to the previous stack frame, and the local  
variables.  You don't have to execute a C program to see what's going  
on because you can look at the disassembly and run it in your mind.

Here's an unoptimized C program that computes (e^3)^(3^2)

#include <stdio.h>
#include <math.h>
float foo(float w) {
   float x,y,z;
   x = exp(w);
   y = sqrt(w);
   z = pow(x,y);
   return z;
}

int main(void) {
   printf("%f\n", foo(3.0f));
   return 0;
}

// gcc foo.c -lm


Here's what happens in pseudo-assembly language:

push 3.0
call foo
   push w
   x = call exp
   push w
   y = call sqrt
   push y
   push x
   z = call pow
   drop_stack_frame // foo's stack frame gets destroyed
return z

That is exactly analogous to the factor code:

3
! foo
   dup exp
   over sqrt
   2dup ^
   3nip  ! drop_stack_frame
! return z

In both cases, we push the function arguments before calling the  
function.  This is how C/ASM/Factor works.  Factor gets away without  
naming the function arguments and local variables, but it's exactly  
analogous to if you wrote it in C like this:

float foo(float w) {
   return pow(exp(w), sqrt(w));  // no temporary x,y,z
}

Notice how the Factor code (above) looks pretty bad--it 3nips the  
w,x,y variables.  We can optimize it to reduce the number of shuffle  
words called by removing some of the copying of the "local" variables.

3 dup exp swap sqrt ^

Now it doesn't leave three temporary variables on the stack when it's  
done, and it's clearer how the dataflow looks.  However, if we had to  
reuse w,x,y at the end, we couldn't optimize as much because we're in  
essence throwing their values away as we use them.  You can't do this  
in C because they stick around til the end of your function, when the  
stack frame goes away.  All you can do is avoid using local variables.


Now let's explore how this applies to pushing to vectors and adding  
to arrays in Factor.

: push ( elt seq -- ) ... ;
: add ( seq elt -- newseq ) ... ;

V{ } dup 1 over push .
   V{ 1 }
   V{ 1 }

{ } dup 1 add
   { }
   { 1 }

Notice that while using a vector modifies the vector literal, adding  
to an array instead creates an entirely new array.  Making an  
entirely new array is expensive -- if you're willing to pay this  
price, chances are you don't need the old array.  Thus, the stack  
effect is such that it throws this array away for you, rather than  
making you do it manually.  So, add is optimized for stack shuffling,  
even though it still works like the C example above.

In the ideal case, you wouldn't need any temporary/local variables  
and each value would be used, and the intermediate result would flow  
into the next calculation,
    e.g.
      3 sqrt exp sq sin tan
      tan(sin(sq(exp(sqrt(3)))));  // most calculations are not like  
this..

So, it turns out stack shuffling is the glue for reusing variables  
without naming them, and we need this glue in all but a very few  
places where it's natural to optimize it away.  From analysis of the  
Factor source repository and experience, words that modify a  
structure or retain it for later use "dup" their arguments, and words  
that return new structures usually do not.

Words that dup their arguments: push, set-nth, nth, set-nth
Words that usually do not dup their arguments: add, remove, prune,  
map, subset, >upper

Doug

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Factor-talk mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/factor-talk

Reply via email to