Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImportC: Add a pragma for ignoring function declarations and definitions with specific identifiers. #16464

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions changelog/dmd.importc-pragma-importc-ignore.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
ImportC acquires `#pragma importc_ignore`

ImportC now has `#pragma importc_ignore`, which is used to ignore kinds of
declarations and definitions with specific identifiers.

This is for situations where it is desirable for ImportC to look past
a C declaration/definition of a symbol, and instead use a declaration/definition
from an `__import`ed D module.

---

`#pragma importc_ignore` has one form:
1. `#pragma importc_ignore(+/-<category>... : <identifier>,...)`

That is, a sequence of plus-or-minus-prefixed categories, followed by a colon,
and then a comma-separated list of identifiers.

The categories recognized by `#pragma importc_ignore` are as follows:
- `function_decl`: which ignores function declarations, e.g. `void foo(int);`.
- `function_def`: which ignores function definitions, e.g. `void foo(int x) {}`.

When a category is prefixed with a plus (`+`), declarations/definitions of that category
will begin to be ignored if their identifier is included in the list of identifiers supplied to
`#pragma importc_ignore`.
When a category is prefixed with a minus (`-`), declarations/definitions of that category
will no longer be ignored if their identifier is included in the list of identifiers supplied to
`#pragma importc_ignore`.

For example:
``` c
// We start ignoring function-declarations and function-definitions of `foo` and `bar`.
#pragma importc_ignore(+function_decl +function_def : foo, bar)
// This declaration of `foo` is ignored.
void foo(int);
// As is this definition of `foo`.
void foo(int x)
{}

// We stop ignoring function-definitions of `bar`.
#pragma importc_ignore(-function_def : bar)
// This declaration of `bar` is ignored.
void bar(int);
// This definition of `bar` is not ignored.
float bar(float x, float y)
{
return x * y;
}
```
237 changes: 223 additions & 14 deletions compiler/src/dmd/cparse.d
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
// #pragma pack stack
Array!Identifier* records; // identifers (or null)
Array!structalign_t* packs; // parallel alignment values

// state for #pragma importc_ignore
Ignored ignored;
}

