Thread overview
Template specialized functions creating runtime instructions?
Aug 20, 2019
ads
Aug 21, 2019
ads
Aug 21, 2019
H. S. Teoh
Aug 21, 2019
ads
Aug 21, 2019
Patrick Schluter
Aug 21, 2019
ag0aep6g
Aug 21, 2019
Jonathan M Davis
August 20, 2019
This piece of code creates a fizzbuzz string with template parameters.

auto fizzbuzz(uint N)() {
	string accumulate;
	return fizzbuzz!N(accumulate);
}

auto fizzbuzz(uint N)(ref string result) if (N % 3 && N % 5) {
	import std.conv : to;

	result ~= N.to!string ~ "\n";
	return fizzbuzz!(N - 1)(result);
}

auto fizzbuzz(uint N)(ref string result) if (!(N % 15)) {
	result ~= "FizzBuzz\n";
	return fizzbuzz!(N - 1)(result);
}

auto fizzbuzz(uint N)(ref string result) if (!(N % 3) && N % 5) {
	result ~= "Fizz\n";
	return fizzbuzz!(N - 1)(result);
}

auto fizzbuzz(uint N)(ref string result) if (!(N % 5) && N % 3) {
	result ~= "Buzz\n";
	return fizzbuzz!(N - 1)(result);
}

auto fizzbuzz(uint N : 0)(ref string result) {
	return result;
}

void main() {
	import std.stdio : writeln;

	fizzbuzz!50().writeln();
}


https://godbolt.org/z/hWENgc

In the generated assembly, it looks like it is creating a lot of runtime instructions, contrary to my belief that templated codes are purely compile-time. I was expecting that the compiler would deduce the fizzbuzz string until 50 in compile-time and just print it in the run-time. Why is this not the case?
August 21, 2019
On Tuesday, 20 August 2019 at 23:48:04 UTC, ads wrote:
> https://godbolt.org/z/hWENgc

A somewhat similar translation in C++ also creates a lot of runtime instructions
https://godbolt.org/z/psyUtq
August 20, 2019
On Tue, Aug 20, 2019 at 11:48:04PM +0000, ads via Digitalmars-d-learn wrote: [...]
> In the generated assembly, it looks like it is creating a lot of runtime instructions, contrary to my belief that templated codes are purely compile-time. I was expecting that the compiler would deduce the fizzbuzz string until 50 in compile-time and just print it in the run-time. Why is this not the case?

Let's clear up a few things:

1) "Template code" is not the same thing as "compile-time". Templates are essentially "code stencils", used for generating multiple copies (usually with variations) of the same *runtime* code.  The *expansion* of the template is done at compile-time, but the *result* is often runtime code (e.g., template functions, like you have here).

2) Deducing the string as you describe would require CTFE (compile-time function evaluation), which usually isn't done unless the result is *required* at compile-time.  The typical way to force this to happen is to store the result into an enum:

	enum myStr = fizzbuzz!...(...);
	writeln(myStr);

Since enums have to be known at compile-time, this forces CTFE evaluation of fizzbuzz, which is probably what you're looking for here.

3) And BTW, don't confuse "templates" and CTFE, because they are not the same thing, in spite of being both done "at compile-time". See:

	https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time


T

-- 
Those who've learned LaTeX swear by it. Those who are learning LaTeX swear at it. -- Pete Bleackley
August 21, 2019
On Wednesday, 21 August 2019 at 00:04:37 UTC, H. S. Teoh wrote:
> On Tue, Aug 20, 2019 at 11:48:04PM +0000, ads via Digitalmars-d-learn wrote: [...]
> 2) Deducing the string as you describe would require CTFE (compile-time function evaluation), which usually isn't done unless the result is *required* at compile-time.  The typical way to force this to happen is to store the result into an enum:
>
> 	enum myStr = fizzbuzz!...(...);
> 	writeln(myStr);
>
> Since enums have to be known at compile-time, this forces CTFE evaluation of fizzbuzz, which is probably what you're looking for here.
>
> T

Thank you for clearing those up. However even if I force CTFE (line 35), it doesn't seem to help much.

https://godbolt.org/z/MytoLF
August 21, 2019
On 21.08.19 01:48, ads wrote:
> This piece of code creates a fizzbuzz string with template parameters.
> 
> auto fizzbuzz(uint N)() {
>      string accumulate;
>      return fizzbuzz!N(accumulate);
> }
> 
> auto fizzbuzz(uint N)(ref string result) if (N % 3 && N % 5) {
>      import std.conv : to;
> 
>      result ~= N.to!string ~ "\n";
>      return fizzbuzz!(N - 1)(result);
> }
[...]
> void main() {
>      import std.stdio : writeln;
> 
>      fizzbuzz!50().writeln();
> }
> 
> 
> https://godbolt.org/z/hWENgc
> 
> In the generated assembly, it looks like it is creating a lot of runtime instructions, contrary to my belief that templated codes are purely compile-time. I was expecting that the compiler would deduce the fizzbuzz string until 50 in compile-time and just print it in the run-time. Why is this not the case?

What you have there are function templates. You're generating functions at compile time, and then you're calling those generated functions at run time.

To do it all at compile time, you can skip the "functions" part and generate the result directly:

    template fizzbuzz(uint N) if (N % 3 && N % 5)
    {
        import std.conv : to;
        enum fizzbuzz = N.to!string ~ "\n" ~ fizzbuzz!(N - 1);
    }
    /* ... etc ... */

You won't be able to use an accumulator, because mutation like that doesn't mix with templates.

Alternatively, you can skip the "templates" part and call normal functions at compile time (CTFE):

    auto fizzbuzz(uint N)
    {
        string accumulate;
        return fizzbuzz(N, accumulate);
    }
    auto fizzbuzz(uint N, ref string result)
    {
        import std.conv : to;
        if (N % 3 && N % 5) result ~= N.to!string ~ "\n";
        /* ... etc ... */
    }
    void main()
    {
        import std.stdio : writeln;
        enum fz = fizzbuzz(50);
        writeln(fz);
    }
August 20, 2019
On Tuesday, August 20, 2019 5:48:04 PM MDT ads via Digitalmars-d-learn wrote:
> This piece of code creates a fizzbuzz string with template parameters.
>
> auto fizzbuzz(uint N)() {
>   string accumulate;
>   return fizzbuzz!N(accumulate);
> }
>
> auto fizzbuzz(uint N)(ref string result) if (N % 3 && N % 5) {
>   import std.conv : to;
>
>   result ~= N.to!string ~ "\n";
>   return fizzbuzz!(N - 1)(result);
> }
>
> auto fizzbuzz(uint N)(ref string result) if (!(N % 15)) {
>   result ~= "FizzBuzz\n";
>   return fizzbuzz!(N - 1)(result);
> }
>
> auto fizzbuzz(uint N)(ref string result) if (!(N % 3) && N % 5) {
>   result ~= "Fizz\n";
>   return fizzbuzz!(N - 1)(result);
> }
>
> auto fizzbuzz(uint N)(ref string result) if (!(N % 5) && N % 3) {
>   result ~= "Buzz\n";
>   return fizzbuzz!(N - 1)(result);
> }
>
> auto fizzbuzz(uint N : 0)(ref string result) {
>   return result;
> }
>
> void main() {
>   import std.stdio : writeln;
>
>   fizzbuzz!50().writeln();
> }
>
>
> https://godbolt.org/z/hWENgc
>
> In the generated assembly, it looks like it is creating a lot of runtime instructions, contrary to my belief that templated codes are purely compile-time. I was expecting that the compiler would deduce the fizzbuzz string until 50 in compile-time and just print it in the run-time. Why is this not the case?

Function templates create normal functions when they're instantiated. They aren't going to be removed from the binary any more than a non-templated function would be. And the compiler has no way of knowing which functions can be removed from the final executable. It's just creating object files that get linked into the final executable. It's the linker that would have to strip unnecessary stuff out. And since fizzbuzz!50 is called at runtime, even if the linker did strip out functions that weren't needed, it couldn't strip out any of fizzbuzz!50 or anything that it calls.

The compiler doesn't call functions at compile time unless they're used in a context where the value must be known at compile time. So,

fizzbuzz!50.writeln();

is not going to involve any fnuctions being called at compile time. Each function template that needs to be instantiated would be instantiated, but that just creates a bunch of functions that can be called. For any functions to be called at compile time, you'd have to force it by calling a function in a context where the result must be known at compile time. e.g.

enum value = fizzbuzz!50();

would result in fizzbuzz!50 being called at compile time, and then the value could be passed to writeln at runtime.

If you actually want templates to do all of their work at compile time, then you wouldn't use functions. You'd just use eponymous templates. e.g. something like

template factorial(int n)
{
    static if(n == 1)
        enum factorial = n;
    else
        enum factorial = factorial!(n - 1) * n;
}

in which case the you'd use it by doing something like

enum result = factorial!5;

or

int result = factorial!5;

In either case, because factorial is purely a template, the work would be done at compile time.

Alternatively, you can just call functions at compile time - you just have to make sure that the call is in a context where the result must be known at compile time. e.g.

int factorial(int n)
{
    int result = 1;
    foreach(i; 2 .. n + 1)
        result *= i;
    return result;
}

enum result = factorial(5);

There arguably isn't much point in using templated functions the way you are. It would be simpler to just write a function that calculated the result normally, and then if you want to have the result at compile time, you just call the function and use the result to give an enum its value.

- Jonathan M Davis



August 21, 2019
On Wednesday, 21 August 2019 at 00:11:23 UTC, ads wrote:
> On Wednesday, 21 August 2019 at 00:04:37 UTC, H. S. Teoh wrote:
>> On Tue, Aug 20, 2019 at 11:48:04PM +0000, ads via Digitalmars-d-learn wrote: [...]
>> 2) Deducing the string as you describe would require CTFE (compile-time function evaluation), which usually isn't done unless the result is *required* at compile-time.  The typical way to force this to happen is to store the result into an enum:
>>
>> 	enum myStr = fizzbuzz!...(...);
>> 	writeln(myStr);
>>
>> Since enums have to be known at compile-time, this forces CTFE evaluation of fizzbuzz, which is probably what you're looking for here.
>>
>> T
>
> Thank you for clearing those up. However even if I force CTFE (line 35), it doesn't seem to help much.
>
> https://godbolt.org/z/MytoLF

It does.

on line 4113 you have that string

.L.str:
        .asciz  "Buzz\n49\nFizz\n47\n46\nFizzBuzz\n44\n43\nFizz\n41\nBuzz\nFizz\n38\n37\nFizz\nBuzz\n34\nFizz\n32\n31\nFizzBuzz\n29\n28\nFizz\n26\nBuzz\nFizz\n23\n22\nFizz\nBuzz\n19\nFizz\n17\n16\nFizzBuzz\n14\n13\nFizz\n11\nBuzz\nFizz\n8\n7\nFizz\nBuzz\n4\nFizz\n2\n1\n"

and all main() does is call writeln with that string

_Dmain:
        push    rax
        lea     rsi, [rip + .L.str]
        mov     edi, 203
        call    @safe void std.stdio.writeln!(immutable(char)[]).writeln(immutable(char)[])@PLT
        xor     eax, eax
        pop     rcx
        ret


You haven't given instruction to the linker to strip unused code so the functions generated by the templates are still there.