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 attribute(pop)`.

Unknown storage classes are ignored.
  • Loading branch information
tim-dlang committed Sep 11, 2024
1 parent 541087f commit d956dc0
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
26 changes: 26 additions & 0 deletions changelog/dmd.importc-pragma-stc.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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 attribute(push, [storage classes...])
```
The storage classes `nothrow`, `nogc` and `pure` are supported.
Unrecognized attributes are ignored.
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 attribute(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 attribute(pop)
```
This can also disable multiple default storage classes at the same time,
if they were enabled with a single `#pragma attribute(push, ...)` directive.
125 changes: 125 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.attribute)
return pragmaAttribute(loc);
if (n.value != TOK.endOfLine)
skipToNextLine();
}
Expand Down Expand Up @@ -5877,6 +5883,125 @@ final class CParser(AST) : Parser!AST
skipToNextLine();
}

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

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

Token n;
Lexer.scan(&n);
if (n.value != TOK.leftParenthesis)
{
error(loc, "left parenthesis expected to follow `#pragma attribute`");
if (n.value != TOK.endOfLine)
skipToNextLine();
return;

Check warning on line 5908 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5905-L5908

Added lines #L5905 - L5908 were not covered by tests
}

void closingParen()
{
if (n.value != TOK.rightParenthesis)
{
error(loc, "right parenthesis expected to close `#pragma attribute(`");

Check warning on line 5915 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5915

Added line #L5915 was not covered by tests
}
if (n.value != TOK.endOfLine)
skipToNextLine();
}

Lexer.scan(&n);

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

Check warning on line 5933 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5930-L5933

Added lines #L5930 - L5933 were not covered by tests
}

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

Check warning on line 5942 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5941-L5942

Added lines #L5941 - L5942 were not covered by tests
}

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

Check warning on line 5946 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5946

Added line #L5946 was not covered by tests

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 attribute(push, %s)`", n.toChars());
break;

Check warning on line 5961 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5960-L5961

Added lines #L5960 - L5961 were not covered by tests
}

Lexer.scan(&n);

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

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

Check warning on line 5972 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5971-L5972

Added lines #L5971 - L5972 were not covered by tests
}
}

this.defaultStorageClassesStack.push(defaultStorageClasses);

return closingParen();
}

/* # pragma attribute(pop)
*/
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];
}

return closingParen();
}

error(loc, "unrecognized `#pragma attribute(%s)`", n.toChars());
if (n.value != TOK.endOfLine)
skipToNextLine();

Check warning on line 6002 in compiler/src/dmd/cparse.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L6000-L6002

Added lines #L6000 - L6002 were not covered by tests
}

//}

/******************************************************************************/
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 attribute(push, nothrow)
void funcNothrow(void);
#pragma attribute(pop)

#pragma attribute(push, nogc)
void funcNogc(void);
#pragma attribute(pop)

#pragma attribute(push, pure)
void funcPure(void);
#pragma attribute(pop)

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

void funcDefault2(void);

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

#pragma attribute(push, nothrow)
void funcWithCallback(void (*f)(void));
struct Callbacks
{
void (*f)(void);
};
#pragma attribute(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 d956dc0

Please sign in to comment.