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