diff --git a/changelog/dmd.importc-pragma-importc-ignore.dd b/changelog/dmd.importc-pragma-importc-ignore.dd new file mode 100644 index 000000000000..65ce74be82d2 --- /dev/null +++ b/changelog/dmd.importc-pragma-importc-ignore.dd @@ -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(+/-... : ,...)` + +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; +} +``` diff --git a/compiler/src/dmd/cparse.d b/compiler/src/dmd/cparse.d index 7fbcd6d9bc1f..52d251436113 100644 --- a/compiler/src/dmd/cparse.d +++ b/compiler/src/dmd/cparse.d @@ -44,6 +44,9 @@ final class CParser(AST) : Parser!AST // #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. @@ -1922,15 +1925,18 @@ final class CParser(AST) : Parser!AST 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; @@ -2035,11 +2041,14 @@ final class CParser(AST) : Parser!AST 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 { @@ -5599,6 +5608,8 @@ final class CParser(AST) : Parser!AST { if (token.ident == Id.pack) pragmaPack(startloc, false); + else if (token.ident == Id.importc_ignore) + pragmaImportCIgnore(startloc, false); else { nextToken(); @@ -5635,8 +5646,13 @@ final class CParser(AST) : Parser!AST { 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(); } @@ -5844,6 +5860,199 @@ final class CParser(AST) : Parser!AST 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; + } + } + + /********* + * `# 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; + } + } + + void unknownTokenFailure() + { + if (n.value != TOK.endOfLine) + skipToNextLine(); + } + + /* # 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(); + } + + Ignored.Category add; + Ignored.Category remove; + + /* # pragma importc_ignore ( +/-... : + */ + 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(); + } + + 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(); + } + + 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`", + prefix, id.toChars()); + skipToNextLine(); + return; + } + + (prefix == '+' ? add : remove) |= category; + (prefix == '+' ? remove : add) &= ~category; + } + + /* # pragma importc_ignore ( +/-... : ... ) + */ + 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`", + id.toChars(), n.toChars()); + return unknownTokenFailure(); + } + } + else if (n.value == TOK.rightParenthesis) + break; + else + { + error(loc, "identifier or right parenthesis expected following `#pragma importc_ignore(... :` not `%s`", + n.toChars()); + return unknownTokenFailure(); + } + } + + 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] = []; + }); + } + //} /******************************************************************************/ diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index a208add26a6f..3853142cce66 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -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; diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index 6dbc60b020cc..98d7e7a7ccee 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -567,6 +567,9 @@ immutable Msgtable[] msgtable = { "show" }, { "push" }, { "pop" }, + { "importc_ignore" }, + { "function_decl" }, + { "function_def" }, { "_pure", "pure" }, { "define" }, { "undef" }, diff --git a/compiler/test/fail_compilation/importc_pragma_ignore.c b/compiler/test/fail_compilation/importc_pragma_ignore.c new file mode 100644 index 000000000000..e1105e1ada9f --- /dev/null +++ b/compiler/test/fail_compilation/importc_pragma_ignore.c @@ -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) +{} diff --git a/compiler/test/runnable/importc_pragma_ignore_c.c b/compiler/test/runnable/importc_pragma_ignore_c.c new file mode 100644 index 000000000000..5ef2929be74b --- /dev/null +++ b/compiler/test/runnable/importc_pragma_ignore_c.c @@ -0,0 +1,57 @@ +// EXTRA_SOURCES: imports/importc_pragma_ignore.d + +// For `size_t`. +#include + +__import importc_pragma_ignore; + +// A trailing comma is allowed for the identifiers. +#pragma importc_ignore(+function_decl : foo, memset,) + +// The ignoring of `foo` and `memset` is still in effect after this. +#pragma importc_ignore(+function_decl +function_def : bar) + +// This compiler should ignore this function declaration, and so the linker +// shouldn't complain about `foo` being undefined when it's called in `useFoo`, +// as the definition of `foo` from `importc_pragma_ignore` should be used. +int foo(void); + +int useFoo() +{ + return foo(); +} + +typedef struct HasFoo +{ + int foo; +} +Foo; + +void *memset(void *); +#pragma importc_ignore(-function_decl : memset) +// The previous declaration of `memset` was ignored, so this redefinition +// with a different type should succeed. +void *memset(void *, int, size_t); + +int bar(int x) +{ + return 0; +} + +int main(void) +{ + __check(useFoo() == 1); + + // The ignoring shouldn't interfere with non-functions. + HasFoo hasFoo = {.foo = 7}; + ++hasFoo.foo; + __check(hasFoo.foo == 8); + + __check(bar(2) == 4); + + unsigned char value = 0; + memset(&value, 111, 1); + __check(value == 111); + + return 0; +} diff --git a/compiler/test/runnable/imports/importc_pragma_ignore.d b/compiler/test/runnable/imports/importc_pragma_ignore.d new file mode 100644 index 000000000000..644632560240 --- /dev/null +++ b/compiler/test/runnable/imports/importc_pragma_ignore.d @@ -0,0 +1,24 @@ +module importc_pragma_ignore; + +int foo() +{ + return 1; +} + +alias bar = _bar; + +int _bar(int x) +{ + return x * 2; +} + +void* memset(void* destination, int value, size_t count) +{ + foreach (index; 0 .. count) + { + // Zero the memory so we can tell the difference between this and the actual memset. + (cast(ubyte*) destination)[index] = 0; + } + + return destination; +}