From cc9b0df32f8299f98ecb189fd994e28606d846fe Mon Sep 17 00:00:00 2001 From: Tim Schendekehl Date: Sat, 14 Sep 2024 19:58:00 +0200 Subject: [PATCH] Fix bugzilla 23812 - ImportC: allow adding function attributes to imported C functions (#16820) 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. --- changelog/dmd.importc-pragma-stc.dd | 26 ++++ compiler/src/dmd/cparse.d | 125 ++++++++++++++++++++ compiler/test/compilable/imports/imp23812.c | 35 ++++++ compiler/test/compilable/test23812.d | 75 ++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 changelog/dmd.importc-pragma-stc.dd create mode 100644 compiler/test/compilable/imports/imp23812.c create mode 100644 compiler/test/compilable/test23812.d diff --git a/changelog/dmd.importc-pragma-stc.dd b/changelog/dmd.importc-pragma-stc.dd new file mode 100644 index 0000000000..37984ecea7 --- /dev/null +++ b/changelog/dmd.importc-pragma-stc.dd @@ -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 +``` + +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. diff --git a/compiler/src/dmd/cparse.d b/compiler/src/dmd/cparse.d index ebeb07834b..af370cc6a2 100644 --- a/compiler/src/dmd/cparse.d +++ b/compiler/src/dmd/cparse.d @@ -47,6 +47,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. @@ -2979,6 +2982,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 @@ -5615,6 +5619,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(); } @@ -5822,6 +5828,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; + } + + void closingParen() + { + if (n.value != TOK.rightParenthesis) + { + error(loc, "right parenthesis expected to close `#pragma attribute(`"); + } + 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; + } + + while (1) + { + Lexer.scan(&n); + if (n.value == TOK.endOfLine) + { + error(loc, "right parenthesis expected to close `#pragma attribute(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 attribute(push, %s)`", n.toChars()); + break; + } + + Lexer.scan(&n); + + if (n.value == TOK.rightParenthesis) + break; + + if (n.value != TOK.comma) + { + error(loc, "unrecognized `#pragma attribute(push, %s)`", n.toChars()); + break; + } + } + + 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(); + } + //} /******************************************************************************/ diff --git a/compiler/test/compilable/imports/imp23812.c b/compiler/test/compilable/imports/imp23812.c new file mode 100644 index 0000000000..26a600a776 --- /dev/null +++ b/compiler/test/compilable/imports/imp23812.c @@ -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) diff --git a/compiler/test/compilable/test23812.d b/compiler/test/compilable/test23812.d new file mode 100644 index 0000000000..502a9d0a2a --- /dev/null +++ b/compiler/test/compilable/test23812.d @@ -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; } ));