Skip to content

Working in a @nogc environment

p0nce edited this page Oct 3, 2022 · 34 revisions

Probably the largest hurdle to cross when learning Dplug is getting used to working without the runtime enabled. It may be confusing or even a little frustrating at first, but once you learn a few key tricks, it's a breeze. Here are some important things to remember when writing Plug-ins with Dplug.

Dplug does not use the -betterC switch, but instead lets the runtime be disabled. This allows us to get back most features of the language, except for:

  • Heap closures
  • Module constructors and destructors: static this(), static ~this() shared static this() and shared static ~this()
  • TLS variables
  • Built-in associative arrays (replace them with dplug.core.map)

No runtime enabled implies no GC, however the GC's code is still here: if a GC allocation happens, the program will either crash or spinlock endlessly. @nogc effectively helps prevent that, and is used everywhere, along with nothrow eventually.

Allocating Memory

Slices

Slices can be allocated using malloc/free, and the functions mallocSlice, mallocSliceNoInit, and freeSlice provide wrappers to do just that.

Example:

float[] myBuf = mallocSliceNoInit(128);

// later
freeSlice(myBuf);

Another even more handy way to allocate @nogc arrays is to use reallocBuffer. This function also takes an alignment.

Example:

float[] myAudioBuf;
myAudioBuf.reallocBuffer(frames, 16); // takes optional alignment (default = 1)

// later
myAudioBuf.reallocBuffer(0, 16);      // must repeat alignment

@nogc Data Structures

  • Dynamic array: You'll find a Vec!T in dplug.core.alignedbuffer.

  • Map: You'll find a Map!T b-tree based map in dplug.core.map.

  • Set: You'll find a Set!T dynamic set in dplug.core.map.

Aligned memory

Dplug generally uses malloc throughout, however some constructs use alignedMalloc, alignedRealloc and alignedFree.

Don't mix and match memory allocated with malloc and memory allocated with alignedMalloc: some additional bytes are used.

Getting back Classes

Since there is no runtime enabled (hence no GC) the keyword new cannot be used to allocate memory for classes. Instead, classes must be allocated using auto mallocNew(T, Args...)(Args args).

Once you are finished using the class, you may deallocate it using void destroyFree(T)(T p)

Example:

class Foo
{
public:
    this(int i)
    {
        f = i;
    }
private:
    int f;
}

// Create a new object of type foo and pass 3 to its constructor.
Foo foo = mallocNew!Foo(3);

// Done using the class so the memory can be freed.
foo.destroyFree();

Getting back Array Literals

See: https://p0nce.github.io/d-idioms/#@nogc-Array-Literals:-Breaking-the-Limits

Getting back Exceptions

If you need an exception in runtime-free D, use the following construct:

Example:

try
{
    if (error)
        throw mallocNew!Exception("An error message without format() call");
}
catch(Exception e)
{
    // do something with e
    e.destroyFree();
}

Features you should avoid

  • Thread-Local Storage (TLS) can't be used without the D runtime
  • Global constructors/destructors (static this and static ~this)

Example:

// At top-level
module mymodule;

int myTLSvar; // NO, can't have TLS variables


shared static this() // NO, can't have static constructors
{
    doSomething();
}

Standard Library

One disadvantage of using @nogc is that much of the standard library is off limits. If you are unsure of what you can and can't use, you can check the documentation here Phobos Documentation, if the function is nothrow @nogc, then it can be used. Most of the core modules can be used, and everything in core.stdc is nothrow @nogc.

Note that template functions have their attributes inferred most of the time: https://p0nce.github.io/d-idioms/#Automatic-attribute-inference-for-function-templates

C runtime traps

Beware of the C locale! In a plugin, the host could change the C locale. It is therefore a trap to call printf/scanf/strtod/atof and friends directly! See dplug.core.string for alternatives to parse numbers. When printing numbers, there is no solution yet.

CTFE

An exception to these rules is when using CTFE(Compile-Time Function Evaluation). Since the D runtime is available during compile time, it is possible to use every feature using the GC: new, heap closures, array literals, GC slices, etc.

Quite ironically, compile-time code doesn't have the restrictions discussed in this very article. It might well be easier to parse something at compile-time than runtime in Dplug!

Another exception is host applications - using dplug:host - will have the runtime enabled.

In general, nothrow @nogc code can be used both by regular code using the runtime and nothrow @nogc code.

Note: Efforts to enable back the runtime have always failed unfortunately. See: https://github.com/AuburnSounds/Dplug/issues/292