Linking
The Pepper compiler turns your program into a native executable in two steps: it compiles your files and the runtime into object files (one per source file; see Output files), and then links them with the system libraries it needs. When you call C functions, linking is where their definitions come from, so this chapter explains what is linked by default and how to add more.
What the compiler links
The compiler uses clang as its link driver, rather than calling the
platform’s linker directly, because clang already knows each platform’s
startup files, C library and SDK locations. It uses the clang from the LLVM
installation the compiler was built with when there is one, and otherwise
the first clang on your PATH.
Every program is linked with:
- Linux: the C library, which clang adds, and the math library (
m), which the Pepper compiler adds (a floating-point**calls itspow), and, when the compiler runs on a glibc older than 2.34,pthreadanddl, where that glibc keeps threads anddlsym(a newer one has them in the C library). - macOS:
libSystem, which contains the C library, plus the SDK and platform-version flags. clang adds all of these, using the Xcode Command Line Tools. - Windows: the Microsoft C runtime (the UCRT,
vcruntimeand the CRT startup library), which clang adds, pluskernel32,user32andshell32, which the Pepper compiler adds. A program that usesasyncalso linkssynchronization, which the runtime names with[Link]for its locks (WaitOnAddress). clang finds the Visual Studio and Windows SDK libraries itself, so you do not need a Developer Command Prompt.
Any library file named lib<name>.a in the runtime directory is linked too.
Anything else must be linked explicitly. On Linux, that includes pthread
on older systems. Android keeps pthread and rt in its C library and has
no library of either name, so the compiler drops -l pthread and -l rt
when linking for Android: one [Link("pthread")] serves both.
Linking a library from the source: [Link]
Attach [Link("name")] (or [Link(lib = "name")]) to an extern
declaration to link the library that defines it. The compiler passes -lname to the linker, just like -l on the
command line. Name the library the way the linker does: m for libm,
gdi32 for gdi32.lib.
# [Link("name")] on an extern links the library that defines it.
# gdi32 is not one of the libraries Windows programs link by default.
[Target(OS(Windows))] [Link("gdi32")] extern ulong GetStockObject(int kind);
# pthread_self is in libpthread on older Linux systems, in libc on newer
# ones, and in libSystem on macOS; linking pthread works on all of them.
[Target(!OS(Windows))] [Link("pthread")] extern ulong pthread_self();
# The math library is separate from the C library on Linux.
[Target(OS(Linux))] [Link("m")] extern double pow(double base, double exponent);
[Target(!OS(Linux))] extern double pow(double base, double exponent);
main(): int {
[Target(OS(Windows))] {
# 5 is NULL_BRUSH, a stock object that always exists.
assert(GetStockObject(5) != 0, "gdi32 was linked");
}
[Target(!OS(Windows))] {
assert(pthread_self() != 0, "pthread was linked");
}
assert(pow(2.0, 10.0) == 1024.0, "the math library was linked");
return 0;
}
- The library is linked only if the extern survives
platform selection. Combining
[Link]with[Target], as above, links a library only on the platforms that need it. - Each library is linked once, however many externs name it.
- The library is linked even when nothing calls the extern.
- The same attribute can also bind the extern to a differently named C
symbol:
[Link(lib = "m", name = "cbrt")](see Binding to a different symbol name).
Putting [Link] in the source keeps the knowledge of which library a function
comes from next to its declaration, so nobody building your program has to
remember a command-line flag.
Linking a library from the command line: -l
The -l option links a library for one build:
pepper build main.pep -l m -o main
Repeat it for more libraries (-l m -l pthread).
A library the linker does not find on its own (one outside the system’s
library directories, such as a Homebrew install on macOS) needs its folder
on the search path. -L dir adds one, like -L for clang:
pepper build main.pep -L /opt/homebrew/opt/llvm@20/lib -o main
Repeat it for more folders. The folders are searched for -l libraries and
[Link] libraries alike, in the order given, before the default ones. On
Windows, clang passes each one to the linker as /LIBPATH:.
Library search directories in the source and the environment
A library that ships with your program, such as a vendored build in a
vendor/ folder, can name its folder in the [Link] attribute with path:
[Link(lib = "probe", path = "vendor/lib")]
extern int probe_version();
A relative path is relative to the folder of the file that declares the
extern, not to where you run the compiler, so the program builds the same
from anywhere. Like the library, the folder is searched only when the extern
survives platform selection, so a [Target] can
give each platform its own folder.
The PEPPER_LIBRARY_PATH environment variable adds folders for every build,
separated like PATH (:, or ; on Windows):
PEPPER_LIBRARY_PATH=/opt/homebrew/lib pepper build main.pep -o main
The linker searches -L folders first, then [Link] paths, then
PEPPER_LIBRARY_PATH, then the runtime’s folder and the system’s.
When linking fails
If a function you declared with extern is not defined by any linked library,
compilation succeeds and the link fails. The compiler prints what the linker
said, which names the missing symbol:
error: Link failed: clang exited with exit status: 1
/usr/bin/ld: .pep/build/main.units/main.o: in function `main':
main:(.text+0x25ba): undefined reference to `no_such_function'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
The object the linker names is the one for the file that made the call, in
.pep/build/<name>.units/. Symbols of your own functions appear as the
compiler spells them for the linker: main and a method such as
Point.length keep their names, and any other name is written _P followed
by the name with _ doubled and other characters as _ and two hex digits,
so a function parse appears as _Pparse and Array<int>.count as
_PArray_3cint_3e.count. That way a Pepper function never collides with a C
library function of the same name, such as exit. extern functions keep
their C names.
To fix it, check the spelling of the extern against the C function (or use
[Link(name = "symbol")]
when the names differ), then add the library that defines it with [Link] or -l.
If the compiler cannot run clang at all, the error says
Link failed: cannot run clang. Install clang (on Linux, from your package
manager) or the Xcode Command Line Tools on macOS.
Warnings from the linker are passed through unchanged, even when the link succeeds.
Output files
With no -o, the executable is named after the first source file, without an
extension, in the directory where you run the compiler. Windows will not run a
file without .exe, so pass -o main.exe there. The object files and other
intermediate files go in .pep/build/ under the current directory, or in the
directory you pass to --build: one object per file of the program in
<name>.units/, and <name>.o, which runs the program’s global
initializers. Unchanged objects come from the build cache in .pep/cache/
(--no-cache skips it).
With --lib static or --lib shared, the output is a library and a C
header instead (see Building the library).