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

Add @standalone attribute for module constructors #15537

Merged
merged 2 commits into from
Dec 22, 2023

Conversation

dkorpel
Copy link
Contributor

@dkorpel dkorpel commented Aug 15, 2023

No description provided.

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @dkorpel! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + dmd#15537"

@dkorpel dkorpel force-pushed the module-constructors-acyclic branch from 0407048 to 216f69e Compare August 15, 2023 19:22
@dkorpel dkorpel changed the title Add @standalone attribute Add @standalone attribute for module constructors Aug 15, 2023
/**
* Returns: true if the given expression is an enum from `core.attribute` named `id`
*/
bool isEnumAttribute(Expression e, Identifier id)
Copy link
Member

Choose a reason for hiding this comment

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

Might be cleaner to return a pointer, the other is functions in DMD do this so its a common pattern to keep going.

StaticDtorDeclarations ectorgates;
symbols sdtors;
symbols stests;

symbols ssharedctors;
symbols ssharedctors; // shared static constructors
symbols sisharedctors; // standalone shared static constructors
SharedStaticDtorDeclarations esharedctorgates;
Copy link
Member

Choose a reason for hiding this comment

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

Ugly naming convention. Unfortunate.

@maxhaton
Copy link
Member

Seems worth a try out.

@thewilsonator thewilsonator added the Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org label Aug 16, 2023
{
auto trust = sharedCtor.type.isTypeFunction().trust;
if (trust != TRUST.system && trust != TRUST.trusted)
error(e.loc, "a module constructor using `@%s` must be `@system` or `@trusted`", Id.udaStandalone.toChars());
Copy link

Choose a reason for hiding this comment

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

why not @standalone directly ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the name can't be out of sync with the attribute I guess. Really, I just mimicked how @mustuse did it

Copy link
Member

Choose a reason for hiding this comment

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

There will be more of these in future, can it be made into a reusable function?

Copy link

Choose a reason for hiding this comment

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

to be frank, this is not a hot path. It's just that it's useless to add N kilobytes of code while the static data is already there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can it be made into a reusable function

What part of the error would other attributes re-use?

Copy link
Member

Choose a reason for hiding this comment

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

That's up to your judgement - "I just mimicked" hinted at some repetition. If there is no overlap then don't, but it would be really good to make these types of logic declarative. I'll write a sketch hang on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The 'repetition' is also using @%s and Id.udaStandalone.toChars() instead of @standalone, that's too small of a thing to extract to a function.

sharedCtor.standalone = true;
}
else
.error(e.loc, "`@%s` can only be used on shared static constructors", Id.udaStandalone.toChars());
Copy link

Choose a reason for hiding this comment

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

ditto

@WalterBright
Copy link
Member

The @standalone attribute is only useful for static constructors. It is meaningless when applied to anything else. Shouldn't an attribute be something that applies to lots of things, instead of exactly one thing? Some other syntax should trigger this. Maybe in the spirit of pragma(crt_constructor) we could have pragma(acyclic_constructor) ?

@WalterBright
Copy link
Member

P.S. the number of pre-defined attributes is getting a little large, and we're running out of space in the integer mask for them!

@dkorpel
Copy link
Contributor Author

dkorpel commented Aug 18, 2023

Shouldn't an attribute be something that applies to lots of things, instead of exactly one thing?

I don't see why that's a rule. override only applies to class methods, @selector only applies to Objective-C methods.
Internally, a pragma is an attribute (it extends AttribDeclaration). The differences that matter are:

  • pragma's can be put inside the function body (PragmaStatement)
  • unknown pragma's are an error unless compiling with -ignore, which few people do
  • in contrast, core.attribute attributes can be made compatible with older compiler versions using static if (__VERSION__ < 2106) enum standalone; else import core.attribute : standalone;.

@dkorpel
Copy link
Contributor Author

dkorpel commented Aug 18, 2023

P.S. the number of pre-defined attributes is getting a little large, and we're running out of space in the integer mask for them!

Then you'd be happy to know that none of the attributes in core.attribute add a new bit mask :)

@ljmf00 ljmf00 added the Industry Applies to PRs pertaining to industry applications of D label Aug 18, 2023
@ljmf00
Copy link
Member

ljmf00 commented Aug 18, 2023

This is a very much appreciated feature to have. Weka struggles a lot with these, and we run our unittests with oncycle=print, which is not desirable. Most of it is fine, because they are, in fact, independent. This could also make the compiler avoid enforcing to reference the ModuleInfo, when its not needed.

The hacky solution "just create another module" is not a feasible thing for large codebases.

@maxhaton
Copy link
Member

Based on our codebases I think I'd prefer an attribute than a pragma.

@WalterBright
Copy link
Member

Pragma is already used for pragma(crt_constructor). Doesn't it seem weird to use a pragma for that unusual constructor, and an attribute for another unusual constructor? pragma(crt_constructor) is a pretty good signal that it is for something that is a bit outside of the language, which acyclic constructors certainly are.

@ljmf00
Copy link
Member

ljmf00 commented Aug 19, 2023

which acyclic constructors certainly are.

It shouldn't be. It's just defining how it should behave. We should have this since the beginning

@WalterBright
Copy link
Member

@ljmf00 The acyclic static constructor creates a correctness hole in the language. It should stand out with a different syntax, one like the crt_constructor which also creates a correctness hole. Using an attribute implies it is more central to the language.

@adamdruppe
Copy link
Contributor

Attributes are the correct solution here. Please don't force it to be a pragma, that'd make it much harder to use in stable codebases.

@MrcSnm
Copy link

MrcSnm commented Aug 22, 2023

Hello, this is a nice feature and all, but there is a fundamental problem:
This could and should be done without any programmer intervention, i.e: no attribute or pragmas.
Think about people trying to learn D, and then they didn't read the entire spec (99% chance of that), they stumble on that error, can't fix it unless they ask (most people just go away, they don't even ask).

D should be all about making it work more by writing less, it is a well suited language for a beginner to learn and putting this attribute instead of really solving the problem and making it just works is just a step backwards.

If you take a look into the forums, people already calls that as attribute soup, and creating a brand new attribute is the wrong direction, Razvan even recently announced the unused import removal tool, which should be similar to what this @standalone attribute does, but automatically.

IMO, this is just more unnecessary friction in the language, more cluttering in symbols (yes, @standalone is not that uncommon word anyway).

@adamdruppe
Copy link
Contributor

It'd be great if it was done automatically but that's not realistically going to happen. D is supposed to be a practical, real world language. This PR serves a practical, real world use case.

If the implementation were to ever automatically infer this, the attribute doesn't harm anything in any way while solving a real problem. Merge ASAP.

@dkorpel
Copy link
Contributor Author

dkorpel commented Aug 23, 2023

Using an attribute implies it is more central to the language.

It's a library attribute though (druntime/core/attribute), not a reserved language attribute. I could make it uglier by adding underscores, but historically, I don't think that refrained people from using __traits or __gshared.

@dkorpel
Copy link
Contributor Author

dkorpel commented Aug 23, 2023

D should be all about making it work more by writing less

I agree, and we considered this in the monthly meeting, but the complexity and fragility of inference of @standalone was not deemed worth it. Any better solution ideas are welcome though!

@dkorpel
Copy link
Contributor Author

dkorpel commented Aug 23, 2023

Merge ASAP

Debugging the failure will have to wait until after DConf. In the meantime, the bike shedding about the syntax may continue! 🙂

@ljmf00
Copy link
Member

ljmf00 commented Aug 27, 2023

@ljmf00 The acyclic static constructor creates a correctness hole in the language. It should stand out with a different syntax, one like the crt_constructor which also creates a correctness hole. Using an attribute implies it is more central to the language.

Sure but its the same argument as the @trusted attribute. It also open holes, but sometimes is needed, if we dont want to degrade performance in some specific cases, as we do assumptions.

@adamdruppe
Copy link
Contributor

This is still an important need for D, it slipped my mind for a month but I hit yet another case where it would be useful. What's the current situation?

@dkorpel
Copy link
Contributor Author

dkorpel commented Oct 30, 2023

I'll look into the test failure this week

@adamdruppe
Copy link
Contributor

ping again

@adamdruppe
Copy link
Contributor

importing the module is the correct behavior.

@RazvanN7
Copy link
Contributor

This needs formal approval from @WalterBright .

@WalterBright
Copy link
Member

@ryuukk 's comment about the syntax #15537 (comment) is persuasive. What is the compelling advantage to using the attribute, with its rather clumsy looking syntax and double underscore?

One of the objections to pragma's was resolved by @adamdruppe 's PR #15893

Doesn't:

pragma(standalone)

just look nice?

@dkorpel
Copy link
Contributor Author

dkorpel commented Dec 21, 2023

Whaaaa? All this time you've been arguing that @standalone looks too nice for an 'ugly wart' in the language, and therefore it should become a pragma. I thought renaming it to @__standalone would be a good compromise, but now it looks clumsy and you want it to look nice? I can rename it back.

@adamdruppe
Copy link
Contributor

adamdruppe commented Dec 21, 2023

I support multiple years of D compilers in the ecosystem. This means I can only use a new feature if either 1) it can be tested and enabled selectively in the code itself or 2) has been in the language for over 3 years, such that the oldest supported compiler has it.

