new annotation or pragma to mark functions that are intended to be only used during compile time
Ilya
ilya.yanok at gmail.com
Sun Feb 23 16:33:48 UTC 2025
On Thursday, 20 February 2025 at 19:44:37 UTC, Steven
Schveighoffer wrote:
> It has not gone anywhere, but if we were to consider a language
> change, I'd want to evaluate whether this is a better option,
> since it's a de-facto standard.
Ok, let me take a step back. What I want to do is give users a
way to say "this function is used only at compile time and you
can skip codegen for it".
You say "this is already `assert(__ctfe)` for it, just use it".
The problem with `assert(__ctfe)` is it *doesn't* guarantee
codegen can be skipped. See the example above. Same goes for the
suggested `in(__ctfe)` function contract.
Yes, we can skip codegen for a function starting with
`assert(__ctfe)`, **if** we replace all calls to this function
with `assert(__ctfe)` **and** we are not in release build. I
don't quite like it:
1. It sounds more complicated than my approach
2. More importantly, it only works in debug builds. To make it
work with release builds, we need to keep `assert(__ctfe)` in
release builds (as you suggest below). But that's a breaking
change and a pretty dangerous one. Imagine you have a function
`f` that starts with `assert(__ctfe)` that is almost always
called under CTFE. Almost. But there is a rare code path that
runs `f` at run time, that code path is not covered by any tests
and happens roughly once a month in prod. What happens today? We
hit `f` at run time, we run it, it might be much slower than we
expected, we might even miss some deadline... but it works. If we
replace calls to `f` with `assert(false)`, once that code path is
triggered, it just crashes.
> At the root this is code generation. Do you want to generate
> code or not? Semantic already has to run for CTFE.
Oh, when I say "semantics" I mean "program meaning", not refer to
a semantic analysis pass in the compiler. Sorry for confusion.
Yes, I want to skip code generation. Just skipping code
generation is pretty easy, I actually started with that, I can
add an UDA and hack our backend (LDC) to skip codegen for marked
functions. But I found that linker errors are not the nicest
feedback when I was trying to make my annotations work. They
usually give you a pretty good hint, yes. But there is only a
function name, and it's mangled, and you have to wait until _all_
the compilations are done, so you can run the linker.
So I thought I can do better, and make a more reasonable error
message much faster with a bit of static analysis.
> `assert(__ctfe)` (and `assert(!__ctfe)`) could be in the same
> vein as `assert(0)` -- that is, it always evaluates even in
> release builds.
Yes, they could. But that's a breaking change, see above.
>> This works with runtime assert, but will be rejected by the
>> check.
>
> Yes, this is a problem with the idea to make it a compiler
> error.
>
> However, the compiler still does not have to generate code for
> `ctonly`, even if we don't make it an error. It can just
> replace the call with an `assert(__ctfe)` (or one that produces
> a nice message).
See above. That only works in debug build. Or we can to enable
`assert(__ctfe)` in release, which is a breaking change.
But even putting that aside, I actually *like* it being a
compiler error (I'm obviously biased, because of my types/static
analysis background).
I was experimenting with our code base and my workflow was like
this:
1. Find a good candidate (a function with lots of instances in
object files but no matching symbols in the linked binary).
2. Mark it with `@ctonly` and look at compiler errors one by one.
3. There are essentially two cases:
- the caller function is RT, so need to add an `enum` to force
CTFE. This case could be caught by `assert(__ctfe)` **if** there
is test coverage
- the caller function could be CT-only itself. This will never
be caught with `assert(__ctfe)`.
>> That won't work the same way `assert(__ctfe)` works today, see
>> the example above, so you are proposing to change the
>> semantics of `assert(__ctfe)`.
>
> Yes, you are right. Then again, your attribute has the same
> issue, no?
No, it's a brand new attribute, with a brand new meaning. Yes, it
will compile-time reject some code that will work with
`assert(__ctfe)`. But that's normal, we don't (and can't) promise
that it is equivalent to `assert(__ctfe)`.
> I think with replacing any runtime calls to a ctfe-only
> function with an assert, we have basically the best that can be
> had.
See above, either it only works in debug or we need the breaking
change.
> In general we should prefer solutions that don't require
> changing syntax/semantics:
> - no need to update IDEs/LSP
> - no need to change any semantics
... but you are proposing to change `assert(__ctfe)` semantics :)
> - no new attributes to worry about
> - if we hook onto a de-facto standard, then existing code is
> upgraded automatically.
Ok, I see two totally reasonable concerns here:
1. Tooling backward compatibility (even though LSPs usually
either use the compiler directly or derive from it, I see your
point here).
2. Upgrading the existing code.
Regarding 1. It doesn't have to be a full-fledged function
attribute. Could be an UDA or pragma, so tools that don't know
about it (pretty much everything) will simply ignore it.
Regarding 2. I don't think automatic upgrade is possible or even
desirable. Since the two things are not strictly equivalent.
Being said that, `assert(__ctfe)` is still a great signal that a
function is CT-only. So, what we could do is provide a migration
tool that would try adding new attributes to such functions, run
the compiler, and if the compiler says "yes", we can do the
change.
More information about the dip.ideas
mailing list