/* C cannot be parsed without determining if an identifier is a type or a variable.
Expand Down Expand Up @@ -1922,15 +1925,18 @@
auto s = cparseFunctionDefinition(id, dt.isTypeFunction(), specifier);
typedefTab.setDim(typedefTabLengthSave);
symbols = symbolsSave;
if (specifier.mod & MOD.x__stdcall)
if (!(cast(const(void)*) id in ignored.functionDefinitions))
{
// If this function is __stdcall, wrap it in a LinkDeclaration so that
// it's extern(Windows) when imported in D.
auto decls = new AST.Dsymbols(1);
(*decls)[0] = s;
s = new AST.LinkDeclaration(s.loc, LINK.windows, decls);
if (specifier.mod & MOD.x__stdcall)
{
// If this function is __stdcall, wrap it in a LinkDeclaration so that
// it's extern(Windows) when imported in D.
auto decls = new AST.Dsymbols(1);
(*decls)[0] = s;
s = new AST.LinkDeclaration(s.loc, LINK.windows, decls);
}
symbols.push(s);
}
symbols.push(s);
return;
}
AST.Dsymbol s = null;
Expand Down Expand Up @@ -2035,11 +2041,14 @@
error("no initializer for function declaration");
if (specifier.scw & SCW.x_Thread_local)
error("functions cannot be `_Thread_local`"); // C11 6.7.1-4
StorageClass stc = specifiersToSTC(level, specifier);
stc &= ~STC.gshared; // no gshared functions
auto fd = new AST.FuncDeclaration(token.loc, Loc.initial, id, stc, dt, specifier.noreturn);
specifiersToFuncDeclaration(fd, specifier);
s = fd;
if (!(cast(const(void)*) id in ignored.functionDeclarations))
{
StorageClass stc = specifiersToSTC(level, specifier);
stc &= ~STC.gshared; // no gshared functions
auto fd = new AST.FuncDeclaration(token.loc, Loc.initial, id, stc, dt, specifier.noreturn);
specifiersToFuncDeclaration(fd, specifier);
s = fd;
}
}
else
{
Expand Down Expand Up @@ -5599,6 +5608,8 @@
{
if (token.ident == Id.pack)
pragmaPack(startloc, false);
else if (token.ident == Id.importc_ignore)
pragmaImportCIgnore(startloc, false);

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5612

Added line #L5612 was not covered by tests
else
{
nextToken();
Expand Down Expand Up @@ -5635,8 +5646,13 @@
{
Token n;
scan(&n);
if (n.value == TOK.identifier && n.ident == Id.pack)
return pragmaPack(loc, true);
if (n.value == TOK.identifier)
{
if (n.ident == Id.pack)
return pragmaPack(loc, true);
else if (n.ident == Id.importc_ignore)
return pragmaImportCIgnore(loc, true);
}
if (n.value != TOK.endOfLine)
skipToNextLine();
}
Expand Down Expand Up @@ -5844,6 +5860,199 @@
skipToNextLine();
}

private struct Ignored
{
// A hash-table with zero-sized values, and `Identifier`s casted to `const(void)*` for keys.
// We need only to test for the presence of an identifier, hence the zero-sized values.
// `Identifier` is an `extern(C++)` class, which an associative-array
// can't handle as keys, hence the cast to a pointer.
alias IdentifierSet = ubyte[0][const(void)*];

// A bitwise combination of categories of C constructs to ignore.
enum Category : uint
{
none = 0,
// These must be in the same order as the members of the anonymous struct below.
functionDeclarations = 1 << 0,
functionDefinitions = 1 << 1
}

union
{
IdentifierSet[2] byCategory;
struct
{
// These must be in the same order as the `Category` enum's members.
IdentifierSet functionDeclarations;
IdentifierSet functionDefinitions;
}
}

// Calls `action` for each category set in `categories`, supplying
// the corresponding `IdentifierSet` for that category.
void eachCategory(Action)(Category categories, scope Action action)
{
import core.bitop : bsr;

while (categories != 0)
{
const index = bsr(categories);
categories &= ~(1 << index);

action(byCategory[index]);
}
}

static bool categoryFromIdentifier(const Identifier id, out Category category)
{
if (id == Id.function_decl)
{
category = Ignored.Category.functionDeclarations;
return true;
}
if (id == Id.function_def)
{
category = Ignored.Category.functionDefinitions;
return true;
}

return false;

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5919

Added line #L5919 was not covered by tests
}
}

/*********
* `# pragma importc_ignore`
* An ImportC-specific pragma for specifying declarations and definitions that are to be ignored
* Scanner is on the `importc_ignore`
* Params:
* startloc = location to use for error messages
* useScan = use scan() to retrieve next token, instead of nextToken()
*/
private void pragmaImportCIgnore(const ref Loc startloc, bool useScan)
{
const loc = startloc;
Token n;

/* Pull tokens from scan() or nextToken()
*/
void scan(Token* t)
{
if (useScan)
{
Lexer.scan(t);
}
else
{
nextToken();
*t = token;

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5946-L5947

Added lines #L5946 - L5947 were not covered by tests
}
}

void unknownTokenFailure()
{
if (n.value != TOK.endOfLine)
skipToNextLine();

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5953-L5954

Added lines #L5953 - L5954 were not covered by tests
}

/* # pragma importc_ignore ( ...
*/
scan(&n);
if (n.value != TOK.leftParenthesis)
{
error(loc, "left parenthesis expected to follow `#pragma importc_ignore` not `%s`", n.toChars());
return unknownTokenFailure();

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5962-L5963

Added lines #L5962 - L5963 were not covered by tests
}

Ignored.Category add;
Ignored.Category remove;

/* # pragma importc_ignore ( +/-<ignore-category>... :
*/
for (;;)
{
scan(&n);
if (n.value == TOK.colon)
break;

if (n.value != TOK.add && n.value != TOK.min)
{
error(loc, "`+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `%s`", n.toChars());
return unknownTokenFailure();

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5979-L5980

Added lines #L5979 - L5980 were not covered by tests
}

const prefix = n.value == TOK.add ? '+' : '-';

scan(&n);
if (n.value != TOK.identifier)
{
error(loc, "identifier expected to follow `#pragma importc_ignore(%c` not `%s`", prefix, n.toChars());
return unknownTokenFailure();

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5988-L5989

Added lines #L5988 - L5989 were not covered by tests
}

const id = n.ident;
Ignored.Category category;

if (!Ignored.categoryFromIdentifier(id, category))
{
error(loc, "`function_decl` or `function_def` expected to follow `#pragma importc_ignore(%c` not `%s`",

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L5997

Added line #L5997 was not covered by tests
prefix, id.toChars());
skipToNextLine();
return;

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

View check run for this annotation

Codecov / codecov/patch

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

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

(prefix == '+' ? add : remove) |= category;
(prefix == '+' ? remove : add) &= ~category;
}

/* # pragma importc_ignore ( +/-<ignore-category>... : <identifier>... )
*/
Array!Identifier identifiers;
for (;;)
{
scan(&n);

if (n.value == TOK.identifier)
{
auto id = n.ident;
identifiers.push(id);

scan(&n);
if (n.value == TOK.rightParenthesis)
break;
else if (n.value != TOK.comma)
{
error(loc, "comma or right parenthesis expected following `#pragma importc_ignore(... : %s` not `%s`",

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L6024

Added line #L6024 was not covered by tests
id.toChars(), n.toChars());
return unknownTokenFailure();

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L6026

Added line #L6026 was not covered by tests
}
}
else if (n.value == TOK.rightParenthesis)
break;
else
{
error(loc, "identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `%s`",

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L6033

Added line #L6033 was not covered by tests
n.toChars());
return unknownTokenFailure();

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

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/cparse.d#L6035

Added line #L6035 was not covered by tests
}
}

skipToNextLine();

assert((add & remove) == 0, "There should be no overlap between add and remove.");

ignored.eachCategory(remove, (ref Ignored.IdentifierSet set)
{
foreach (id; identifiers)
set.remove(cast(const(void)*) id);
});

ignored.eachCategory(add, (ref Ignored.IdentifierSet set)
{
foreach (id; identifiers)
set[cast(const(void)*) id] = [];
});
}

//}

/******************************************************************************/
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dmd/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -8793,6 +8793,9 @@ struct Id final
static Identifier* show;
static Identifier* push;
static Identifier* pop;
static Identifier* importc_ignore;
static Identifier* function_decl;
static Identifier* function_def;
static Identifier* _pure;
static Identifier* define;
static Identifier* undef;
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dmd/id.d
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,9 @@ immutable Msgtable[] msgtable =
{ "show" },
{ "push" },
{ "pop" },
{ "importc_ignore" },
{ "function_decl" },
{ "function_def" },
{ "_pure", "pure" },
{ "define" },
{ "undef" },
Expand Down
41 changes: 41 additions & 0 deletions compiler/test/fail_compilation/importc_pragma_ignore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* TEST_OUTPUT:
---
fail_compilation/importc_pragma_ignore.c(23): Error: left parenthesis expected to follow `#pragma importc_ignore` not `\n`
fail_compilation/importc_pragma_ignore.c(24): Error: `+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `\n`
fail_compilation/importc_pragma_ignore.c(25): Error: `+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `foo`
fail_compilation/importc_pragma_ignore.c(26): Error: `+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `function_decl`
fail_compilation/importc_pragma_ignore.c(27): Error: identifier expected to follow `#pragma importc_ignore(+` not `\n`
fail_compilation/importc_pragma_ignore.c(28): Error: identifier expected to follow `#pragma importc_ignore(-` not `\n`
fail_compilation/importc_pragma_ignore.c(29): Error: identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `\n`
fail_compilation/importc_pragma_ignore.c(30): Error: identifier expected to follow `#pragma importc_ignore(+` not `123`
fail_compilation/importc_pragma_ignore.c(31): Error: `function_decl` or `function_def` expected to follow `#pragma importc_ignore(+` not `not_a_category`
fail_compilation/importc_pragma_ignore.c(32): Error: `+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `\n`
fail_compilation/importc_pragma_ignore.c(33): Error: `+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `function_def`
fail_compilation/importc_pragma_ignore.c(34): Error: `+`, `-`, or a colon is expected to follow `#pragma importc_ignore(` not `\n`
fail_compilation/importc_pragma_ignore.c(35): Error: identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `123`
fail_compilation/importc_pragma_ignore.c(36): Error: comma or right parenthesis expected following `#pragma importc_ignore(... : foo` not `\n`
fail_compilation/importc_pragma_ignore.c(37): Error: identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `\n`
fail_compilation/importc_pragma_ignore.c(38): Error: identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `,`
fail_compilation/importc_pragma_ignore.c(39): Error: identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `\n`
---
*/

#pragma importc_ignore
#pragma importc_ignore(
#pragma importc_ignore(foo
#pragma importc_ignore(function_decl
#pragma importc_ignore(+
#pragma importc_ignore(-
#pragma importc_ignore(:
#pragma importc_ignore(+123
#pragma importc_ignore(+not_a_category
#pragma importc_ignore(+function_decl
#pragma importc_ignore(+function_decl function_def
#pragma importc_ignore(+function_decl -function_def
#pragma importc_ignore(+function_decl -function_def : 123
#pragma importc_ignore(+function_decl -function_def : foo
#pragma importc_ignore(+function_decl -function_def : foo,
#pragma importc_ignore(+function_decl -function_def : ,
#pragma importc_ignore(+function_decl -function_def :
void foo(void)
{}