new annotation or pragma to mark functions that are intended to be only used during compile time

user1234 user1234 at 12.de
Thu Feb 13 17:16:45 UTC 2025


On Thursday, 13 February 2025 at 14:56:42 UTC, Ilya wrote:
> # Proposal
>
> I'm proposing to add a new function annotation (or pragma) to 
> mark functions that should never be executed at run time. I 
> call the annotation `@ctonly` in this proposal, but I'm 
> notoriously bad at naming things, so I'm open to naming 
> suggestions.
>
> I have a draft PR with a (slightly simplified) implementation 
> here: https://github.com/dlang/dmd/pull/20858
>
> ## Motivation
>
> My motivation is two-fold.
>
> 1. We have some functions that we know we intend to only use 
> during compile time. Yet it’s really easy to write `auto v = 
> f(x);`  instead of `enum v = f(x);` , so the function call 
> shifts to run time. We could hope for some constant 
> fold/partial evaluation optimization to kick in the later 
> stages of the compilation, such that it still gets pre-computed 
> at compile time. But why should we rely on that, while D gives 
> us all the controls? Marking `f` as `@ctonly` will easily 
> uncover this unintended usages.
> 2. If a function is marked `@ctonly`, backends can skip codegen 
> for that function (currently I’ve only implemented that for LDC 
> in my local branch, but the implementation is really 
> straightforward). That might seem like a micro optimization at 
> first glance, but if a `@ctonly` function is templated and 
> instantiated widely, code generation cost for it becomes 
> non-trivial.
>
> ## Implementation Ideas
>
> Basically, what we need is to check is that `@ctonly` functions 
> are never called outside from CTFE context and that we never 
> take addresses of these functions.
>
> ### New function attribute
>
> Let’s add a new function attribute `@ctonly` to mark CT only 
> functions.
>
> This doesn’t require mangling change, since the feature is 
> essentially frontend-only. It also doesn’t make much sense to 
> have both `@ctonly` and non `@ctonly` versions of a function 
> with the same signature, so I suggest to **not** count the new 
> attribute when comparing function attributes for equality.
>
> This is implemented in the first commit of the PR.
>
> ### Basic check rules
>
> 1. In semantic check of `CallExp`, make sure that if the callee 
> function is `@ctonly`, either the caller function is also 
> `@ctonly` or the call is in CTFE context.
> 2. Fail on taking the address of `@ctonly` functions.
>
> Basic rules are implemented in the second commit, and the third 
> commit adds some tests to showcase accepted and rejected code 
> snippets.
>
> ### Basic rules limitations
>
> Though this works in simple cases, unfortunately there is at 
> least one case, where it doesn’t work as I would like. Consider 
> this code:
>
> ```cpp
> int f(int x) @ctonly { return x + 1; }
> enum a = map!f([1, 2, 3]).array;
> ```

This is not that a problem imo. I think @ctonly would only be 
used in a certain
way that is `mixin(someCall(someArgs))`, so if a few useless code 
still slips in the object files, well that's a pity, but that can 
get fixed later.

(That case is actually a problem of "attribute-transitivity")

>
> There is nothing wrong with it, `f` is only called during 
> compile time. But with only the basic rules, the compiler fails 
> to understand that. The problem with the basic rules is they 
> require explicit `@ctonly` everywhere. Which I think is good 
> for the most part. Except for this case with templates from the 
> standard library (or any other library actually).
>
> There is a way to write `MapResult` implementation such that it 
> checks if a function it gets via its template parameter is 
> `@ctonly` and adjusts all callers to also be `@ctonly`... but 
> that’s going to result in a lot of code duplication and, 
> probably more importantly, will require changes to the stdlib.
>
> So what if we enable inference for template instance functions?
>
> ### Additional rules
>
> 1. Add an extra bit marking if `@ctonly` was inferred.
> 2. If `f` is `@ctonly` **and** `f` is called from `g` **and** 
> `g` is a template instance function **and** `f` is an alias 
> parameter somewhere in `g`'s instantiation stack, mark `g` as 
> inferred `@ctonly` with an inference reason `f`.
> 3. If `f` is inferred `@ctonly` with reason `r` **and** `f` is 
> called from `g` **and** `g` is a template instance function 
> **and** `r` is an alias parameter somewhere in `g`'s 
> instantiation stack, mark `g` as inferred `@ctonly` with an 
> inference reason `r`.
>
> A hackier version of these rules is implemented in the fourth 
> commit. In particular I only look at the first level of 
> template parameters for initial inference step. And don’t look 
> at template parameters at all for subsequent steps, instead 
> promoting further as long as the caller is a template instance 
> function. This is suboptimal and could be improved.

Overall that's a good idea. In the bug tracker we have that 
alternative that is "pure functions must be ctfe-able". Problem 
is that D purity is not strong, you can wrap a function in a 
delegate so that it appears to be pure to the compiler; so the 
obvious question is "@ctonly would mean strongly-pure" ?

Also as far as I understand the intent, it should be well denoted 
that the goal is to skip emiting useless code in the objects.


More information about the dip.ideas mailing list