1 day ago

On Thursday, 20 February 2025 at 15:50:33 UTC, Steven Schveighoffer wrote:

>

This has been proposed before, and it doesn't require language changes:

void foo() {
   assert(__ctfe);
}

Yes, I've seen that. But did it go anywhere? I think on the thread I've found there are some discussions with no conclusion.

I also found another proposals that are suggesting introducing a new attribute (like I do). But there are also no sign of any activity.

I do see a value in not requiring any language changes, but:

  1. I'm concerned about doing compile time things based on an assert, which is a run time thing.
  2. I think making an attribute is a bit clearer. Like, asserts are supposed to only affect debug builds (well, except for assert(false)), but I want this new functionality to work in release builds too. There is nothing that stops me from implementing that, but I don't like that assert's meaning gets another exception.
>

Basically, this will never work at runtime, only at compile time. It's already used in a lot of D code for things like this.

I agree with the point that existing code could immediately benefit from it. That's great. But there is also the second side of this medal: that could also render some existing code non-compilable. How come? Asserts are run-time, what I'm proposing is a static check, so compile time. Static checks are usually either unsound or incomplete. I prefer to stay on the sound side, so it's doomed to be incomplete, meaning it will reject some examples that are working fine with run-time asserts. Imagine this:

int ctonly(int x) {
  assert(__ctfe);
  return x+1;
}
int f(int v) {
  if (v < 10) {
    return ctonly(v);
  } else {
    return smth_runtime(v);
  }
}
void main() {
  enum x = f(9); // that's fine, CTFE
  auto y = f(10); // that's also fine, f() doesn't call ctonly()
}

This works with runtime assert, but will be rejected by the check.

>

The compiler can take this hint as "do not optimize or generate object code for this".

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).

>

The compiler can also decide at code-generation time to have an error if it has tried to call the function, or maybe the mark gets spread to the next level up?

I think it's better to require the user to be explicit about that, otherwise we may end up with skipping code generation of main :) The only exception is when such function is passed as a template parameter, in this case I believe we need to add some basic inference to make it usable in practice.

>

It can be very straightforward -- if this is not the first runtime statement in the function, then it doesn't get the benefit. It has to be this form.

Yeah, we can take the shortcut in the implementation and only look at the first statement, but I'm more concerned about changing the semantics.

Why does it have to be this form?

1 day ago

On Thursday, 20 February 2025 at 15:51:51 UTC, Paul Backus wrote:

>

In case you weren't aware, this exists in C++, and is called [consteval][1].

Thanks for the pointer, I haven't used consteval in C++, that seems relevant indeed.

1 day ago

On Thursday, 20 February 2025 at 17:42:25 UTC, Ilya wrote:

>

On Thursday, 20 February 2025 at 15:50:33 UTC, Steven Schveighoffer wrote:

>

This has been proposed before, and it doesn't require language changes:

void foo() {
   assert(__ctfe);
}

Yes, I've seen that. But did it go anywhere? I think on the thread I've found there are some discussions with no conclusion.

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.

>

I do see a value in not requiring any language changes, but:

  1. I'm concerned about doing compile time things based on an assert, which is a run time thing.

At the root this is code generation. Do you want to generate code or not? Semantic already has to run for CTFE.

>
  1. I think making an attribute is a bit clearer. Like, asserts are supposed to only affect debug builds (well, except for assert(false)), but I want this new functionality to work in release builds too. There is nothing that stops me from implementing that, but I don't like that assert's meaning gets another exception.

assert(__ctfe) (and assert(!__ctfe)) could be in the same vein as assert(0) -- that is, it always evaluates even in release builds.

> >

Basically, this will never work at runtime, only at compile time. It's already used in a lot of D code for things like this.

I agree with the point that existing code could immediately benefit from it. That's great. But there is also the second side of this medal: that could also render some existing code non-compilable. How come? Asserts are run-time, what I'm proposing is a static check, so compile time. Static checks are usually either unsound or incomplete. I prefer to stay on the sound side, so it's doomed to be incomplete, meaning it will reject some examples that are working fine with run-time asserts. Imagine this:

int ctonly(int x) {
  assert(__ctfe);
  return x+1;
}
int f(int v) {
  if (v < 10) {
    return ctonly(v);
  } else {
    return smth_runtime(v);
  }
}
void main() {
  enum x = f(9); // that's fine, CTFE
  auto y = f(10); // that's also fine, f() doesn't call ctonly()
}

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).

> >

The compiler can take this hint as "do not optimize or generate object code for this".

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?

> >

The compiler can also decide at code-generation time to have an error if it has tried to call the function, or maybe the mark gets spread to the next level up?

I think it's better to require the user to be explicit about that, otherwise we may end up with skipping code generation of main :) The only exception is when such function is passed as a template parameter, in this case I believe we need to add some basic inference to make it usable in practice.

I think with replacing any runtime calls to a ctfe-only function with an assert, we have basically the best that can be had.

> >

It can be very straightforward -- if this is not the first runtime statement in the function, then it doesn't get the benefit. It has to be this form.

Yeah, we can take the shortcut in the implementation and only look at the first statement, but I'm more concerned about changing the semantics.

Why does it have to be this form?

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
  • no new attributes to worry about
  • if we hook onto a de-facto standard, then existing code is upgraded automatically.

-Steve

1 day ago
On 21/02/2025 8:44 AM, Steven Schveighoffer wrote:
> On Thursday, 20 February 2025 at 17:42:25 UTC, Ilya wrote:
> 
>     On Thursday, 20 February 2025 at 15:50:33 UTC, Steven Schveighoffer
>     wrote:
> 
>         This has been proposed before, and it doesn't require language
>         changes:
> 
>         |void foo() { assert(__ctfe); } |
> 
>     Yes, I've seen that. But did it go anywhere? I think on the thread
>     I've found there are some discussions with no conclusion.
> 
> 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.

