Skip to content

Commit

Permalink
SCons: Disable C++ exception handling
Browse files Browse the repository at this point in the history
Upon investigating the extremely slow MSVC build times in godotengine#80513, I noticed
that while Godot policy is to never use exceptions, we weren't enforcing it
with compiler flags, and thus still included exception handling code and
stack unwinding.

This is wasteful on multiple aspects:

- Binary size: Around 20% binary size reduction with exceptions disabled
  for both MSVC and GCC binaries.
- Compile time:
  * More than 50% build time reduction with MSVC.
  * 10% to 25% build time reduction with GCC + LTO.
- Performance: Possibly, needs to be benchmarked.

Since users may want to re-enable exceptions in their own thirdparty code
or the libraries they compile with Godot, this behavior can be toggled with
the `disable_exceptions` SCons option, which defaults to true.
  • Loading branch information
akien-mga committed Aug 16, 2023
1 parent 90f90cb commit 3907e53
Show file tree
Hide file tree
Showing 9 changed files with 31 additions and 30 deletions.
14 changes: 11 additions & 3 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True))
opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True))
opts.Add(BoolVariable("openxr", "Enable the OpenXR driver", True))
opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loader dynamically", True))
opts.Add(BoolVariable("disable_exceptions", "Force disabling exception handling code", True))
opts.Add("custom_modules", "A list of comma-separated directory paths containing custom modules to build.", "")
opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True))

Expand Down Expand Up @@ -710,6 +711,16 @@ if selected_platform in platform_list:
)
Exit(255)

# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time (GH-80513).
if env["disable_exceptions"]:
if env.msvc:
env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)])
else:
env.Append(CCFLAGS=["-fno-exceptions"])
elif env.msvc:
env.Append(CCFLAGS=["/EHsc"])

# Configure compiler warnings
if env.msvc: # MSVC
if env["warnings"] == "no":
Expand Down Expand Up @@ -739,9 +750,6 @@ if selected_platform in platform_list:
]
)

# Set exception handling model to avoid warnings caused by Windows system headers.
env.Append(CCFLAGS=["/EHsc"])

if env["werror"]:
env.Append(CCFLAGS=["/WX"])
else: # GCC, Clang
Expand Down
9 changes: 9 additions & 0 deletions modules/denoise/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ env_oidn.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds

env_thirdparty = env_oidn.Clone()
env_thirdparty.disable_warnings()

if env["disable_exceptions"]:
# OIDN hard-requires exceptions, so we re-enable them here.
if env.msvc and ("_HAS_EXCEPTIONS", 0) in env_thirdparty["CPPDEFINES"]:
env_thirdparty["CPPDEFINES"].remove(("_HAS_EXCEPTIONS", 0))
env_thirdparty.AppendUnique(CCFLAGS=["/EHsc"])
elif not env.msvc and "-fno-exceptions" in env_thirdparty["CCFLAGS"]:
env_thirdparty["CCFLAGS"].remove("-fno-exceptions")

env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj

Expand Down
5 changes: 3 additions & 2 deletions modules/openxr/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ if env["builtin_openxr"]:

env_thirdparty = env_openxr.Clone()
env_thirdparty.disable_warnings()

env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"])
if env["disable_exceptions"]:
env_thirdparty.AppendUnique(CPPDEFINES=["XRLOADER_DISABLE_EXCEPTION_HANDLING", ("JSON_USE_EXCEPTION", 0)])

if "-fno-exceptions" in env_thirdparty["CXXFLAGS"]:
env_thirdparty["CXXFLAGS"].remove("-fno-exceptions")
env_thirdparty.Append(CPPPATH=[thirdparty_dir + "/src/loader"])

# add in external jsoncpp dependency
Expand Down
4 changes: 0 additions & 4 deletions platform/android/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,6 @@ def configure(env: "Environment"):
env["RANLIB"] = compiler_path + "/llvm-ranlib"
env["AS"] = compiler_path + "/clang"

# Disable exceptions on template builds
if not env.editor_build:
env.Append(CXXFLAGS=["-fno-exceptions"])

