Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Compiling

The compiler takes source files and produces one artifact. This chapter covers the options that a normal build needs. The command line reference lists all of them.

Files in, artifact out

plc main.st                      # one file
plc main.st motor.st sensor.st   # several files
plc "src/**/*.st"                # everything below src

Quote a pattern, so that the compiler expands it and not the shell.

Without an option that says otherwise, the compiler links an executable and names it after the first input file, here main.st.out. -o gives it another name. A pattern has no first file, so the name then comes from the pattern text and you get a file called *.st.out. Always give -o with a pattern.

Build a project

Once a build needs more than a file list, put it into plc.json next to the sources:

{
    "name": "plant",
    "files": [ "src/**/*.st" ],
    "compile_type": "Static",
    "output": "plant"
}
plc build

plc build reads plc.json from the current directory, or from the path that you give it. Everything lands in build/: the artifact, and one object file per source file under the path of the source, so src/motor.st becomes build/src/motor.st.o. --build-location moves that directory.

The project file reference describes every key, including the libraries.

Choose what to produce

OptionArtifact
noneAn executable
-cAn object file, not linked
--sharedA shared object
--irLLVM intermediate representation, as text

In a project file, the key compile_type does the same. Use Static, Object, Shared, Relocatable, Bitcode, or IR.

Note

Static and --static mean “link the units into one executable”. They do not produce a fully static binary. The system libraries, the C library included, stay dynamic.

Optimization

plc main.st -O aggressive

The four levels are none, less, default, and aggressive, and they are the levels of LLVM from -O0 to -O3. The default is default. Use none while you debug, because the generated code then follows the source closely. The level changes the machine code only. The text that --ir writes is the same at every level.

Check without producing anything

plc --check "src/**/*.st"
plc check plc.json

Both run the compiler up to validation and report every diagnostic, which is what an editor or a pre-commit hook needs.

Build for another machine

plc main.st --target aarch64-linux-gnu --sysroot /opt/toolchains/aarch64 -o app

--target takes a target triple that LLVM knows, and --sysroot tells the linker where the headers and libraries of that target are. The compiler builds for one target per run, so a build for two machines runs twice.

Speed

The compiler uses every core of the machine. -j 4 limits it to four threads.

Each unit becomes its own module, and the linker joins them. --single-module builds one module for the whole project instead, which is slower but sometimes necessary for a tool that reads the result. A plc build of the project above then writes one object file, build/src/main.st.o, in place of three.

Which compiler built an artifact

Every artifact carries the version of the compiler that produced it. In a linked artifact it sits in the .comment section, next to the lines of the linker and of the C runtime:

readelf -p .comment app
String dump of section '.comment':
  [     1]  Linker: Ubuntu LLD 21.1.8
  [    1b]  plc version 1.1.0-dev (Thu Sep 10 12:08:58 2026 +0200, 6f6e1d7f2db)
  [    5f]  GCC: (Ubuntu 15.2.0-16ubuntu1) 15.2.0

The version, the date, and the commit are the ones of the compiler that you used, and plc --version prints the same three. A deployed binary can therefore be matched to the compiler that built it. A pipeline that needs identical artifacts across compiler updates suppresses the line with --fno-ident.

What’s next

The compiler produced object files. The next chapter joins them with libraries into the final artifact.