Skip to content

Update compiler from GCC 10 to LLVM 22 - #2519

Draft
SamuelRiedel wants to merge 7 commits into
lowRISC:masterfrom
SamuelRiedel:compiler
Draft

SamuelRiedel wants to merge 7 commits into
lowRISC:masterfrom
SamuelRiedel:compiler

Conversation

@SamuelRiedel

Copy link
Copy Markdown
Contributor

This is a draft PR to evaluate updating the toolchain from GCC to LLVM and to a newer version.

Point CI at the lowRISC LLVM-based toolchain release (Binutils +
Clang RV32IMCB) instead of the 2022-vintage GCC toolchain. The
download/extract logic in install-build-deps.sh is unchanged since
the new release follows the same "<variant>-<version>.tar.xz" naming
scheme, just with the host architecture folded into the variant name.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
The new LLVM toolchain ships no C library or compiler runtime, so
CoreMark's soft-float division routines (previously pulled in via
-lgcc) have nowhere to come from. Rather than keeping GCC around as a
compiler, fetch the last GCC toolchain release purely to expose its
libgcc.a via a new RISCV_LIBGCC_DIR environment variable, which the
CoreMark Makefile will link against directly.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
Switch common.mk's default compiler from riscv32-unknown-elf-gcc to
riscv32-unknown-elf-clang. The old CROSS_COMPILE derivation stripped a
literal "-gcc" suffix off $(CC) to get the objcopy/objdump prefix,
which doesn't work for a "clang"-named binary, so hardcode the prefix
instead (binutils tool names are unchanged between the two
toolchains).

Also make the .bin -> .vmem conversion pad to a 4-byte boundary
unconditionally: srec_cat's vmem format requires word-aligned data,
and the previous GCC-linked binaries happened to always come out
4-byte aligned by chance. Clang-linked binaries are not guaranteed to,
and hello_test already produces one that isn't.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
CoreMark is the one target in this repo that isn't fully freestanding:
it calls libm's modf() in its number formatting code, and its MHz
report needs soft-float division routines. GCC supplied both via its
bundled newlib and libgcc; the new LLVM toolchain ships neither.

Rather than pull in a full newlib/compiler-rt story, compile with
clang and reuse the old GCC toolchain purely as a source of headers,
libm.a and libgcc.a (RISCV_OLD_TOOLCHAIN_DIR / RISCV_LIBGCC_DIR, wired
up in ci/install-build-deps.sh). It's never invoked as a compiler.

Also drop -mtune=sifive-3-series, a GCC-specific tuning flag clang
doesn't understand, and distinguish Clang from GCC in the compiler
version banner (clang also defines __GNUC__ for compatibility).

Verified by building examples/sw/benchmarks/coremark and confirming
modf/__divsf3 resolve against the old toolchain's libm.a/libgcc.a.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
Point RISCV_GCC at riscv32-unknown-elf-clang. riscv-dv's own compile
templates are already GCC/Clang-agnostic (-nostdlib, no GCC-specific
flags), so the random riscv-dv test flow needs no further changes.

The directed tests needed more work:

- The epmp-tests config used -fno-section-anchors, a GCC-only flag
  Clang rejects outright. Drop it.
- The same config's vendored syscalls.c (from lowRISC's riscv-isa-sim
  fork) defines a GCC-only nested function inside sprintf(); Clang
  doesn't support nested functions at all. Hoist it to file scope.
- epmp-tests binaries need libgcc's 64-bit division helpers
  (__udivdi3/__umoddi3, pulled in by syscalls.c's printf) and libc
  headers (string.h etc.), none of which the LLVM toolchain ships.
  compile_test.py now unconditionally appends -isystem/-L/-lgcc/-lm
  flags pointing at the old GCC toolchain for every directed test;
  this is harmless for tests that don't need them since unreferenced
  archive members are never pulled into the link. Drop the resulting
  redundant bare "-lm -lgcc" from the epmp-tests config itself, since
  those flags need the accompanying -L (bare -l flags earlier in the
  command line can't see -L directories added later).

Also document RISCV_TOOLCHAIN and the new RISCV_OLD_TOOLCHAIN_DIR/
RISCV_LIBGCC_DIR variables in the core_ibex README (RISCV_TOOLCHAIN
was previously undocumented despite being required).

Verified by hand-reconstructing compile_test.py's exact compile/link
commands for an epmp-tests binary (confirming the syscalls.c fix and
old-toolchain linking work) and for a plain riscv-tests directed test
(confirming the new unconditional flags don't regress the common case).

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
The external riscv-compliance repo's Makefile.include derives
RISCV_GCC from RISCV_PREFIX with "?=" ($(RISCV_PREFIX)gcc), which
breaks now that the toolchain ships no riscv32-unknown-elf-gcc binary.
Since "?=" only takes effect when the variable isn't already set,
exporting RISCV_GCC directly overrides it without needing to touch
(or fork) that external repo. RISCV_PREFIX still applies to
objdump/nm/readelf, which are unaffected binutils tools.

Verified the override mechanism against riscv-compliance's actual
Makefile.include (pinned rev from ci/vars.env): RISCV_GCC_OPTS there
has no GCC-specific flags, so no other changes are needed.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
Point RISCV_GCC at riscv32-unknown-elf-clang and drop the stale
"build riscv-gnu-toolchain from source and manually apply Bitmanip
patches" alternative, since Bitmanip is natively supported by the new
toolchain. Document the new RISCV_OLD_TOOLCHAIN_DIR/RISCV_LIBGCC_DIR
variables needed for directed tests that still depend on GCC's
newlib/libgcc.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
@kulan-pal

Copy link
Copy Markdown
Contributor

I tried ee87cbb5 with the 20260913-1 LLVM toolchain on the opentitan config. I compiled all directed tests,
and generated and compiled three riscv-dv tests. I compared the directed builds with GCC 10. I did not run any
simulations.

Two assembly problems showed up:

  1. riscv_bitmanip_full_test fails on the carry-less multiply instructions ("instruction requires ... 'Zbc'").
    The compile step still passes -march=rv32imcb, and in this toolchain b does not include Zbc. The same
    program assembles with -march=rv32imc_zba_zbb_zbc_zbs_zbkb_zbkx. Could the compiler ISA use the explicit
    per-config subsets that cosim already uses? The balanced-bitmanip and arithmetic tests compiled.
  2. 13 rv32mi tests (csr, illegal, scall and others) fail with
    error: mtvec_handler changed binding to STB_GLOBAL. riscv_test.h declares the handler weak before the test
    declares it global. The other 931 directed tests compiled, including all epmp tests.

A few smaller things in the source:

  1. Every directed test now needs both old-toolchain variables, though the README says only some tests do.
    Could the check and the extra flags apply only to the tests that need those libraries? The Nix shell does not
    set them either.
  2. The vendoring configuration does not preserve the local syscalls.c change. Could it be preserved as a
    vendor patch or upstreamed?
  3. CoreMark: the variable checks also make make clean fail when they are unset, and the benchmark README does
    not mention them. Clang also reports -funroll-all-loops and -falign-jumps=4 as unsupported, so the
    published CoreMark numbers may be worth marking as GCC results.

Separately, the private CI log for bmbalanced shows the workflow itself still exporting the GCC compiler path.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants