hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.23k stars 224 forks source link

[BUG] Cannot emit `static constexpr` function local variables #1093

Open bluetarpmedia opened 4 weeks ago

bluetarpmedia commented 4 weeks ago

Describe the bug I can't find a Cpp2 syntax to emit a static constexpr local variable. static constexpr variables are useful (particularly in debug/non-optimised builds) for look-up tables in a function where you want the data initialised once and also prefer to keep the variable close to its use in the function, rather than declaring it globally.

See:

To Reproduce Run cppfront on this code:

main: () -> int = {
    ints: std::array == (11, 22, 33);
    it: == std::find(ints.cbegin(), ints.cend(), 33);
    return it*;
}

It lowers to:

auto main() -> int{
    std::array constexpr ints{ 11, 22, 33 };
    auto constexpr it = std::find(CPP2_UFCS(cbegin)(ints), CPP2_UFCS(cend)(ints), 33);
    return *cpp2::impl::assert_not_null(it); 
}

which produces a C++ compiler error:

main.cpp2:3:20: error: constexpr variable 'it' must be initialized by a constant expression
    3 |     auto constexpr it = std::find(CPP2_UFCS(cbegin)(ints), CPP2_UFCS(cend)(ints), 33);
      |                    ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp2:3:20: note: pointer to subobject of 'ints' is not a constant expression
main.cpp2:2:26: note: address of non-static constexpr variable 'ints' may differ on each invocation of the enclosing function; add 'static' to give it a constant address
    2 |     std::array constexpr ints{ 11, 22, 33 };
      |                          ^
      |     static 

Repro on Godbolt

Why static constexpr

Here's a C++ example demonstrating why static constexpr is useful:

Side-by-side repro on Godbolt

With static constexpr

int main()
{
    static constexpr std::array ints = {11, 22, 33};
    constexpr auto it = std::find(ints.cbegin(), ints.cend(), 33);
    return *it;
}
MSVC Release `/O2` ```assembly main PROC ; COMDAT mov eax, 33 ; 00000021H ret 0 main ENDP ```
MSVC Debug `/Od` ```assembly std::array const `main'::`2'::ints DD 0bH ; `main'::`2'::ints DD 016H DD 021H it$ = 32 main PROC $LN3: sub rsp, 56 ; 00000038H lea rax, OFFSET FLAT:std::array const `main'::`2'::ints add rax, 8 mov QWORD PTR it$[rsp], rax lea rcx, QWORD PTR it$[rsp] call int const & std::_Array_const_iterator::operator*(void)const ; std::_Array_const_iterator::operator* mov eax, DWORD PTR [rax] add rsp, 56 ; 00000038H ret 0 main ENDP ```

Without static constexpr

int main()
{
    std::array ints = {11, 22, 33};
    auto it = std::find(ints.cbegin(), ints.cend(), 33);
    return *it;
}
MSVC Release `/O2` ```assembly ints$ = 32 __$ArrayPad$ = 48 main PROC ; COMDAT $LN41: sub rsp, 72 ; 00000048H mov rax, QWORD PTR __security_cookie xor rax, rsp mov QWORD PTR __$ArrayPad$[rsp], rax mov r8d, 33 ; 00000021H mov DWORD PTR ints$[rsp], 11 lea rdx, QWORD PTR ints$[rsp+12] mov DWORD PTR ints$[rsp+4], 22 lea rcx, QWORD PTR ints$[rsp] mov DWORD PTR ints$[rsp+8], 33 ; 00000021H call __std_find_trivial_4 mov eax, DWORD PTR [rax] mov rcx, QWORD PTR __$ArrayPad$[rsp] xor rcx, rsp call __security_check_cookie add rsp, 72 ; 00000048H ret 0 main ENDP ```
MSVC Debug `/Od` ```assembly voltbl SEGMENT DDSymXIndex: FLAT:main voltbl ENDS $T1 = 32 $T2 = 40 $T3 = 48 it$ = 56 $T4 = 64 $T5 = 72 ints$ = 80 __$ArrayPad$ = 96 main PROC $LN3: sub rsp, 120 ; 00000078H mov rax, QWORD PTR __security_cookie xor rax, rsp mov QWORD PTR __$ArrayPad$[rsp], rax mov DWORD PTR ints$[rsp], 11 mov DWORD PTR ints$[rsp+4], 22 mov DWORD PTR ints$[rsp+8], 33 ; 00000021H mov DWORD PTR $T1[rsp], 33 ; 00000021H lea rdx, QWORD PTR $T4[rsp] lea rcx, QWORD PTR ints$[rsp] call std::_Array_const_iterator std::array::cend(void)const ; std::array::cend mov rax, QWORD PTR [rax] mov QWORD PTR $T2[rsp], rax lea rdx, QWORD PTR $T5[rsp] lea rcx, QWORD PTR ints$[rsp] call std::_Array_const_iterator std::array::cbegin(void)const ; std::array::cbegin mov rax, QWORD PTR [rax] mov QWORD PTR $T3[rsp], rax lea r9, QWORD PTR $T1[rsp] mov r8, QWORD PTR $T2[rsp] mov rdx, QWORD PTR $T3[rsp] lea rcx, QWORD PTR it$[rsp] call std::_Array_const_iterator std::find,int>(std::_Array_const_iterator,std::_Array_const_iterator,int const &) ; std::find,int> lea rcx, QWORD PTR it$[rsp] call int const & std::_Array_const_iterator::operator*(void)const ; std::_Array_const_iterator::operator* mov eax, DWORD PTR [rax] mov rcx, QWORD PTR __$ArrayPad$[rsp] xor rcx, rsp call __security_check_cookie add rsp, 120 ; 00000078H ret 0 main ENDP ```
hsutter commented 1 week ago

Thanks! More generally, Cpp2 doesn't yet support "magic statics" / static local variables, whether constexpr or not. I think your request is to support a "magic statics" equivalent.

I was dragging my feet on supporting that because call_once is still available for single-lazy-initialization semantics, so I was waiting to see if there was demand for the feature. However, call_once doesn't let you do the initialization of the constexpr value you want.

So it seems that a "static" or "once" or similar qualifier or similar is wanted, that lowers to magic statics...