Skip to content

musl targets compile every optimizing profile at -Og, clang included #694

Description

@yspbwx2010

On a *-linux-musl target, --release, --profile dist and any profile with a non-zero opt all compile at -Og. docs/04-mcpp-toml.md §2.9 says release is -O2 and dist is -O3 (main @ 82867ad7, L1260). The build summary still prints Finished release [optimized], so nothing tells the user. The level is set in the file-level $cflags/$cxxflags, which means it applies to the whole graph. In the release build below, every C and C++ entry in compile_commands.json carries -Og. That is 1621 entries, including the C library and the C++ runtime, which are built from source. The other three entries are assembly files, which get no -O.

The cause is this condition in src/build/flags.cppm (main @ 82867ad7, L980-L990). It is the same in v2026.9.21.3 and v2026.9.25.1:

    // Opt level + debug come from the resolved build profile
    // ([profile.<name>] → buildConfig). musl keeps -Og as an ICE workaround
    // unless the profile pins -O0.
    auto& prof = plan.manifest.buildConfig;
    std::string opt_flag = isMuslTc && prof.optLevel != "0"
        ? " -Og"

When the condition first appeared, in v0.0.1 (92f1335), its comment gave the full reason: musl-gcc 15.1.0 hit an ICE in tree-ssa-ccp on libstdc++'s std::format (__write_padded) at -O2. The comment also carried TODO(musl-gcc-upstream): remove once musl-gcc@16+ ships. 53f85a6 (#24) shortened the comment to "musl ICE workaround". 40215eb (#109) kept the condition when profiles arrived and only added the exception for opt = 0. Two things have changed since then:

  • isMuslTc is is_musl_target(plan.toolchain), which reads only the target triple. Now that clang can target musl, clang builds get -Og as well. tree-ssa-ccp is a GCC pass, and the libc++ in openkal-llvm-runtime 0.15.1 has no __write_padded at all, so the original reason does not apply to clang builds.
  • musl-gcc 16.1.0 is in xim-pkgindex. mcpp's target table pins gcc@16.1.0 for both x86_64-linux-musl and aarch64-linux-musl (triple.cppm L450-L451), and mcpp's own mcpp.toml pins gcc@16.1.0-musl for x86_64. The TODO's condition therefore looks met. release.yml builds the Linux artifacts with --target x86_64-linux-musl and --target aarch64-linux-musl. mcpp.toml sets default-profile = "release" under the comment "Without this the released binary would be -O0". If I read this path correctly, both released Linux binaries are compiled at -Og. This comes from reading the code; I have not checked the published binaries.

Reproduction

mcpp 2026.9.21.3, llvm@22.1.8, Linux x86_64, glibc 2.44.

mcpp.toml:

[package]
name    = "optlevel"
version = "0.1.0"

[dependencies]
openkal-llvm-runtime = "0.15.1"

[toolchain]
default = "llvm@22.1.8"

[profile.small]
opt   = "s"
debug = false

src/main.cpp:

import std;

int main() {
    std::println("[{:>8}] [{:<8}] [{:^9}] [{:*^12.3f}]", 42, "left", "mid", 3.14159);
    return 0;
}
$ mcpp build --release --target x86_64-linux-musl
   Resolving toolchain
    Resolved llvm@22.1.8 → x86_64-linux-musl → @mcpp/registry/data/xpkgs/xim-x-llvm/22.1.8/bin/clang++
    Resolved host toolchain for build.mcpp: clang 22.1.8 (x86_64-unknown-linux-gnu)
  build.mcpp compiling
  build.mcpp running
      Target x86_64-linux-musl → x86_64-unknown-linux-musl
             compiler-runtime  compiler-rt    (openkal-llvm-runtime@0.15.1, graph)
             kernel-abi        openkal        (openkal-linux@0.15.0, graph)
             c-abi             musl           (openkal-musl@0.19.1, graph)
             c++-abi           libc++         (openkal-llvm-runtime@0.15.1, graph)
    Inferred sources [src/**/*.{cppm,cpp,cc,c,S,s,asm}]
    Inferred target optlevel (bin from src/main.cpp)
   Compiling optlevel v0.1.0 (.)
      Cached openkal-llvm-runtime v0.15.1 (244 units)
    Finished release [optimized] in 5.14s
$ grep -o -- '"-O[0-9gsz]"' compile_commands.json | sort | uniq -c
   1621 "-Og"
$ grep -o -- '^cflags *= -std=[^ ]* -O[^ ]*' target/x86_64-linux-musl/*/build.ninja
cflags    = -std=c11 -Og
$ ./target/x86_64-linux-musl/*/bin/optlevel
[      42] [left    ] [   mid   ] [***3.142****]

The optimization flags on the compile line of main.cpp, for each profile:

--release          -Og
--profile dist     -Og
--profile small    -Og       (opt = "s")
--profile dev      -O0 -g

All four results are the same on aarch64-linux-musl.

To check whether clang has any trouble at the level mcpp replaces, I added this profile:

[profile.forced-o2]
opt      = 2
debug    = false
cxxflags = ["-O2"]
cflags   = ["-O2"]

A profile's flags only reach the root package's own units. With this profile, main.cpp is compiled with -Og ... -O2, and the later flag wins. main.cpp is the unit that instantiates libc++'s std::format. The runtime units stay at -Og. clang 22.1.8 compiles it, and the program prints the same line as above. I have no musl-gcc here, so I cannot say whether the original ICE still reproduces on 15.1.0 or is gone on 16.1.0.

Suggestion

At minimum, keep the workaround off the clang path. caps.stdlib_id is already in scope in compute_flags, and the header of that function asks for these queries instead of is_gcc(). capabilities_for sets stdlib_id to "libstdc++" exactly when the compiler is GCC, and libstdc++ is also where the ICE happened. The line that picks -B for binutils (L966) already uses it. I have not built or tested this:

--- a/src/build/flags.cppm
+++ b/src/build/flags.cppm
@@ -980,8 +980,10 @@
     // Opt level + debug come from the resolved build profile
-    // ([profile.<name>] → buildConfig). musl keeps -Og as an ICE workaround
-    // unless the profile pins -O0.
+    // ([profile.<name>] → buildConfig). GCC on musl keeps -Og as a workaround
+    // for the musl-gcc 15.1.0 ICE in tree-ssa-ccp on libstdc++'s std::format,
+    // unless the profile pins -O0. Clang never had that ICE.
     auto& prof = plan.manifest.buildConfig;
-    std::string opt_flag = isMuslTc && prof.optLevel != "0"
+    std::string opt_flag = isMuslTc && caps.stdlib_id == "libstdc++"
+                           && prof.optLevel != "0"
         ? " -Og"
         : (isMsvcDialect && prof.optLevel == "0")
         ? " /Od"    // MSVC's no-opt spelling (there is no /O0)

A test could go next to the other compute_flags cases in tests/unit/test_ninja_backend.cpp. This is a sketch that I have not compiled. The GCC half may need more of the target side set up than minimal_plan() provides:

TEST(NinjaBackend, MuslOgWorkaroundIsGccOnly) {
    auto plan = minimal_plan();
    plan.toolchain.targetTriple = "x86_64-linux-musl";
    plan.manifest.buildConfig.optLevel = "2";
    EXPECT_NE(compute_flags(plan).cxx.find(" -Og"), std::string::npos);

    plan.toolchain.compiler   = mcpp::toolchain::CompilerId::Clang;
    plan.toolchain.binaryPath = "/usr/bin/clang++";
    auto cxx = compute_flags(plan).cxx;
    EXPECT_NE(cxx.find(" -O2"), std::string::npos) << cxx;
    EXPECT_EQ(cxx.find(" -Og"), std::string::npos) << cxx;
}

If musl-gcc 16.1.0 no longer hits the ICE, the TODO says the condition can go entirely. The release job's two musl builds would test that. The aarch64 build is a cross build with its own GCC package (aarch64-linux-musl-gcc). The comment above [target.aarch64-linux-musl] in mcpp.toml still says gcc 15.1.0, so it is worth confirming which version that job actually gets. If some form of the workaround has to stay, please document it where users will look: the profile line in docs/04, or a note in the build output. Right now the docs, the profile name and the "[optimized]" label all describe an optimization level that is not the one used to compile.

Environment

mcpp self env (home paths shortened):

MCPP_HOME           = <home>
xlings binary       = <home>/registry/bin/xlings
xlings pinned       = 2026.9.16.1
xlings home         = <home>/registry
config              = <home>/config.toml
build cache         = <home>/build-cache/v1
meta cache          = <home>/cache
default toolchain   = (none — run `mcpp toolchain install gcc 16.1.0`)

Index repos:
  mcpplibs https://github.com/mcpplibs/mcpp-index.git  [artifact]  (default)

Toolchain       = gcc 20260810 (x86_64-pc-linux-gnu)
std module src  = /usr/include/c++/16/bits/std.cc

Linux x86_64, ldd (GNU libc) 2.44.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions