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

stack overflow when compiling win32 - solved #92

Closed
jmkinzer opened this issue Oct 22, 2016 · 19 comments
Closed

stack overflow when compiling win32 - solved #92

jmkinzer opened this issue Oct 22, 2016 · 19 comments
Labels
Milestone

Comments

@jmkinzer
Copy link

Compiling under VS2015 I received an immediate crash due to stack overflow initializing SolveSpace::SS (at least under the debug build). I found it necessary to bump the stack to around 32MB to resolve this, i.e.:
/STACK:33554432
Would recommend adding something like this to CMakeLists.txt (untested):
if(MSVC) set(CMAKE_EXE_LINKER_FLAGS "/STACK:33554432 ${CMAKE_EXE_LINKER_FLAGS}") endif()

@whitequark whitequark added the bug label Oct 22, 2016
@whitequark
Copy link
Contributor

Yes, this has been a problem with debug builds in the past with other functions... interestingly, VS2013 doesn't have that. Ideally this should be fixed by redoing the way the text window works.

@Evil-Spirit
Copy link
Collaborator

@jmkinzer, thank you, I have the same issue but not actually try to understand why it happens.

@whitequark whitequark modified the milestone: 3.0 Nov 18, 2016
@bjaraujo
Copy link

Got the same issue.
Going to try that fix.

@plachenko
Copy link

This allowed me to compile in VS2017-- will this be added to master?

@whitequark
Copy link
Contributor

Fixed in master.

@ruevs
Copy link
Member

ruevs commented Dec 20, 2019

@whitequark
Let me resurrect this ancient one. I think I know why the stack overflow was (is) happening on Windows. Using the code analysis tool in MSVC 2019 I get the following warnings:

.\src\solvespace.h(813): warning C6262: Function uses '5614392' bytes of stack:  exceeds /analyze:stacksize '16384'.. This allocation was for a compiler-generated temporary for 'class SolveSpace::TextWindow' at line 813.  Consider moving some data to heap.
.\src\solvespace.h(814): warning C6262: Function uses '21016632' bytes of stack:  exceeds /analyze:stacksize '16384'.. This allocation was for a compiler-generated temporary for 'class SolveSpace::System' at line 814.  Consider moving some data to heap.

The lines in question are these:
https://github.com/solvespace/solvespace/blob/master/src/solvespace.h#L813

So the analyser thinks that this constructor:

    SolveSpaceUI()
        : pTW(new TextWindow({})), TW(*pTW),
          pSys(new System({})), sys(*pSys) {}

somehow temporarily puts a copy of both TextWindow and System on the stack or about 26MB - not nice. I'll try to debug this on assebler level to confirm that this is indeed the case.

@whitequark
Copy link
Contributor

whitequark commented Dec 20, 2019

I know that this is/was the case, no need to confirm anything. Fixing this properly requires rewriting the text window completely to use a less primitive rendering approach. This is complicated: it really ought to use Freetype and sprite atlases to handle CJK with good resolution as well as to allow changing text window size. So this isn't done yet.

I'm not really interested in replacing the current (poor) solution that commits 26 MB of stack with another hack.

ruevs added a commit to ruevs/solvespace that referenced this issue Dec 20, 2019
See:
solvespace#92 (comment)

The problem was first introduced here:
solvespace@dabd578

and later "fixed" here:
solvespace@f324477
by setting the stack size to /STACK:33554432

Solvespace now starts up even with /STACK:554432

The only question now is whether omitting the {} in TextWindow and System constructor calls in the initializer list of the SolveSpaceUI constructor has any adverse effect.
@ruevs
Copy link
Member

ruevs commented Dec 20, 2019

Hmmm... I think the problem is that new TextWindow({}) in addition to allocating memory on the heap for the instance also creates a zero initialized TextWindow object on the stack (because of {}) and passes it to the constructor. new System({}) does the same. If we remove the {} the stack usage drops dramatically. See my pull request #533.

Solvespace runs. The only remaining question is whether omitting the {} is actually bad. I suspect it might be, because (if I understand the C++ initialization semantics correctly) the two instances end up uninitialized ("random" garbage from the heap). If we can find a way to assign to the TW and sys references zero initialized, heap allocated TextWindow and System objects without the current initialization syntax, we would be fine.
For example pull pTW and pSys out of the SolveSpaceUI class, "memset" them to 0 and then change the SolveSpaceUI constructor to something like this:

SolveSpaceUI() : TW(*pTW), sys(*pSys) {}

But it is not "pretty" from "architecture" point of view.

@whitequark
Copy link
Contributor

But it is not "pretty" from "architecture" point of view.

There is nothing "not pretty" about memsetting them. It is simply wrong (invokes UB), which is why it is not done in the first place. TextWindow contains, right now, non-POD objects like std::shared_ptr, which may not be memset. System does not but it should be possible to use them there.

(If you're observant, you might have noticed that TextWindow::ClearSuper() does exactly that. Which is a bug that needs fixing.)

@ruevs
Copy link
Member

ruevs commented Dec 20, 2019

Thanks for making me think harder (I'm a C guy by day :-) ). And sorry for the "noise" I'm making.

So omitting the {} will cause all POD members of the two instances to contain garbage from the heap, while all "proper objects" (e.g. std::*) will be OK because of their constructors. On the other hand we can not memset the instances, because then only the PODs will be properly set to 0 while the "proper classes"... will? ... be filled with zeroes and that may not be OK?

In this case what is the proper way to initialize these two instances without using {} and wasting a bunch of stack? Would it be to write proper constructors that set all the members to "sane" default values?

@whitequark
Copy link
Contributor

Would it be to write proper constructors that set all the members to "sane" default values?

Here are the exact rules 1 2 3 for our case. They cause no shortage of bugs, and are a good example of why C++ should not be used for new projects. In practice, the worst issues result from the fact that once you add a base class, in C++11, doing new T{} stops performing aggregate-initialization (which does initialize POD members) and starts performing default-initialization (which does not).

In practice adding default member initializers (class { int x = 0; }) would fix this, but it is ugly for other reasons. We should probably systematically add default member initializers to all classes that may eventually become non-POD (not everywhere, since zero-initializing an array of Vectors is something we IIRC profiled and rejected as too expensive), but not just as a workaround for this bug.

@ruevs
Copy link
Member

ruevs commented Dec 20, 2019

Thanks! I read them all, plus these:
https://en.cppreference.com/w/cpp/language/aggregate_initialization
https://en.cppreference.com/w/cpp/language/initializer_list
and - after looking at the definitions of TextWindow and System - came to the conclusion that we should be OK. But since it is a lot of information to parse I decided to debug it as well. As far as I can see everything is properly initialized with #533 in place. And 2519 frames are worth a few hundred words ;-) https://youtu.be/oZcwAU8KcuU
So I think you should consider merging it.

@whitequark
Copy link
Contributor

came to the conclusion that we should be OK

Why? I don't see what guarantees that System is initialized at all.

@ruevs
Copy link
Member

ruevs commented Dec 21, 2019

From 1:

"2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor; "

But all 5 links we discussed make it hard to be sure - that is why I debugged it, and at least VS2019 does call memset on it (as seen in the video), which is no proof, just another data poit :-)

@whitequark
Copy link
Contributor

@ruevs Hm, OK. Then you also should take out the /STACK linker parameter (IIRC it's in two places) in your PR.

ruevs added a commit to ruevs/solvespace that referenced this issue Dec 23, 2019
See:
solvespace#92 (comment)

The problem was first introduced here:
solvespace@dabd578

and later "fixed" here:
solvespace@f324477
by setting the stack size to /STACK:33554432

Solvespace now starts up even with /STACK:554432

The only question now is whether omitting the {} in TextWindow and System constructor calls in the initializer list of the SolveSpaceUI constructor has any adverse effect.
ruevs added a commit to ruevs/solvespace that referenced this issue Dec 23, 2019
See:
solvespace#92 (comment)

The problem was first introduced here:
solvespace@dabd578

and later "fixed" here:
solvespace@f324477
by setting the stack size to /STACK:33554432

Solvespace now starts up even with /STACK:554432

According to this:
https://en.cppreference.com/w/cpp/language/value_initialization
```
"2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor; "
```
So removing the `{}` should leave both the `System` and `TextWindow` class instances properly initialized.
@ruevs
Copy link
Member

ruevs commented Dec 23, 2019

@ruevs
Copy link
Member

ruevs commented Feb 20, 2020

@whitequark are you waiting for something else from me?

@whitequark
Copy link
Contributor

@ruevs Sorry, I missed this comment somehow. Which PR should I merge?

@ruevs
Copy link
Member

ruevs commented Feb 20, 2020

This one:
#533
:-)

whitequark pushed a commit that referenced this issue Feb 24, 2020
See:
#92 (comment)

The problem was first introduced here:
dabd578

and later "fixed" here:
f324477
by setting the stack size to /STACK:33554432

Solvespace now starts up even with /STACK:554432

According to this:
https://en.cppreference.com/w/cpp/language/value_initialization
```
"2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor; "
```
So removing the `{}` should leave both the `System` and `TextWindow` class instances properly initialized.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants