Skip to content

Troubleshooting

Look at build/<preset>/pebble-env.txt first. It records exactly which compiler, LLVM, FileCheck, lit, and GoogleTest the build found. ./course doctor checks the same file.

"My pass does nothing" — optnone

At -O0, clang marks every function optnone. The new pass manager then skips your pass on those functions without saying anything. To generate test IR from C, use:

clang -S -emit-llvm -O0 -Xclang -disable-O0-optnone foo.c -o foo.ll

In lit tests, the %clang-ir substitution does exactly this and writes to stdout. To check an existing .ll file, run grep optnone foo.ll. If it's there, delete optnone from the attributes #N = { ... } lines, or regenerate the file. Also run opt -passes=mem2reg first if your pass expects SSA values rather than allocas.

Plugin loading

opt -load-pass-plugin=build/<preset>/lib/PebblePasses.so can fail in several ways. The plugin ends in .so on macOS too, because that is how CMake names MODULE libraries there.

Error Cause and fix
Could not load library ... image not found / cannot open shared object file The path is wrong or the plugin isn't built yet. Run cmake --build --preset <p>. In lit, %plugin pointing at NOT-BUILT-... means the target doesn't exist yet.
Symbol not found: __ZN4llvm... (macOS) or undefined symbol: _ZN4llvm... (Linux) The plugin was loaded into a different LLVM than it was compiled against. Use the opt from the same LLVM 23 install. %opt does this, and so does $(brew --prefix llvm)/bin/opt. Never use an opt from another version on PATH.
unknown pass name 'pebble-xyz' The plugin loaded, but its registerPipelineParsingCallback doesn't match that name. Check the spelling, and check that llvmGetPassPluginInfo is extern "C".
Plugin API version mismatch / wrong API version The plugin was built against headers from another LLVM version. Delete the build directory and reconfigure.
Crash at load time on macOS Usually a toolchain mix: the plugin was built with Apple clang and loaded into Homebrew opt. The macos preset prevents this, so reconfigure from scratch with cmake --preset macos.
CommandLine Error: Option 'xyz' registered more than once LLVM ended up in the process twice: the plugin statically links LLVM libraries and opt also contains them. Plugins made with pebble_add_pass_plugin don't link LLVM themselves. Don't add LLVM libraries to a plugin's DEPS.

On macOS, plugins link with -undefined dynamic_lookup, so LLVM symbols are resolved from the loading opt at runtime. On Linux, shared objects allow undefined symbols by default.

rpath: libLLVM.so.23.1: cannot open shared object file / Library not loaded: @rpath/libLLVM.dylib

The course binaries are built with LLVM_LIBRARY_DIR on their rpath (CMAKE_BUILD_RPATH), so they find libLLVM without any environment variables. If you see this error:

  • You moved or copied the binary out of the build tree, or LLVM was upgraded underneath it. Rebuild with cmake --build --preset <p>, or reconfigure from scratch if LLVM moved.
  • Setting LD_LIBRARY_PATH (Linux) as a stopgap works. On macOS, DYLD_LIBRARY_PATH is stripped by SIP for system binaries, so prefer rebuilding.
  • otool -l <binary> | grep -A2 LC_RPATH (macOS) or readelf -d <binary> | grep RUNPATH (Linux) shows the rpath.

RTTI mismatch: undefined reference to typeinfo for llvm::...

LLVM may be built with or without RTTI (LLVM_ENABLE_RTTI), and your code has to match. The build reads LLVM's setting and adds -fno-rtti when needed through the pebble_settings target. If you see this error:

  • You created a target with plain add_library/add_executable instead of the pebble_add_* helpers. Link it to pebble_settings, or use a helper.
  • Don't use dynamic_cast or typeid on LLVM classes. Use LLVM's isa<>/cast<>/dyn_cast<>.

Exceptions work the same way. LLVM is built with -fno-exceptions, and course code must report errors with std::expected or llvm::Expected, never throw. Unit-test executables keep exceptions enabled because GoogleTest uses them.

FileCheck not found

The lit tests need FileCheck, and not every LLVM package installs it in the same place:

Install FileCheck location
Homebrew $(brew --prefix llvm)/bin/FileCheck
apt.llvm.org /usr/lib/llvm-23/bin/FileCheck (package llvm-23-tools; llvm.sh 23 all installs it)
conda-forge <prefix>/libexec/llvm/FileCheck

CMake searches all of these, and also PATH for FileCheck and FileCheck-23. To point it at a specific copy:

cmake --preset <p> -DPEBBLE_FILECHECK=/path/to/FileCheck

not is optional. If LLVM's not isn't found, %not falls back to lit's built-in not.

Other problems

  • "Apple clang ... detected": this is intentional. See macos.md. Delete build/<preset> and run cmake --preset macos.
  • Undefined libc++ symbols on macOS (for example std::__1::__is_posix_terminal): the headers of Homebrew's libc++ are newer than the system libc++ that gets linked. Reconfigure with -DPEBBLE_USE_HOMEBREW_LIBCXX=ON. That links Homebrew's libc++ and puts it on the rpath.
  • The imported target "..." references the file ".../libPolly.a" but this file does not exist (apt): install libpolly-23-dev, plus libzstd-dev libxml2-dev libedit-dev libcurl4-openssl-dev if CMake names those.
  • find_package(LLVM) picks up LLVM 18 (or another version): the error message tells you which one it found. Pass -DLLVM_DIR=<LLVM 23 prefix>/lib/cmake/llvm.
  • lit/PyYAML version warnings: run uv sync (or cmake --build build/<preset> --target setup-venv) and then reconfigure.
  • course: uv was not found: install uv (macOS brew install uv; Linux curl -LsSf https://astral.sh/uv/install.sh | sh), then uv sync. If you manage Python yourself, COURSE_NO_UV=1 ./course … skips uv (PyYAML and lit must be importable).
  • ./course serve says mkdocs-material is missing: uv sync (the site group is installed by default; uv sync --no-group site leaves it out).
  • Changing PEBBLE_USE_SOLUTION does nothing: it's a cache variable, so pass it again with -D... and then rebuild. The configure output lists every component taken from solutions/.
  • A test prints TODO(chNN): ... and exits with code 99: that's not a bug. It's an unimplemented exercise. Implement it, or use -DPEBBLE_USE_SOLUTION=<switch>.

conflicting types for '__cxa_init_primary_exception' (Linux)

You installed libc++abi-23-dev, probably through llvm.sh 23 all. Its cxxabi.h lives in /usr/lib/llvm-23/include, which is on the include path for LLVM's headers, so it shadows libstdc++'s version. Fix: sudo apt remove libc++abi-23-dev libc++-23-dev. The course builds against libstdc++ on Linux, the same library Ubuntu's LLVM packages use.