the pragma objection stands until 2027. It is good it is going to be fixed some day, but I can't go breaking my client's code until then, so if this is a pragma, it is useless to me for years, whereas the attribute can be committed to library code right now, heck, before the feature is even merged.

@MrcSnm
Copy link

MrcSnm commented Dec 21, 2023

@WalterBright The main reason why pragma is not being considered is the difficulty of compiler feature identification.
That means, people prefer it as an attribute since it can be checked on compilation time. For example, the inexistence of a featured condition such as static if(compilerHasStandalone) makes it harder.

The pragma problem is that one would need to static if the compiler versions for trying to detect that feature (and still be erratic, since, for example, extern(Objective-C) is supported in DMD from version X, while it is not even implemented on LDC and GDC)

Even if this was done right now, it would take some years for it being considered in various codebases since most don't want to keep updating their compilers all the time.

@WalterBright
Copy link
Member

I have a mtg to go to, so will address all points later.

@WalterBright
Copy link
Member

WalterBright commented Dec 22, 2023

@dkorpel :

@__standalone is just ugly. Can I change my mind about that? Even worse, though, is the required import:

pragma(crt_constructor) void foo();
pragma(crt_destructor) void bar();

import core.attribute : __standalone;
@__standalone void xxx();

The more I look at it, the worse the inconsistency and aesthetics of xxx() look.

Another peculiar inconsistency of this is it being the only compiler recognized attribute that is not part of the grammar. I am hesitant to embark down that road.

@adamdruppe :

I didn't know you were committed to supporting 3 years worth of compilers, thanks for telling me. Even though your initiative to ignore unsupported pragmas has been merged, I understand it won't work with older compilers. But I do have a solution! Consider modules A and B which import each other. We want an acyclic constructor in A. This can be done with:

module C;
extern (C) void acyclic_constructor();
static this() { acyclic_constructor(); }
module A;
import C;
import B;
extern (C) void acyclic_constructor() { ... }
module B;
import A;
...

This should work on all versions of D.

@MrcSnm :

There is currently no __traits support for detecting things like constructors, pragma(crt_destructor), etc. If there is a need to detect such pragmas, another __trait could be added for the set of them.

@adamdruppe
Copy link
Contributor

You can't do those extern(C) import tricks in mixin templates, which is the primary use case I have for this, because you don't control which module the functions end up in. They exist in random user modules, not something I write, and it is impossible to split a single mixed in declaration across two modules.

@adamdruppe
Copy link
Contributor

There is currently no __traits support for detecting things like constructors, pragma(crt_destructor), etc. If there is a need to detect such pragmas, another __trait could be added for the set of them.

One of the beauties of using attributes is you don't need more special cases. The existing language supports all this stuff for them already - reflection, grouping, name conflict resolution, and more - the only magic thing about them is the compiler recognizes them. It is extremely beneficial to use this instead of adding more and more special cases for every little thing that comes up.

Another peculiar inconsistency of this is it being the only compiler recognized attribute that is not part of the grammar.

ldc and gdc have tons of compiler recognized attributes. Why would being part of the grammar be beneficial at all anyway?

I also don't think mustUse is in the grammar and even dmd supports that one.

@pbackus
Copy link
Contributor

pbackus commented Dec 22, 2023

Another peculiar inconsistency of this is it being the only compiler recognized attribute that is not part of the grammar.

@mustuse is not part of the grammar.

@WalterBright
Copy link
Member

I'll think about the template mixin case.

mustuse is indeed in dmd, though I wasn't involved with it.
https://dlang.org/spec/attribute.html#mustuse-attribute

If the __standalone attribute is ignored in older compilers, how do your mixin templates work around that?

@dkorpel
Copy link
Contributor Author

dkorpel commented Dec 22, 2023

though I wasn't involved with it.

You reviewed and accepted the DIP. Either way, core.attribute exists, is used by dmd, and works fine. There is no dangerous road to embark on.

