views:

1101

answers:

8

I've heard of stackless languages. However I don't have any idea how such a language would be implemented. Can someone explain?

+1  A: 

In the stackless environments I'm more or less familiar with (Turing machine, assembly, and Brainfuck), it's common to implement your own stack. There is nothing fundamental about having a stack built into the language.

In the most practical of these, assembly, you just choose a region of memory available to you, set the stack register to point to the bottom, then increment or decrement to implement your pushes and pops.

EDIT: I know some architectures have dedicated stacks, but they aren't necessary.

Matthew Flaschen
some assembly languages have push/pop and call/return built in, and the stack pointer is a dedicated cpu register. That's what I noticed when I programmed on the z80 anyway.
Breton
You're right though, I suppose you could implement those using other operations if necessary.
Breton
In fact, there's nothing fundamental about building most features into most langauges. Wolframs minimal Turing Machine http://www.wolframscience.com/prizes/tm23/background.html is adequate to implement anything. The point of language features is to make complex computations easier to express. "Stacks" are not mentioned as features in most languages, but recursion is allowed because you can solve lots of useful problems with it. And if you have recursion, you don't wan't to program "stack like" behavior by hand.
Ira Baxter
+3  A: 

I guess in a language like lisp, call frames are stored on the heap, rather than in a stack. This makes it simple to implement closures as you can then treat a call frame as a first class object that is available for garbage collection once the closure is unreferenced. I think of the call frames as being implemented as a kind of graph structure as opposed to a flat structure in other languages.

1800 INFORMATION
Even lisp has a call stack (or at least, Scheme and Common LISP do).
Crashworks
Chicken Scheme even uses the stack for dynamic allocation
Pete Kirkham
+10  A: 

Stackless Python still has a Python stack (though it may have tail call optimization and other call frame merging tricks), but it is completely divorced from the C stack of the interpreter.

Haskell (as commonly implemented) does not have a call stack; evaluation is based on graph reduction.

ephemient
+37  A: 

The modern operating systems we have (Windows, Linux) operate with what I call the "big stack model". And that model is wrong, sometimes, and motivates the need for "stackless" languages.

The "big stack model" assumes that a compiled program will allocate "stack frames" for function calls in a contiguous region of memory, using machine instructions to adjust registers containing the stack pointer (and optional stack frame pointer) very rapidly. This leads to fast function call/return, at the price of having a large, contiguous region for the stack. Because 99.99% of all programs run under these modern OSes work well with the big stack model, the compilers, loaders, and even the OS "know" about this stack area. One common problem all such applications have is, "how big should my stack be?". With memory being dirt cheap, mostly what happens is that a large chunk is set aside for the stack (MS defaults to 1Mb), and typical application call structure never gets anywhere near to using it up. But if an application does use it all up, it dies with an illegal memory reference ("I'm sorry Dave, I can't do that"), by virtue of reaching off the end of its stack.

Most so-called called "stackless" languages aren't really stackless. They just don't use the contiguous stack provided by these systems. What they do instead is allocate a stack frame from the heap on each function call. The cost per function call goes up somewhat; if functions are typically complex, or the language is interpretive, this additional cost is insignificant. (One can also determine call DAGs in the program call graph and allocate a heap segment to cover the entire DAG; this way you get both heap allocation and the speed of classic big-stack function calls for all calls inside the call DAG).

There are several reasons for using heap allocation for stack frames:

1) If the program does deep recursion dependent on the specific problem it is solving, it is very hard to preallocate a "big stack" area in advance because the needed size isn't known. One can awkwardly arrange function calls to check to see if there's enough stack left, and if not, reallocate a bigger chunk, copy the old stack and readjust all the pointers into the stack; that's so awkward that I don't know of any implementations. Allocating stack frames means the application never has to say its sorry until there's literally no allocatable memory left.

2) The program forks subtasks. Each subtask requires its own stack, and therefore can't use the one "big stack" provided. So, one needs to allocate stacks for each subtask. If you have thousands of possible subtasks, you might now need thousands of "big stacks", and the memory demand suddenly gets ridiculous. Allocating stack frames solves this problem. Often the subtask "stacks" refer back to the parent tasks to implement lexical scoping; as subtasks fork, a tree of "substacks" is created called a "cactus stack".

3) Your language has continuations. These require that the data in lexical scope visible to the current function somehow be preserved for later reuse. This can be implemented by copying parent stack frames, climbing up the cactus stack, and proceeding.

The PARLANSE programming langauge I implemented does 1) and 2). I'm working on 3).

Ira Baxter
(2) Well, threads have their own stack and storage area.
Marco van de Voort
All the thread implementations I've seen for Windoze and Linux have the same "big stack" assumption (mostly because the "process" is just a distinguished thread with an associated address space). So all the same issues arise. For PARLANSE, I multiplex Window's threads onto zillions of "grains", each of which uses its own allocated stack frames.
Ira Baxter
Perhaps to clarify, if you are happy with forking a number of subtasks limited by the number of threads your OS offers you (typically a few hundred), perhaps you can live with the big stack model offered by threads. If your parallel/concurrent computations have lots of interactions, you may needs thousands of computational elements, and then the OS thread model fails you.
Ira Baxter
Haskell seriously doesn't use a call stack at all, not even one made up of linked lists through heap space. Think of it as a very advanced macro replacement language :)
ephemient
You might also want to check out http://gcc.gnu.org/wiki/SplitStacks
none
+3  A: 

There is a nice article about the language framework Parrot at http://www.linux-mag.com/cache/7373/1.html. Parrot does not use the stack for calling and this article explains the technique a bit.

Roman Plášil
A: 

There is an easy to understand description of continuations on this article: http://www.defmacro.org/ramblings/fp.html

Continuations are something you can pass into a function in a stack-based language, but which can also be used by a language's own semantics to make it "stackless". Of course the stack is still there, but as Ira Baxter described, it's not one big contiguous segment.

harms
A: 

Call me ancient, but I can remember when the FORTRAN standards and COBOL did not support recursive calls, and therefore didn't require a stack. Indeed, I recall the implementations for CDC 6000 series machines where there wasn't a stack, and FORTRAN would do strange things if you tried to call a subroutine recursively.

Stephen C
Right. As long as you limit the size of the call tree, you can statically allocate all the space needed for activation records (in theory; most applications still have limited call trees, but it is almost impossible for the compiler to figuure out such a layout if there is any call from A to A indirectly). But now all modern versions of FORTRAN and COBOL allow recursion, and stack like behavior has to occur somewhere to implement it.
Ira Baxter
A: 

Please feel free to correct me if I'm wrong, but I would think that allocating memory on the heap for each function call frame would cause extreme memory thrashing. The operating system does after all have to manage this memory. I would think that the way to avoid this memory thrashing would be a cache for call frames. So if you need a cache anyway, we might as well make it contigous in memory and call it a stack.

ssteidl
If you make it contiguous, you have to place a bound on its size. And the bound will prevent you from processing complex recursive applications of large scale. If you want undbounded recursion, either you need an unbounded contiguous stack, or someplace you have to break it into pieces.
Ira Baxter
... and yes, one should use some kind of activation record pool to help ensure locality. With that, it doesn't thrash.
Ira Baxter