1

I am building an interpreter in C for a simple programming language.

The interpreter is fitted with a built in garbage collector. The GC simply marks all objects which are linked from some root (the call stack, the evaluation stack, loaded modules, etc.) and collects the rest.

I am now writing an extension DLL for the interpreter. The DLL is "wrapping" another C library for graphics (SDL). This library creates objects which represent operating system resources, such as graphical windows. So I now need to think about how the interpreter and GC integrate with the "outside world" in a correct way.

In the current situation, one could write the following code in my language and it will work fine:

import graphics
window = graphics.new_window()
# ... all the rest

However, the following code I suppose will cause problems:

import graphics
graphics.new_window()

Since the result of new_window() isn't stored anywhere, at some point the GC will collect the Window object. When being collected, the Window object will return the native resources to the OS, as part of it's deallocate implementation that is being called by the GC.

I'm not sure if this is reasonable behavior for a programming language. On one hand, if we lose the reference to the Window it doesn't make much sense to leak it.

On the other hand, if I remember correctly - in Java (and I suppose we can find equivalent examples in most languages), it is common and legal to write code such as the following (psuedo Java):

public void makeWindow() {
    new JFrame();
}

This would create a GUI window that will appear on the screen and stay visible. And as you can see, the JFrame object isn't saved anywhere. Still, the GC will not collect it (or at least it doesn't collect the OS resource the JFrame persumebly wraps).

What is the standard way to implement this in a language VM?

The only approach I can think of right now, is that objects that wrap native resources will not free these resources when being claimed by the GC. The only way to free these resources would be with an explicit call in user code to a dispose method on the object. What approach is standard in VM implementations?

5
  • 1
    I have no idea whether the javax.swing.JFrame.<init> constructor actually does this or not, but it could just do something simple as passing this to register with the top-level window object, which would then hold a reference to it. No VM magic required. – Jörg W Mittag Apr 2 '20 at 5:29
  • Bob Nystrum just wrote a chapter about this in his very good online book: Crafting Interpreters – Kain0_0 Apr 2 '20 at 6:11
  • 2
    It might be worth looking into the IDisposable pattern in C# (and other .NET languages I suppose) – Sean Reid Apr 2 '20 at 10:47
  • @Kain0_0 That book is amazing! It's how I learned the subject of interpreter writing. I diverged from the book before the GC chapter was released however (I implemented the GC inspired by a blog post by the same author :)). I just read the chapter. What in it might explain this issue in your opinion? Weak references don't seem to be the answer IMHO. – Aviv Cohn Apr 2 '20 at 18:12
  • Read gchandbook.org – Basile Starynkevitch Apr 3 '20 at 2:15
1

An interpreter that manages allocated objects must have full control over where pointers to those objects are stored. If the interpreter interfaces to external libraries, any allocatable resources in those libraries should be managed only within the interpreter, for simplicity and reliability.

There are many standard garbage collection schemes, and choosing one involves deciding how complex you want the interpreter to become, whether your applications can tolerate pauses of half a second or more while objects are collected, and other considerations.

Allocated resources such as variables, arrays, and objects can have pointers stored in standard places, such as lists of local resources on the stack for local functions. Then they can be deallocated when the scope ends, with no need for complex garbage collection schemes.

Garbage collection can be done in stages known as generations, so only half the GC has to be done when needed. This can support limited delay in garbage collection during critical application functions.

One method for high-speed allocation and freeing of memory, when the language supports this, eliminates garbage collection entirely. This method uses many free lists to store free blocks whose lengths are powers of two in some range. When memory of a certain size needs to be allocated, its log is calculated by shifting right until zero, counting the number of shifts. Then the block is allocated by using the free list corresponding to that number. Blocks are split only when a free list is exhausted. Only if the application runs for a very long time can fragmentation become a problem, but this case must be handled if such applications are to be supported.

In some languages, it is possible to define so-called smart pointers, which hold resource identifiers or pointers. When such pointers are copied, one pointer remains "authoritative" for the resource, and when authoritative pointers are deleted (or finish their lifetime), the resource is freed. Such pointers require little or no support in the interpreter and can be supported in compiled languages.

1

If there is no reference, your window is garbage collected. If there is no reference that you know of and it is not garbage collected, then there is a reference somewhere that you don’t know off.

For example, there might be a “screen” object that holds a reference to all windows.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.