forcing compile time function execution

Artur Skawina art.08.09 at gmail.com
Tue Dec 27 13:44:47 PST 2011


On 12/27/11 20:18, so wrote:
> On Tue, 27 Dec 2011 21:02:49 +0200, Artur Skawina <art.08.09 at gmail.com> wrote:
> 
>> On 12/27/11 19:29, Vladimir Panteleev wrote:
>>> On Tuesday, 27 December 2011 at 18:00:27 UTC, Artur Skawina wrote:
>>>> Is there a way to *force* CTFE? Without using an extra wrapper like this:
>>>>
>>>> auto f_impl(alias a)() { / * ... ctfeable ... */ }
>>>> auto f(alias a)() { enum ctfed = f_impl!a; return ctfed; }
>>>>
>>>> A "@compile" (or "@ctfe" etc) function attribute would eliminate the need for the wrapper...
>>
>> Umm, the point was to *avoid* the extra useless wrapper. The only reason for its existence (and any kind of assignment to a static/enum) is to tell the compiler to try CTFE. Without the compile time assignment, it won't try hard enough - which means that, for f_impl()s that are not simple enough, code will be silently emitted and evaluated at runtime. Even when the functions are supposed to only do *compiletime* checking. (This happens eg. when /ctfeable/ contains loops)
>>
>> Hence the "@compile" attribute suggestion, and list selection.
>>
>> (I actually tried something similar to Now(), but that only obfuscates the code even more, for no gain)
>>
>> artur
> 
> // lib.d
> 
> T fun(T);
> 
> unittest
> {
>   enum a = fun;
> }
> 
> Should do the trick. No change in client code. Yes @ctfe_forced might be nice but not that we lack a solution.
> I also like this one because it doesn't pollute the function.
> 

Hmm, maybe i'll try an example.

Let's say you want to calculate the value for use as a mask to strip off some high bits of an int. You only have the number of allowed values, which /is supposed to/ be a power of two. This is in generic, library code, so you can not assume anything more.
Obviously the answer is "uint POW2MASK(uint n) {return n-1;}"
But if somebody calls this with a wrong argument it will silently fail horribly. Also it is simple enough to always be fully inlined. And can then be made to work on more than just an int. And will often be used with a predefined constant 'n'.
The "safe" solution for the "unknown constant case" might look like this:

auto POW2MASK(alias N)() {
   ulong n = N;
   enum MSB = 1UL<<(n.sizeof*8-1);
   n--;
   while ((n&MSB)==0)
      n = (n<<1) + 1;
   if (n==n.max)
      return cast(typeof(N))(N-1);
   assert(0, N.stringof ~ " is not a power of two.");  // Abort compilation.
}

which can be used to statically check constants, like this:

private enum HASHSIZE_check_ = POW2MASK!HASHSIZE;


But now that we have this function, we can also use it in place of the open coded "n-1" or the original POW2MASK() in other (template) functions to gain some extra safety for free, right? "return (result&POW2MASK!N)" etc.

Wrong. The compiler will actually emit the constant-checking code, instead of just using the right constant. It will do this silently, so, unless you inspect the generated code, you might never realize this.

The solution? This:

// Given a constant power-of-two value (with just one bit set) return
// a 'mask' that can be used to mask of any higher bits so that
// the result fits into [0..N) range.
// Checks (at compile time) that the integer is a usable power of two.
// NOTE:
// "N==0" _is_ allowed, as it can be useful;
// "N==1" is not very useful (mask is zero) but is allowed too. 
// 'N' should probably be unsigned, but this function does not enforce
// this and will return the same type as that of 'N'.
//
// The implementation has to be split into two functions to force
// the compiler to execute the checks at compile time and not emit
// any of the ctfe-only code.
private auto POW2MASK_CTFE(alias N)() {
   ulong n = N;
   enum MSB = 1UL<<(n.sizeof*8-1);
   n--;
   while ((n&MSB)==0)
      n = (n<<1) + 1;
   if (n==n.max)
      return cast(typeof(N))(N-1);
   assert(0, N.stringof ~ " is not a power of two.");  // Abort compilation.
}
auto POW2MASK(alias N)() {
   enum R = POW2MASK_CTFE!N;
   return R;
}

Using this version we now get 100% bit-for-bit identical executable to the "N-1" open coded one.
Zero runtime cost, but the build will immediately fail if someone tries to use a non-power-of-2 value. (This has already caught at least one off-by-one bug here, btw)

This is just an example, that check could be done in n different ways, some of which would probably be completely optimized out (eg switch statement). CTFE can be very useful for things like checking constant requirements (especially when you have several /dependent/ constants) in templates, but if every time it's used to gain some extra safety you have to obfuscate the code, then it becomes much less attractive. 

The wrapper is there *only* to force CTFE, and *has* to be there for every single function that relies on ctfe happening, and probably would be a good idea for every function that is expected to evaluate at compile time. Omitting it could mean that even if things are ok now (ie no ctfe required), later when someone changes something (like adds another check after finding a bug) the result could be an unexpected app slowdown, with absolutely no warning. If you think that that someone should realize this -- well, even then, he/she has a choice of adding the check, then renaming the function and introducing a wrapper, or deciding this is not worth the effort... I know what i would do... ;)

"auto @ctfe POW2MASK(alias N)() {...}" would be much more straightforward than the idiom above, wouldn't it? [1]

artur

[1] and i don't even like "@ctfe", but "@compile_time" would be too verbose...


More information about the Digitalmars-d mailing list