[core] Use __attribute__((noinline)) on IIFE lambdas to honor attribute

The C++ standard-attribute spelling [[gnu::noinline]] placed between a
lambda's parameter list and body binds to the return type, not the
call operator. GCC 14 silently ignores it and emits -Wattributes
warnings at every chunk site. Switch to GCC's __attribute__((...))
syntax which binds to operator() as intended.

Measured impact on apollo-r-pro-1-eth (esp32-s3, -Os) vs the broken
[[gnu::noinline]] version: setup() frame 160 B -> 32 B, peak stack
304 B -> 176 B (another -42%). Flash grows by 888 B because all 86
chunks now stay as separate functions instead of GCC inlining the
small ones (which it was free to do when the attribute was ignored).

Net vs baseline -Os: peak stack 1264 B -> 176 B (-86%); flash
+388 B (<0.05% of a typical esp32 partition).
This commit is contained in:
J. Nick Koston
2026-04-17 15:06:42 -05:00
parent 6b67224286
commit 29dcf9fc51
2 changed files with 15 additions and 10 deletions
+7 -2
View File
@@ -532,12 +532,17 @@ class Library:
def _wrap_in_noinline_iifes(lines: list[str], max_statements: int) -> list[str]:
"""Wrap ``lines`` in one or more ``[]() [[gnu::noinline]] { ... }();`` IIFEs.
"""Wrap ``lines`` in one or more ``[]() __attribute__((noinline)) {...}();`` IIFEs.
Splits into IIFEs of up to ``max_statements`` entries each. Never splits
inside a brace-balanced block (e.g. the ``{`` / ``}`` pair that
``cg.with_local_variable()`` emits around a scoped local), so an IIFE
may exceed ``max_statements`` when a block straddles the boundary.
GCC note: ``__attribute__((noinline))`` between the lambda parameter
list and body binds to ``operator()``. The C++ standard-attribute
spelling ``[[gnu::noinline]]`` in that position binds to the return
type instead and produces ``-Wattributes`` warnings.
"""
out: list[str] = []
chunk: list[str] = []
@@ -546,7 +551,7 @@ def _wrap_in_noinline_iifes(lines: list[str], max_statements: int) -> list[str]:
def flush() -> None:
if not chunk:
return
out.append("[]() [[gnu::noinline]] {")
out.append("[]() __attribute__((noinline)) {")
out.extend(chunk)
out.append("}();")
chunk.clear()
+8 -8
View File
@@ -877,7 +877,7 @@ def test_wrap_in_noinline_iifes_empty_input() -> None:
def test_wrap_in_noinline_iifes_fewer_lines_than_limit() -> None:
lines = ["a();", "b();", "c();"]
assert core._wrap_in_noinline_iifes(lines, max_statements=10) == [
"[]() [[gnu::noinline]] {",
"[]() __attribute__((noinline)) {",
"a();",
"b();",
"c();",
@@ -896,13 +896,13 @@ def test_wrap_in_noinline_iifes_never_splits_inside_braces() -> None:
# max=2 would naively split after "{" but brace guard keeps block whole.
lines = ["a();", "{", "inner();", "}", "b();"]
assert core._wrap_in_noinline_iifes(lines, max_statements=2) == [
"[]() [[gnu::noinline]] {",
"[]() __attribute__((noinline)) {",
"a();",
"{",
"inner();",
"}",
"}();",
"[]() [[gnu::noinline]] {",
"[]() __attribute__((noinline)) {",
"b();",
"}();",
]
@@ -911,14 +911,14 @@ def test_wrap_in_noinline_iifes_never_splits_inside_braces() -> None:
def test_wrap_in_noinline_iifes_nested_braces() -> None:
lines = ["{", "{", "deep();", "}", "}", "after();"]
assert core._wrap_in_noinline_iifes(lines, max_statements=1) == [
"[]() [[gnu::noinline]] {",
"[]() __attribute__((noinline)) {",
"{",
"{",
"deep();",
"}",
"}",
"}();",
"[]() [[gnu::noinline]] {",
"[]() __attribute__((noinline)) {",
"after();",
"}();",
]
@@ -929,7 +929,7 @@ def test_wrap_in_noinline_iifes_unbalanced_braces_fall_through() -> None:
# a single IIFE with all lines rather than splitting mid-flight.
lines = ["a();", "}", "b();"]
result = core._wrap_in_noinline_iifes(lines, max_statements=1)
assert result[0] == "[]() [[gnu::noinline]] {"
assert result[0] == "[]() __attribute__((noinline)) {"
assert result[-1] == "}();"
assert [line for line in result if line in lines] == lines
@@ -952,7 +952,7 @@ def test_cpp_main_section_component_marker_wraps_in_iife() -> None:
RawStatement("new_wifi();"),
]
out = target.cpp_main_section
assert out.count("[]() [[gnu::noinline]] {") == 2
assert out.count("[]() __attribute__((noinline)) {") == 2
assert out.count("}();") == 2
assert "// === logger ===" in out
assert "// === wifi ===" in out
@@ -966,4 +966,4 @@ def test_cpp_main_section_prefix_statements_stay_outside_iife() -> None:
RawStatement("body();"),
]
out = target.cpp_main_section
assert out.index("prefix();") < out.index("[]() [[gnu::noinline]] {")
assert out.index("prefix();") < out.index("[]() __attribute__((noinline)) {")