@dkorpel
Copy link
Contributor Author

dkorpel commented Dec 22, 2023

The more I look at it, the worse the inconsistency and aesthetics of xxx() look.

I'll remove the underscores. The import is not ergonomic, but remember that this is a once-in-100KLOC feature, not something the user is supposed to reach to quickly (by your own account). As for consistency, @standalone is an attribute of static constructors, not regular functions like pragma(crt_constructor), so a more apt comparison is this:

import core.attribute : standalone;

shared static this()
{
    // normal sctor
}

@standalone @system 
shared static this()
{
    // standalone sctor
}

Which looks fine to me.

@dkorpel dkorpel changed the title Add @__standalone attribute for module constructors Add @standalone attribute for module constructors Dec 22, 2023
@adamdruppe
Copy link
Contributor

adamdruppe commented Dec 22, 2023

If the __standalone attribute is ignored in older compilers, how do your mixin templates work around that?

This is the status quo, so there's no regression. But the situation is iffy:

  1. In some cases, they simply don't notice the buggy behavior because the mixins I provide tend to be used in top-level modules that aren't mutually imported anyway. This works decently well for applications, but not really for libraries.

  2. In other cases, the module cycle detection in druntime must be turned off. e.g. for using my android bindings library: https://github.com/adamdruppe/d_android/blob/master/source/hacks.d#L8 (note that with the attribute, I can static if(__traits(hasMember, core.attribute, "standalone")) /* no hack */ else { /* keep old hack */ } to transparently improve the situation for all users - reflecting on availability of features lets libraries do things like this! BTW static if(__VERSION__ > 2010 is a possible substitute for this, but __VERSION__ does not correctly cover uses where a feature is backported; it is often inaccurate for gdc's LTS users, so a direct detection of the feature with __traits(hasMember) - possible only with attributes, not with pragmas - is preferable for them to benefit too)

This is a global switch that is not suitable for libraries in general - it can have unpredictable side effects when combined with other code - but is the only option at this time to move forward with the use case, so we use it and hope for the best.

Having @standalone means these uses can finally be expanded with confidence in new compilers, while users of old compilers don't get the enhancement of expanded use, but also don't get the regression of their existing use being broken; they're no better AND no worse than they are now.

@WalterBright
Copy link
Member

Ok, @dkorpel and @adamdruppe make a good case, thank you.

@WalterBright WalterBright merged commit 14ecea9 into dlang:master Dec 22, 2023
44 checks passed
@dkorpel dkorpel deleted the module-constructors-acyclic branch December 22, 2023 19:10
@adamdruppe
Copy link
Contributor

Splendid, thank you, this is gonna solve a long annoyance.

@schveiguy
Copy link
Member

Is there a reason why @standalone cannot be applied to thread-local static constructors? All it is doing is removing it from cycle detection, and there are still valid uses in the case of thread-local constructors.

@adamdruppe
Copy link
Contributor

I just don't think there's an existing cycle-independent list for thread local ones, whereas there was for global ones so it just a factor of convenience.

@schveiguy
Copy link
Member

schveiguy commented Feb 29, 2024

No, both cycles are detected independently.

dmd/druntime/src/rt/minfo.d

Lines 517 to 518 in 03f1497

if (!doSort(MIctor | MIdtor, _ctors) ||
!doSort(MItlsctor | MItlsdtor, _tlsctors))

@adamdruppe
Copy link
Contributor

Yes, but m.ictor already exists (the cycle-independent analog to m.ctor) but there is no m.itlsctor to pair with m.tlsctor (see runCtors and runTlsCtors a few lines below the function you linked).

So before (or simultaneously with) standalone can be added for tls ctors, some kind of itlsctor implementation also needs to be added.

@schveiguy
Copy link
Member

Huh, I never knew about the ictor thing. What I am talking about is the MIstandalone flag (which is what I thought designated standalone modules).

See

if (m.flags & MIstandalone)

Now I have no idea exactly what this PR is doing...

@schveiguy
Copy link
Member

https://issues.dlang.org/show_bug.cgi?id=24425 Please comment if I'm off base here.

@adamdruppe
Copy link
Contributor

I think that would work, indeed, that was one of my first ideas to implement this but when i looked at the code, ictor was already there so the plan shifted to just use that existing stuff. But yeah it is limited to shared static this so expanding to the others would need something new and putting them in a dummy module with the standalone flag indeed ought to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Industry Applies to PRs pertaining to industry applications of D Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org New Language Feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.