Skip to content

Commit

Permalink
Fix bugzilla 23812 - ImportC: allow adding function attributes to imp…
Browse files Browse the repository at this point in the history
…orted C functions

This adds a new pragma for ImportC, which allows to set default storage
classes. Only `nothrow`, `@nogc` and `pure` are supported for now.
They can be disabled later using `#pragma D pop`.

Unknown storage classes are ignored.

The pragma starts with identifier `D` to avoid conflicts.
  • Loading branch information
tim-dlang committed Sep 10, 2024
1 parent 541087f commit 9de583c
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 0 deletions.
25 changes: 25 additions & 0 deletions changelog/dmd.importc-pragma-stc.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
A pragma for ImportC allows to set `nothrow`, `@nogc` or `pure`

The following new pragma for ImportC allows to set default storage
classes for function declarations:
```
#pragma D push([storage classes...])
```
The storage classes `nothrow`, `nogc` and `pure` are supported.
Enabling a default storage class affects all function declarations
after the pragma until it is disabled with another pragma.
Declarations in includes are also affected. The following example
enables `@nogc` and `nothrow` for a library:

```
#pragma D push(nogc, nothrow)
#include <somelibrary.h>
```

The changed storage classes are pushed on a stack. The last change can
be undone with the following pragma:
```
#pragma D pop
```
This can also disable multiple default storage classes at the same time,
if they were enabled with a single `#pragma D push` directive.
102 changes: 102 additions & 0 deletions compiler/src/dmd/cparse.d
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ final class CParser(AST) : Parser!AST
// #pragma pack stack
Array!Identifier* records; // identifers (or null)
Array!structalign_t* packs; // parallel alignment values

STC defaultStorageClasses;
Array!STC* defaultStorageClassesStack;
}

/* C cannot be parsed without determining if an identifier is a type or a variable.
Expand Down Expand Up @@ -3019,6 +3022,7 @@ final class CParser(AST) : Parser!AST
StorageClass stc = specifier._nothrow ? STC.nothrow_ : 0;
if (specifier._pure)
stc |= STC.pure_;
stc |= defaultStorageClasses;
AST.Type tf = new AST.TypeFunction(parameterList, t, lkg, stc);
//tf = tf.addSTC(storageClass); // TODO
insertTx(ts, tf, t); // ts -> ... -> tf -> t
Expand Down Expand Up @@ -5670,6 +5674,8 @@ final class CParser(AST) : Parser!AST
scan(&n);
if (n.value == TOK.identifier && n.ident == Id.pack)
return pragmaPack(loc, true);
if (n.value == TOK.identifier && n.ident == Id.D)
return pragmaD(loc);
if (n.value != TOK.endOfLine)
skipToNextLine();
}
Expand Down Expand Up @@ -5877,6 +5883,102 @@ final class CParser(AST) : Parser!AST
skipToNextLine();
}

/*********
* # pragma D ...
* Sets default storage classes
* Params:
* startloc = location to use for error messages
*/
private void pragmaD(const ref Loc startloc)
{
const loc = startloc;

if (!defaultStorageClassesStack)
{
defaultStorageClassesStack = new Array!STC;
}

Token n;
Lexer.scan(&n);
if (n.value == TOK.endOfLine)
return;

/* # pragma D push (...)
*/
if (n.value == TOK.identifier && n.ident == Id.push)
{
Lexer.scan(&n);
if (n.value != TOK.leftParenthesis)
{
error(loc, "left parenthesis expected to follow `#pragma D push`");
if (n.value != TOK.endOfLine)
skipToNextLine();
return;
}

while (1)
{
Lexer.scan(&n);
if (n.value == TOK.endOfLine)
{
error(loc, "right parenthesis expected to close `#pragma D push(`");
break;
}

if (n.value == TOK.rightParenthesis)
break;

if (n.value == TOK.identifier)
{
if (n.ident == Id._nothrow)
defaultStorageClasses |= STC.nothrow_;
else if (n.ident == Id.nogc)
defaultStorageClasses |= STC.nogc;
else if (n.ident == Id._pure)
defaultStorageClasses |= STC.pure_;
// Ignore unknown identifiers
}
else
{
error(loc, "unrecognized `#pragma D push(%s)`", n.toChars());
break;
}

Lexer.scan(&n);

if (n.value == TOK.rightParenthesis)
break;

if (n.value != TOK.comma)
{
error(loc, "unrecognized `#pragma D push(%s)`", n.toChars());
break;
}
}

this.defaultStorageClassesStack.push(defaultStorageClasses);
}
/* # pragma D pop
*/
else if (n.value == TOK.identifier && n.ident == Id.pop)
{
scan(&n);
size_t len = this.defaultStorageClassesStack.length;

if (len)
{
this.defaultStorageClassesStack.setDim(len - 1);
if (len == 1) // stack is now empty
defaultStorageClasses = STC.init;
else
defaultStorageClasses = (*this.defaultStorageClassesStack)[len - 2];
}
}

if (n.value != TOK.endOfLine)
skipToNextLine();
}