env.Append(
CCFLAGS=(
"-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
Expand Down
6 changes: 0 additions & 6 deletions platform/ios/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def get_opts():
),
("IOS_SDK_PATH", "Path to the iOS SDK", ""),
BoolVariable("ios_simulator", "Build for iOS Simulator", False),
BoolVariable("ios_exceptions", "Enable exceptions", False),
("ios_triple", "Triple for ios toolchain", ""),
]

Expand Down Expand Up @@ -138,11 +137,6 @@ def configure(env: "Environment"):
env.Append(ASFLAGS=["-arch", "arm64"])
env.Append(CPPDEFINES=["NEED_LONG_INT"])

if env["ios_exceptions"]:
env.Append(CCFLAGS=["-fexceptions"])
else:
env.Append(CCFLAGS=["-fno-exceptions"])

# Temp fix for ABS/MAX/MIN macros in iOS SDK blocking compilation
env.Append(CCFLAGS=["-Wno-ambiguous-macro"])

Expand Down
9 changes: 3 additions & 6 deletions platform/web/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,9 @@ def configure(env: "Environment"):
if env["use_assertions"]:
env.Append(LINKFLAGS=["-s", "ASSERTIONS=1"])

if env.editor_build:
if env["initial_memory"] < 64:
print('Note: Forcing "initial_memory=64" as it is required for the web editor.')
env["initial_memory"] = 64
else:
env.Append(CPPFLAGS=["-fno-exceptions"])
if env.editor_build and env["initial_memory"] < 64:
print('Note: Forcing "initial_memory=64" as it is required for the web editor.')
env["initial_memory"] = 64

env.Append(LINKFLAGS=["-s", "INITIAL_MEMORY=%sMB" % env["initial_memory"]])

Expand Down
6 changes: 2 additions & 4 deletions tests/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ env_tests = env.Clone()
if env_tests["platform"] == "windows":
env_tests.Append(CPPDEFINES=[("DOCTEST_THREAD_LOCAL", "")])

# Increase number of addressable sections in object files
# due to doctest's heavy use of templates and macros.
if env_tests.msvc:
env_tests.Append(CCFLAGS=["/bigobj"])
if env["disable_exceptions"]:
env_tests.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"])

env_tests.add_source_files(env.tests_sources, "*.cpp")

Expand Down
6 changes: 2 additions & 4 deletions tests/core/io/test_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,7 @@ TEST_CASE("[Image] Modifying pixels of an image") {

for (const Rect2i &rect : rects) {
Ref<Image> img = memnew(Image(img_width, img_height, false, Image::FORMAT_RGBA8));
CHECK_NOTHROW_MESSAGE(
img->fill_rect(rect, Color(1, 1, 1, 1)),
"fill_rect() shouldn't throw for any rect.");
img->fill_rect(rect, Color(1, 1, 1, 1));
for (int y = 0; y < img->get_height(); y++) {
for (int x = 0; x < img->get_width(); x++) {
if (rect.abs().has_point(Point2(x, y))) {
Expand Down Expand Up @@ -317,7 +315,7 @@ TEST_CASE("[Image] Modifying pixels of an image") {
// Pre-multiply Alpha then Convert from RGBA to L8, checking alpha
{
Ref<Image> gray_image = memnew(Image(3, 3, false, Image::FORMAT_RGBA8));
CHECK_NOTHROW_MESSAGE(gray_image->fill_rect(Rect2i(0, 0, 3, 3), Color(1, 1, 1, 0)), "fill_rect() shouldn't throw for any rect.");
gray_image->fill_rect(Rect2i(0, 0, 3, 3), Color(1, 1, 1, 0));
gray_image->set_pixel(1, 1, Color(1, 1, 1, 1));
gray_image->set_pixel(1, 2, Color(0.5, 0.5, 0.5, 0.5));
gray_image->set_pixel(2, 1, Color(0.25, 0.05, 0.5, 1.0));
Expand Down
2 changes: 1 addition & 1 deletion tests/scene/test_viewport.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {

SUBCASE("[Viewport][GuiInputEvent] nullptr as argument doesn't lead to a crash.") {
ERR_PRINT_OFF;
CHECK_NOTHROW(root->push_input(nullptr));
root->push_input(nullptr);
ERR_PRINT_ON;
}

Expand Down

0 comments on commit 3907e53

Please sign in to comment.