3

Scoped languages tend to store the local variables of a given scope or function together in a data structure known as an activation record. Stack frames are examples of instances of activation records. Not all activation records are part of a stack; for example, in ECMAScript/JavaScript they are garbage collected and you can re-enter a scope through a function object.

Does any strongly-typed language exist with activation records as first-class objects, even with severely limited capabilities? What considerations does this tend to raise? For example, this function:

void f( int i ) {
    int j = i + 3;
    {
        int * q = & j;
    }
    {
        char r = 'z';
    }
}

… could map to this data type:

struct f_activation {
    int i;
    int j;
    union {
        int * q;
        char r;
    };
};

This is just an example; an optimizing compiler wouldn't want to nail down such a restrictive mapping.

Assembly language programmers often declare activation records using the same record directive as other data types. TCL has the uplevel command which is sufficient to treat the scopes of calling functions as objects. (I have seen this done. No joke.)

Context of the question: The C++ community is trying to add coroutines to a future language revision. C++ already has a popular design pattern, "function objects," and many would like paused coroutines to act like objects too, ideally with serialization and clean forced-termination.

I'm not looking for a laundry list, but for past experience with this language design direction. However, I'm happy just to get references so I can do deeper reading myself.

Potatoswatter
  • 1,077
  • 1
  • 7
  • 12
  • recommended reading: **[Why do 'some examples' and 'list of things' questions get closed?](http://meta.programmers.stackexchange.com/a/7538/31260)** – gnat Sep 19 '15 at 09:13
  • @gnat I wouldn't have asked unless I had some doubt as to whether any example exists. If there are two, I'll eat my hat. – Potatoswatter Sep 19 '15 at 09:15
  • 1
    Python has [`locals`](https://docs.python.org/2/library/functions.html#locals). – 5gon12eder Sep 19 '15 at 16:05
  • @5gon12eder According to that link, `locals` isn't a method of an activation record object, but a built-in function of the interpreter which only takes a read-only snapshot of the current context. Doesn't appear to fit the pattern. – Potatoswatter Sep 20 '15 at 02:25

2 Answers2

1

Yes, but not quite in the way you're thinking of. This is an integral part of closures, which many languages make use of. Closures are implemented by moving part or all of the activation record of the enclosing method into a new object, which is then passed somewhere and a method is called on that object, which can access the data in the closed-over activation record.

In at least one such language, Delphi, it's possible to use RTTI to get at the members of the closure's activation record, which would be useful for the serialization use case noted in the question. Similar things can probably be done in .NET languages with reflection.

Mason Wheeler
  • 82,151
  • 24
  • 234
  • 309
  • Thanks! Would these be considered hacks in Delphi and the .NET runtime, or supported features? – Potatoswatter Sep 19 '15 at 10:02
  • @Potatoswatter It's very much a hack, but it can be done. – Mason Wheeler Sep 19 '15 at 10:08
  • You can use a buffer overflow, walk the stack and get at any local variable… this is a well-known "programming technique" but not really part of the language. I don't think it's fair to include things that aren't portable between platforms or versions of the same platform. But, dynamic reflection and RTTI might fit the bill… do you have any references to get me started? I'm not really interested in reverse-engineering the JIT just to tell if it's possible. – Potatoswatter Sep 19 '15 at 12:49
  • 1
    @Potatoswatter: I just poked at it a little. If you have a reference to a closure in .NET, you should be able to cast it to `System.Delegate`, which has a `Target` property containing a reference to an instance of the closure object. You can reflect on that without having to reverse-engineer the JIT. – Mason Wheeler Sep 19 '15 at 13:15
1

Interesting question (which I studied in my 1990 PhD thesis, BTW). I'll rather speak of call frames (on the call stack) instead of activation records (which IIRC are relevant for nested functions displays).

It is quite related to continuations, and there are several languages reifying somehow continuations (notably Scheme). Most of the time continuations are considered abstract (e.g. in relation to CPS or call/cc); sometimes there are inspectable (or even mutable). Read also about dynamic software updating, persistence, application checkpointing, Lisp machines.

Read also old A.Appel's book Compiling With Continuations & his old paper Garbage Collection Can Be Faster than Stack Allocation. Read the Flanagan & al paper Essence of Compiling With Continuations.

Read C.Queinnec Lisp In Small Pieces book which explain various strategies and tradeoffs related to your questions.

Notice that closures (used for function abstractions and anonymous functions) are often heap allocated (and they usually want an efficient garbage collection implementation). Look into Ocaml.

Several implementations of languages are able to introspect the call stack, at least for debugging purposes, a minima like the GNU libc backtrace functions. Look for instance into the JVM (see this question) and the JPDA. IIRC, Smalltalk has explicitly reified call stacks.

A practical issue is efficiency (in particular when you have genuine machine code, e.g. produced by JIT techniques), since most calling conventions do not require that all arguments go thru the processor stack, and a few of them go only thru registers (and might not even be spilled on the stack). Efficiency consideration and compatibility with existing ABIs and calling conventions could explain lack of standard call-stack introspection techniques (since efficient register allocation is hostile to full stack introspection).

BTW, a precise garbage collector is in need of call stack introspection (since it needs to trace local variables pointing to GC-ed values or objects). So look also into some implementations of Scheme entirely or mostly written in Scheme (e.g. Bones, S48, Chicken).

The SBCL implementation of Common Lisp is also able to introspect call stacks (and both its debugger and its compiler is written in SBCL).

Look also into Lua & Neko & Parrot. IIRC, their bytecode VM is introspectable (including the stack[s]).

Look also into Lisaac project.

See also the nearly obsolete setcontext(3) (on Linux and some other Unixes) & longjmp(3). Notice that C++ destructors and exceptions (and threads) are related to the call stack.

Read the technical appendix of J.Pitrat's book Artificial Beings, the Conscience of a Conscious Machine and his blog which have several explanations about why introspecting the call stack can be practically useful.

BTW, my MELT stuff (a Lispy domain specific language to customize GCC) nearly has introspectable call stack (the internal implementation has it, but I might not yet have exposed it).

Basile Starynkevitch
  • 32,434
  • 6
  • 84
  • 125
  • 1
    Yes, the fundamental conflict with register allocation is definitely a consideration. (The question was meant to touch on this.) I think that garbage collectors fall a bit short, because they don't have time or space to worry about variable names. But run-time debugging support is right on the mark. – Potatoswatter Sep 19 '15 at 22:06
  • GC don't worry about variable names, but worry a lot about variable *values* – Basile Starynkevitch Sep 19 '15 at 23:55
  • Note that the register allocation issue is complicated further by ABIs that reserve one or more registers for temporary storage for the caller of the current function - functions that modify such registers are required to save the value and restore it after use. The C ABI used on x86 is an example (IIRC ESI and EDI are reserved for the caller). This means that determining the location of a value isn't even possible to do just by inspecting the code of a function; you also need to examine functions that it calls, and so on. – Jules Sep 20 '15 at 16:37