//}

/******************************************************************************/
Expand Down
35 changes: 35 additions & 0 deletions compiler/test/compilable/imports/imp23812.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

void funcDefault(void);

#pragma D push(nothrow)
void funcNothrow(void);
#pragma D pop

#pragma D push(nogc)
void funcNogc(void);
#pragma D pop

#pragma D push(pure)
void funcPure(void);
#pragma D pop

#pragma D push(nothrow, nogc)
void funcNothrowNogc(void);
#pragma D pop

void funcDefault2(void);

#pragma D push(nothrow)
#pragma D push(nogc)
void funcNothrowNogc2(void);
#pragma D pop
void funcNothrow2(void);
#pragma D pop

#pragma D push(nothrow)
void funcWithCallback(void (*f)(void));
struct Callbacks
{
void (*f)(void);
};
#pragma D pop
75 changes: 75 additions & 0 deletions compiler/test/compilable/test23812.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// EXTRA_FILES: imports/imp23812.c

import imports.imp23812;

void callDefault()
{
funcDefault();
funcDefault2();
}

static assert(!__traits(compiles, () nothrow { funcDefault(); } ));
static assert(!__traits(compiles, () @nogc { funcDefault(); } ));
static assert(!__traits(compiles, () pure { funcDefault(); } ));

static assert(!__traits(compiles, () nothrow { funcDefault2(); } ));
static assert(!__traits(compiles, () @nogc { funcDefault2(); } ));
static assert(!__traits(compiles, () pure { funcDefault2(); } ));

void callNothrow() nothrow
{
funcNothrow();
funcNothrow2();
}

static assert(!__traits(compiles, () @nogc { funcNothrow(); } ));
static assert(!__traits(compiles, () pure { funcNothrow(); } ));

static assert(!__traits(compiles, () @nogc { funcNothrow2(); } ));
static assert(!__traits(compiles, () pure { funcNothrow2(); } ));

void callNogc() @nogc
{
funcNogc();
}

static assert(!__traits(compiles, () nothrow { funcNogc(); } ));
static assert(!__traits(compiles, () pure { funcNogc(); } ));

void callPure() pure
{
funcPure();
}

static assert(!__traits(compiles, () nothrow { funcPure(); } ));
static assert(!__traits(compiles, () @nogc { funcPure(); } ));

void callNothrowNogc() nothrow @nogc
{
funcNothrowNogc();
funcNothrowNogc2();
}

static assert(!__traits(compiles, () pure { funcNothrowNogc(); } ));

static assert(!__traits(compiles, () pure { funcNothrowNogc2(); } ));

extern(C) void callbackDefault()
{
}

extern(C) void callbackNothrow() nothrow
{
}

void callFuncWithCallback() nothrow
{
funcWithCallback(&callbackNothrow);

Callbacks callbacks;
callbacks.f = &callbackNothrow;
}

static assert(!__traits(compiles, () { funcWithCallback(&callbackDefault); } ));

static assert(!__traits(compiles, () { Callbacks callbacks; callbacks.f = &callbackDefault; } ));

0 comments on commit 9de583c

Please sign in to comment.