I wouldn't call it any kind of standard.

But since it cannot work at runtime, and can be seen during compilation it doesn't need a DIP to see this statement and turn off codegen for the function.

Hmm, I should be able to implement it, gimmie a bit.

1 day ago
On 21/02/2025 1:38 PM, Richard (Rikki) Andrew Cattermole wrote:
> On 21/02/2025 8:44 AM, Steven Schveighoffer wrote:
>> On Thursday, 20 February 2025 at 17:42:25 UTC, Ilya wrote:
>>
>>     On Thursday, 20 February 2025 at 15:50:33 UTC, Steven Schveighoffer
>>     wrote:
>>
>>         This has been proposed before, and it doesn't require language
>>         changes:
>>
>>         |void foo() { assert(__ctfe); } |
>>
>>     Yes, I've seen that. But did it go anywhere? I think on the thread
>>     I've found there are some discussions with no conclusion.
>>
>> 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.
> 
> I wouldn't call it any kind of standard.
> 
> But since it cannot work at runtime, and can be seen during compilation it doesn't need a DIP to see this statement and turn off codegen for the function.
> 
> Hmm, I should be able to implement it, gimmie a bit.

Not possible.

assert gets processed during semantic 3, but things like variable declarations are seen during semantic 2.

It has to be an attribute seeable on the function declaration.

```d
extern(C) void main() {
	__gshared global = func; // Error: cannot use non-constant CTFE pointer in an initializer `func()
	func; // Error: undefined reference to `void start.func()`
}

int* func() {
	assert(__ctfe);
	return new int;
}
```

17 hours ago

On Thursday, 20 February 2025 at 19:44:37 UTC, Steven Schveighoffer wrote:

>

On Thursday, 20 February 2025 at 17:42:25 UTC, Ilya wrote:

>

On Thursday, 20 February 2025 at 15:50:33 UTC, Steven Schveighoffer wrote:

>

This has been proposed before, and it doesn't require language changes:

void foo() {
   assert(__ctfe);
}

Yes, I've seen that. But did it go anywhere? I think on the thread I've found there are some discussions with no conclusion.

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.

Paul Backus pointed out on discord, there is a very large problem with this mechanism:

importing a file with a function that is not a template nor an auto function means the semantic is skipped for that function. This means it would be impossible to know that this function is ctfe-only. The only other solution there is to semantic every function, which basically negates any wins we get from not optimizing these functions.

So reading all the responses here, my impression is:

  1. I think it's a necessary idea to have an attribute if we want this feature. I like using @__ctfe as this requires no new syntax, and actually already compiles (it just doesn't do the thing expected). What remains to be seen is if we can hook this properly. @__ctfe becomes @(true) currently. If not, then a new attribute is trivial to add to core.attributes.
  2. If we are going to have a mechanism to flag that code generation should be avoided, it would be good to also do this for functions that begin with assert(__ctfe). This at least helps with existing uses of that mechanism.
  3. I don't agree with the compiler errors for all uses of ctfe-only functions at runtime. As you pointed out, this can be valid for cases. I don't think it's worth the headache, and I have learned in the past not to pre-optimize for rule following especially in generic code. Just let the thing happen as it would.
  4. To that end, calling a known ctfe-only function should result in an appropriate assert(false, "cannot call ctfe-only function blahblah at runtime") when doing code generation instead of the call to a non-existent function that has a linker error.

-Steve

17 hours ago
On Friday, 21 February 2025 at 01:14:50 UTC, Richard (Rikki) Andrew Cattermole wrote:
> On 21/02/2025 1:38 PM, Richard (Rikki) Andrew Cattermole wrote:
>> On 21/02/2025 8:44 AM, Steven Schveighoffer wrote:
>>> [...]
>> 
>> I wouldn't call it any kind of standard.
>> 
>> But since it cannot work at runtime, and can be seen during compilation it doesn't need a DIP to see this statement and turn off codegen for the function.
>> 
>> Hmm, I should be able to implement it, gimmie a bit.
>
> Not possible.
>
> assert gets processed during semantic 3, but things like variable declarations are seen during semantic 2.
>
> It has to be an attribute seeable on the function declaration.
>
> ```d
> extern(C) void main() {
> 	__gshared global = func; // Error: cannot use non-constant CTFE pointer in an initializer `func()
> 	func; // Error: undefined reference to `void start.func()`
> }
>
> int* func() {
> 	assert(__ctfe);
> 	return new int;
> }
> ```


Instead of `@ctonly`, we could just reuse also the existing identifier, and use it as `@__ctfe`, so it is easier for the compiler to take it into account. Creating a new identifier only for that would create a competition between those two needlessly when they mean the same thing.
55 minutes ago
On Friday, 21 February 2025 at 01:14:50 UTC, Richard (Rikki) Andrew Cattermole wrote:
> On 21/02/2025 1:38 PM, Richard (Rikki) Andrew Cattermole wrote:
>> [...]
>
> Not possible.
>
> assert gets processed during semantic 3, but things like variable declarations are seen during semantic 2.
>
> It has to be an attribute seeable on the function declaration.
>
> ```d
> extern(C) void main() {
> 	__gshared global = func; // Error: cannot use non-constant CTFE pointer in an initializer `func()
> 	func; // Error: undefined reference to `void start.func()`
> }
>
> int* func() {
> 	assert(__ctfe);
> 	return new int;
> }
> ```

You might want to look at my 4 years old PR:|
https://github.com/dlang/dmd/pull/11007/files
1 2
Next ›   Last »