Skip to content

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

Open
just-harry wants to merge 6 commits into
dlang:masterfrom
just-harry:importc-pragma-ignore
Open

ImportC: Add a pragma for ignoring function declarations and definitions with specific identifiers.#16464
just-harry wants to merge 6 commits into
dlang:masterfrom
just-harry:importc-pragma-ignore

Conversation

@just-harry

@just-harry just-harry commented May 9, 2024

Copy link
Copy Markdown
Contributor

Spec PR: dlang/dlang.org#3828


This PR adds an ImportC-specific #pragma importc_ignore.
This pragma 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 __imported D module.

A real world use-case for this would be handling MSVC compiler intrinsics when MSVC's #pragma intrinsic is used. (More on that later.)

I can also think of situations where it could be very convenient to be able to ignore a function-definition: such as to replace a C function's implementations with an __imported D function, if the C function happens to use compiler-extensions not supported by ImportC; or, even just to fix bugs, or add logging, or whatever, without having to bother with a whole fork.


#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:

// 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;
}

The rationale behind allowing declarations and definitions to be ignored separately is to make it easier to account for situations where a C library may conditionally declare or define a function based on preprocessor #defines.
E.g. the same function is declared when targeting one platform but defined when targeting a different platform.


Back to a real world use-case for this—MSVC has #pragma intrinsic, which causes the compiler to treat calls to identifiers supplied to #pragma intrinsic as calls to intrinsics, and not calls to functions.

For example, in the standard Windows headers _ReadWriteBarrier, and hundreds of other intrinsics, are defined as so:

void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)

The presence of #pragma intrinsic(_ReadWriteBarrier) means that no actual calls to _ReadWriteBarrier are emitted, despite the body-less declaration of _ReadWriteBarrier.

Whereas, in ImportC, because #pragma intrinsic has no effect, the body-less declaration of _ReadWriteBarrier will cause actual calls to _ReadWriteBarrier to be emitted—and worse, the body-less declaration of _ReadWriteBarrier will prevent the compiler from using an __imported definition of _ReadWriteBarrier.

This worsens runtime performance, as D implementations of #pragma intrinsic intrinsics cannot be inlined, and increases compile-times as codegen for such D implementations must be performed unconditionally.

However, if we were to implement and use #pragma importc_ignore, then D implementations of #pragma intrinsic intrinsics could be inlined, and can be templates: improving runtime performance, and reducing compile-times as codegen need be performed only for the intrinsics that are actually used.

This would be achieved with a simple:

#pragma importc_ignore(+function_decl : _ReadWriteBarrier)
void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)

As a side-note, this is my third iteration of this kind of pragma.

The first was to reimplement MSVC's #pragma intrinsic, and #pragma function, by means of forwarding #pragma intrinsic functions to a D module specified by #pragma intrinsicsModule.

The second was to refine that to just one #pragma forwardTo, which would rewrite function calls to a target, like so: #pragma forwardTo(set c_intrinsics : foo), which would rewrite foo() to c_intrinsics.foo().
But, the implementation for that ends up being more complex than the implementation for importc_ignore, and importc_ignore achieves more-or-less the same effect.
Outside of parsing the pragma and keeping track of what's to be ignored, the implementation for this is only two if-statements.

I did consider also allowing declarations/definitions of structs, unions, and enums, and typedefs to be ignored, but that then raises the question of whether they're ignored in all scopes or only the global scope. Whereas, functions have only global scope in C; so to keep things simple: functions only.

Comment thread compiler/src/dmd/frontend.h Outdated
Comment on lines +6 to +8
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to elaborate a bit on why this is useful. Does this aid in porting, does it resolve duplicate definition errors? ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aye, I've amended the changelog entry; better now?

@thewilsonator thewilsonator left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, I presume we track these pragmas in the spec somewhere, updating/adding that would be good.

Comment thread changelog/dmd.importc-pragma-importc-ignore.dd Outdated
@just-harry just-harry force-pushed the importc-pragma-ignore branch from 729dcd4 to f52b62b Compare June 27, 2026 17:15
@just-harry

Copy link
Copy Markdown
Contributor Author

also, I presume we track these pragmas in the spec somewhere, updating/adding that would be good.

Indeed, hence dlang/dlang.org#3828; though I see the spec's DDoc has been moved into to this repo since then, so I've closed that PR and moved the changes into this PR

@just-harry

Copy link
Copy Markdown
Contributor Author

I've implemented the syntax changes suggested by Walter, although I've opted to use the term consider instead of recognize, as I think it better fits as a complement to ignore.

Likewise, to avoid double negation in the code, Ignored ignored; has been changed to ConstructConsideration constructConsideration;, and thus:

if (!(cast(const(void)*) id in ignored.functionDeclarations))

becomes

if (constructConsideration.considering(id.name, ConstructCategory.functionDeclarations))

The spec and the changelog entry have been amended accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants