Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Compile Time Function Execution #9627

Closed
nikeee opened this issue Mar 9, 2016 · 2 comments
Closed

Proposal: Compile Time Function Execution #9627

nikeee opened this issue Mar 9, 2016 · 2 comments

Comments

@nikeee
Copy link

nikeee commented Mar 9, 2016

Let's take this code:

class SomeClass
{
    public const string AConstString = "Foo";
    public const string AnotherConstString = AConstString + "Bar";
}

This works as expected, therefore, the value of AnotherConstString will be set to "FooBar" by the compiler. The IL does not contain anything like AConstString + "Bar". Instead, any use of the constant values above will be inlined to its constant value.

Now take a look at this code:

class SomeOtherClass
{
    public const string AConstString = "Foo";
    public static readonly string AnotherConstString = CreateAnotherString(AConstString);

    private static string CreateAnotherString(string prefix) => prefix + "Bar";
}

This already works as of today. The difference here is that AnotherConstString is clearly a constant value that can be determined at compile time. So what ends up in the IL is a reference to the static field AnotherConstString while CreateAnotherString will be invoked by the static constructor during runtime.

The code:

Console.WriteLine(SomeClass.AnotherConstString);
Console.WriteLine(SomeOtherClass.AnotherConstString);

Compiles to this IL (with optimizations enabled):

ldstr     "FooBar"
call      void [mscorlib]System.Console::WriteLine(string)
ldsfld    string ctfe.SomeOtherClass::AnotherConstString
call      void [mscorlib]System.Console::WriteLine(string)

So, if the compiler knew that CreateAnotherString has no side effects and its return value is entirely based on its parameters and the parameters are compile time constants, the compiler itself could evaluate the function.
The function is required to be a pure function. There already is a proposal for pure functions, since they may have additional benefits.

This could also affect cases like these:

public static void Baz(int foo)
{
    if(foo < 0)
        throw new ArgumentException($"{nameof(foo)} must be greater than or equal to zero.");
}

Currently, what ends up in the IL will be something like this (translated back to C#):

string.Format("{0} must be greater than or equal to zero.", "foo")

If string.Format is a pure function, all parameters are compile time constants, so the compiler could actually invoke string.Format and emit the result instead:

"foo must be greater than or equal to zero."

Of course, if a function is pure, it can remain in the compiled assembly, in case someone invokes it with non-constant parameters.

const string constFoo = "Foo";
string varFoo = "Foo";
Console.WriteLine(SomeOtherClass.CreateAnotherString(constFoo)); // becomes "FooBar"
Console.WriteLine(SomeOtherClass.CreateAnotherString(varFoo)); // remains SomeOtherClass.CreateAnotherString(varFoo)

Other thoughts:

  • Evaluating constructors at compile time? (Could it be used for constant immutable structs?)
  • If a user-defined struct is constant and its overloaded operators can be evaluated by the compiler, something like const Vector2 upLeft = Vector2.Up + Vector2.Left would be a constant expression. This is actually the same thing as in the code of SomeClass above, just using Vector2 instead of string.
  • Common functions of the framework may be marked as "pure" or "constexpr" (e. g. string.Format, Convert.*, string.Join) without losing compatibility.
  • Regarding strings: "FooBar" is a compile time constant, so "FooBar".Length or "FooBar".get_Length() may return constant values. Imagine using it in a scenario like str.Remove(str.Length - "RemoveMe".Length), while the code remains maintainable because "RemoveMe".Length is more self-explanatory than just 8. If str is a constant and Remove is a pure function, the entire expression may be computed by the compiler.

This feature would be pretty similar to other languages supporting CTFE, like D or C++11 with constexpr.
Also, CTFE doesn't have to be implemented in the compiler that compiles to IL. It may be also implemented in the JIT or other AOT compilers.

@svick
Copy link
Contributor

svick commented Mar 9, 2016

In the case of static readonly field, what is the point of optimizing the IL? Under normal circumstances (JIT compilation), it will behave as a constant during JIT anyway (i.e. the CLR executes the field initializers and only then JIT compiles the method, knowing the field can't change).

In the case of interpolated strings, string.Format() is not a pure function (it depends on the current culture), so this also wouldn't help (see #9212).

The constant.Length optimization sounds like something the JIT compiler should be able to do (but as far as I can tell, doesn't currently do).

@gafter
Copy link
Member

gafter commented Mar 30, 2019

Issue moved to dotnet/csharplang #2379 via ZenHub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants