LLVM examples on Windows ⬆
Directory examples\ contains LLVM code examples coming from various websites - mostly from the LLVM project and tested on a Windows machine.
|
In this document we present the following examples in more detail (each of them is a CMake
project 1):
hello
JITTutorial1
JITTutorial1_main
(our extended version ofJITTutorial1
)JITTutorial2
JITTutorial2_main
(our extended version ofJITTutorial2
)llvm-hello
Example hello\
simply prints the message "Hello world !"
to the console. It has the following directory structure :
> tree /a /f . | findstr /v /b [A-Z] | build.bat | CMakeLists.txt | Doxyfile | Makefile \---src \---main +---c | hello.c \---cpp hello.cpp
Our main goal here is to refresh our knowledge of the build tools Clang
, CMake
, GCC
, GNU Make
and MSBuild
(see also the page "Getting Started with the LLVM System using Microsoft Visual Studio" from the LLVM documentation).
Command build.bat
with no argument displays the available options and subcommands:
🔎 Command
build.bat
is a basic batch file consisting of ~500 lines of code [2]; it provides support for the three toolsets MSVC/MSBuild, Clang/GNU Make and GCC/GNU Make.
> build Usage: build { <option> | <subcommand> } Options: -cl use MSVC/MSBuild toolset (default) -clang use Clang/GNU Make toolset instead of MSVC/MSBuild -config:(D|R) use D)ebug or R)elease (default) configuration -debug print commands executed by this script -gcc use GCC/GNU Make toolset instead of MSVC/MSBuild -msvc use MSVC/MSBuild toolset (alias for option -cl) -open display generated HTML documentation ^(subcommand 'doc'^) -timer print total execution time -verbose print progress messages Subcommands: clean delete generated files compile generate executable doc generate HTML documentation with Doxygen dump dump PE/COFF infos for generated executable help print this help message lint analyze C++ source files with Cppcheck run run the generated executable
Command build.bat clean run
produces the following output (default toolset: MSVC/MSBuild):
> build clean run Hello world !
Command build.bat -verbose clean run
also displays progress messages:
> build -verbose clean run Delete directory "build" Toolset: MSVC/MSBuild, Project: hello Configuration: Release, Platform: x64 Generate configuration files into directory "build" Generate executable hello.exe Execute build\Release\hello.exe Hello world !
Command build.bat -debug run
uses the MSVC
/MSBuild
toolset to generate executable hello.exe
> build -debug run [build] Options : _TIMER=0 _TOOLSET=msvc _VERBOSE=0 [build] Subcommands: _CLEAN=0 _COMPILE=1 _DOC=0 _DUMP=0 _LINT=0 _RUN=1 [build] Variables : "DOXYGEN_HOME=C:\opt\doxygen" [build] Variables : "MSYS_HOME=C:\opt\msys64" [build] Toolset: MSVC/MSBuild, Project: hello [build] Configuration: Release, Platform: x64 [build] Current directory is: L:\examples\hello\build [build] cmake.exe -Thost=x64 -A x64 -Wdeprecated .. -- Building for: Visual Studio 16 2019 -- The C compiler identification is MSVC 19.22.27905.0 -- The CXX compiler identification is MSVC 19.22.27905.0 -- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/bin/Hostx64/x64/cl.exe -- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/bin/Hostx64/x64/cl.exe -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done [...] -- Configuring done -- Generating done -- Build files have been written to: L:/examples/hello/build [build] msbuild.exe /nologo /p:Configuration=Release /p:Platform="x64" "hello.sln" [...] Generation was successful 0 Warning(s) 0 Error(s) Elapsed time 00:00:01.02 [build] build\Release\hello.exe Hello world ! [build] _EXITCODE=0
Command build.bat -debug -clang clean run
uses the Clang
/GNU Make
toolset instead of MSVC
/MSBuild
to generate executable hello.exe
:
> build -debug -clang clean run [build] Options : _TIMER=0 _TOOLSET=clang _VERBOSE=0 [build] Subcommands: _CLEAN=1 _COMPILE=1 _DOC=0 _DUMP=0 _LINT=0 _RUN=1 [build] Variables : "DOXYGEN_HOME=C:\opt\doxygen" [build] Variables : "MSYS_HOME=C:\opt\msys64" [build] rmdir /s /q "L:\examples\hello\build" [build] Toolset: Clang/GNU Make, Project: hello [build] Current directory is: L:\examples\hello\build [build] cmake.exe -G "Unix Makefiles" .. -- The C compiler identification is Clang 15.0.6 with GNU-like command-line -- The CXX compiler identification is Clang 15.0.6 with GNU-like command-line -- Check for working C compiler: C:/opt/LLVM-15.0.6/bin/clang.exe -- Check for working C compiler: C:/opt/LLVM-15.0.6/bin/clang.exe -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done [...] -- Configuring done -- Generating done -- Build files have been written to: L:/examples/hello/build [build] make.exe --debug=v [...] Scanning dependencies of target hello [ 75%] Building C object CMakeFiles/hello.dir/src/main/c/hello.c.obj [100%] Linking C executable hello.exe [100%] Built target hello [build] build\hello.exe Hello world ! [build] _EXITCODE=0
Finally, command build.bat -debug -gcc clean run
uses the GCC
/GNU Make
toolset to generate executable hello.exe
:
> build -debug -gcc clean run [build] Options : _TIMER=0 _TOOLSET=gcc _VERBOSE=0 [build] Subcommands: _CLEAN=1 _COMPILE=1 _DOC=0 _DUMP=0 _LINT=0 _RUN=1 [build] Variables : "DOXYGEN_HOME=C:\opt\doxygen" [build] Variables : "MSYS_HOME=C:\opt\msys64" [build] rmdir /s /q "L:\examples\hello\build" [build] Toolset: GCC/GNU Make, Project: hello [build] Current directory is: L:\examples\hello\build [build] cmake.exe -G "Unix Makefiles" .. -- The C compiler identification is GNU 12.2.0 -- The CXX compiler identification is GNU 12.2.0 -- Check for working C compiler: C:/opt/msys64/mingw64/bin/gcc.exe -- Check for working C compiler: C:/opt/msys64/mingw64/bin/gcc.exe -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features [...] -- Configuring done -- Generating done -- Build files have been written to: L:/examples/hello/build [build] make.exe --debug=v [...] Scanning dependencies of target hello [ 75%] Building C object CMakeFiles/hello.dir/src/main/c/hello.c.obj [100%] Linking C executable hello.exe [100%] Built target hello [build] build\hello.exe Hello world ! [build] _EXITCODE=0
Command build.bat -debug lint
performs code analysis with the Cppcheck tool :
> build -debug lint [build] Options : _TIMER=0 _TOOLSET=msvc _VERBOSE=0 [build] Subcommands: _CLEAN=0 _COMPILE=0 _DOC=0 _DUMP=0 _LINT=1 _RUN=0 [build] Variables : "DOXYGEN_HOME=C:\opt\doxygen" [build] Variables : "MSYS_HOME=C:\opt\msys64" [build] "C:\Program Files\Cppcheck\cppcheck.exe" --template=vs --std=c++17 "L:\examples\hello\src" Checking L:\examples\hello\src\main\c\hello.c ... 1/2 files checked 30% done Checking L:\examples\hello\src\main\cpp\hello.cpp ... 2/2 files checked 100% done [build] _EXITCODE=0
Example JITTutorial1\
is based on example "A First Function" (outdated) from the LLVM 2.6 tutorial. It defines a function mul_add
and generates its IR code (source: tut1.cpp
).
This example has the following directory structure :
> tree /a /f . | findstr /v /b [A-Z] | 00download.txt | build.bat | build.sh | CMakeLists.txt | Doxyfile | Makefile \---src tut1.cpp
Command build.bat
with no argument displays the available options and subcommands :
> build Usage: build { <option> | <subcommand> } Options: -cl use MSVC/MSBuild toolset (default) -clang use Clang/GNU Make toolset instead of MSVC/MSBuild -config:(D|R) use D)ebug or R)elease (default) configuration -debug show commands executed by this script -gcc use GCC/GNU Make toolset instead of MSVC/MSBuild -msvc use MSVC/MSBuild toolset (alias for option -cl) -open display generated HTML documentation ^(subcommand 'doc'^) -timer display total elapsed time -verbose display progress messages Subcommands: clean delete generated files compile generate executable doc generate HTML documentation with Doxygen dump dump PE/COFF infos for generated executable help display this help message lint analyze C++ source files with Cppcheck run run executable
Command build.bat clean run
produces the following output:
> build clean run ; ModuleID = 'tut1' source_filename = "tut1" define i32 @mul_add(i32 %x, i32 %y, i32 %z) { entry: %tmp = mul i32 %x, %y %tmp2 = add i32 %tmp, %z ret i32 %tmp2 }
Command build.bat -verbose clean run
also displays progress messages:
> build -verbose clean run Delete directory "build" Toolset: MSVC/MSBuild, Project: JITTutorial1 Configuration: Release, Platform: x64 Current directory: L:\examples\JITTUT~1\build Generate configuration files into directory "build" Generate executable JITTutorial1.exe Execute build\Release\JITTutorial1.exe ; ModuleID = 'tut1' source_filename = "tut1" define i32 @mul_add(i32 %x, i32 %y, i32 %z) { entry: %tmp = mul i32 %x, %y %tmp2 = add i32 %tmp, %z ret i32 %tmp2 }
Finally, command build.bat -debug clean run
displays the commands executed during the build process:
> build -debug clean run [build] Options : _TIMER=0 _TOOLSET=msvc _VERBOSE=0 [build] Subcommands: _CLEAN=1 _COMPILE=1 _DOC=0 _DUMP=0 _LINT=0 _RUN=1 [build] Variables : "DOXYGEN_HOME=C:\opt\doxygen" [build] Variables : "MSYS_HOME=C:\opt\msys64" [build] rmdir /s /q "L:\examples\JITTUT~1\build" [build] Toolset: MSVC/MSBuild, Project: JITTutorial1 [build] Configuration: Debug, Platform: x64 [build] "Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -Thost=x64 -A x64 -Wdeprecated .. -- Building for: Visual Studio 16 2019 -- Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621. -- The CXX compiler identification is MSVC 19.29.30137.0 -- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.21.27702/bin/Hostx64/x64/cl.exe -- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.21.27702/bin/Hostx64/x64/cl.exe -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- [DEBUG] CMAKE_MODULE_PATH=C:/opt/LLVM-15.0.6/lib/cmake/llvm;C:/opt/LLVM-15.0.6/lib/cmake/llvm -- [DEBUG] LLVM_DIR=C:/opt/LLVM-15.0.6/lib/cmake/llvm -- Configuring done -- Generating done -- Build files have been written to: L:/examples/JITTutorial1/build [build] msbuild.exe /nologo /m /p:Configuration=Release /p:Platform="x64" "L:\example\JITTUT~1\build\JITTutorial1.sln" The generation has started 02.08.2019 19:36:32. [...] 0 Warning(s) 0 Error(s) Elapsed time 00:00:03.65 [build] build\Release\JITTutorial1.exe ; ModuleID = 'tut1' source_filename = "tut1" define i32 @mul_add(i32 %x, i32 %y, i32 %z) { entry: %tmp = mul i32 %x, %y %tmp2 = add i32 %tmp, %z ret i32 %tmp2 } [build] _EXITCODE=0
🔎 Output generated by options
-verbose
and-debug
are redirected to stderr and can be discarded by adding2>NUL
, e.g.:> build -debug clean run 2>NUL ; ModuleID = 'tut1' source_filename = "tut1" define i32 @mul_add(i32 %x, i32 %y, i32 %z) { entry: %tmp = mul i32 %x, %y %tmp2 = add i32 %tmp, %z ret i32 %tmp2 }
Finally, one may wonder what's happen if we transform the above IR code into an executable:
> build run > tut1.ll > clang -Wno-override-module -o tut1.exe tut1.ll LINK : fatal error LNK1561: entry point must be defined clang: error: linker command failed with exit code 1561 (use -v to see invocation)
The LLVM linker requires an entry point to successfully generate an executable, ie. we have to add a function main
to our IR code; we present our solution in our extended example JITTutorial1_main
.
JITTutorial1_main\
is our extended version of previous example JITTutorial1
:
- it defines the same function
mul_add
as in exampleJITTutorial1
, - it defines a
main
function (with no parameter as program entry point) and - it defines a
printf
function to print out the result.
🔎 The source code has been reorganized in order to better distinguish between prototype definition and code generation (
main.cpp
,tut1.h
andtut1.cpp
).
Command build.bat clean run
produces the following output:
> build clean run ; ModuleID = 'tut1_main' source_filename = "tut1_main" @.str = private constant [4 x i8] c"%d\0A\00" define i32 @main() { entry: %mul_add = call i32 (i32, i32, i32, ...) @mul_add(i32 10, i32 2, i32 3) %printf = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i32 %mul_add) ret i32 0 } define private i32 @mul_add(i32 %x, i32 %y, i32 %z, ...) { entry: %tmp = mul i32 %x, %y %tmp2 = add i32 %tmp, %z ret i32 %tmp2 } declare i32 @printf(i8*, ...)
🔎 In the above IR code we can recognize the call to function
mul_add
(ie.call .. @mul_add(i32 10, i32 2, i32 3)
); the three arguments are10
,2
and3
; so the result should be(10 * 2) + 3 = 23
.
Now, let's transform the above IR code into an executable:
> build run > build\tut1.ll > clang -Wno-override-module -o build\tut1.exe build\tut1.ll > tut1.exe 23
🔎 We use Clang option
-Wno-override-module
to hide the following warning:> clang -o build\tut1.exe build\tut1.ll warning: overriding the module target triple with x86_64-pc-windows-msvc19.22.27905 [-Woverride-module] 1 warning generated.We will address that warning message in our extended example
JITTutorial2_main
.
JITTutorial2\
is based on example "A More Complicated Function" (outdated) from the LLVM 2.6 tutorial.
It defines a function gcd
(greatest common denominator) and generates its IR code (source: tut2.cpp
).
Command build.bat clean run
produces the following output:
> build clean run ; ModuleID = 'tut2' source_filename = "tut2" define i32 @gcd(i32 %x, i32 %y) { entry: %tmp = icmp eq i32 %x, %y br i1 %tmp, label %return, label %cond_false return: ; preds = %entry ret i32 %x cond_false: ; preds = %entry %tmp2 = icmp ult i32 %x, %y br i1 %tmp2, label %cond_true, label %cond_false1 cond_true: ; preds = %cond_false %tmp3 = sub i32 %y, %x %tmp4 = call i32 @gcd(i32 %x, i32 %tmp3) ret i32 %tmp4 cond_false1: ; preds = %cond_false %tmp5 = sub i32 %x, %y %tmp6 = call i32 @gcd(i32 %tmp5, i32 %y) ret i32 %tmp6 }
JITTutorial2_main\
is our extended version of previous example JITTutorial2
:
- it defines the same function
gcd
as in exampleJITTutorial2
, - it defines a
main
function with parametersargc
andargv
as program entry point and - it defines several
printf
functions to print out both string and integer values. - it defines a
strtol
function to convert string values to integer values.
🔎 The source files are organized as follows:
- The
gcd
function is defined/implemented intut2.h
resp.tut2.cpp
- The
printf
functions are defined/implemented inutils.h
resp.utils.cpp
- The main source file
main.cpp
is thus more readable (e.g. functionemitMain
).For instance include file
utils.h
defines the following functions:void initModule(Module* Mod); CallInst* createPrintInt(Module* Mod, IRBuilder<> Builder, Value* Arg); CallInst* createPrintStr(Module* Mod, IRBuilder<> Builder, const char* ArgStr); CallInst* createPrintStr(Module* Mod, IRBuilder<> Builder, Value* Arg); CallInst* createPrintIntLn(Module* Mod, IRBuilder<> Builder, Value* Arg); CallInst* createPrintStrLn(Module* Mod, IRBuilder<> Builder, const char* ArgStr); CallInst* createPrintStrLn(Module* Mod, IRBuilder<> Builder, Value* Arg); CallInst* createStrToInt(Module* Mod, IRBuilder<> Builder, Value* ArgStr);We use function
initModule(Module* mod)
to include the two fieldstarget datalayout
andtarget triple
into the generated IR code (see below); that solves the warning "warning: overriding the module target triple
" we encountered in exampleJITTutorial1_main
.
Command build.bat clean run
produces the following output:
> build clean run ; ModuleID = 'tut2_main' source_filename = "tut2_main" target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-windows-msvc19.22.27905" @.str = private constant [6 x i8] c"argc=\00" @.str_s = private constant [3 x i8] c"%s\00" @.str_d = private constant [4 x i8] c"%d\0A\00" @.str.1 = private constant [7 x i8] c"argv1=\00" @.str_s.2 = private constant [4 x i8] c"%s\0A\00" @.str.3 = private constant [7 x i8] c"argv2=\00" @.str.4 = private constant [8 x i8] c"result=\00" define private i32 @gcd(i32 %x, i32 %y, ...) { // same as before } define dso_local i32 @main(i32 %argc, i8** %argv) { entry: %0 = alloca i32, align 4 %1 = alloca i32, align 4 %2 = alloca i8**, align 8 store i32 0, i32* %0, align 4 store i32 %argc, i32* %1, align 4 store i8** %argv, i8*** %2, align 8 %3 = load i32, i32* %1 %4 = load i8**, i8*** %2, align 8 %printf = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str_s, i32 0, i32 0), [6 x i8]* @.str) %printf1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str_d, i32 0, i32 0), i32 %3) %5 = getelementptr inbounds i8*, i8** %4, i64 1 %elem_i = load i8*, i8** %5, align 8 %6 = getelementptr inbounds i8*, i8** %4, i64 2 %elem_i2 = load i8*, i8** %6, align 8 %printf3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str_s, i32 0, i32 0), [7 x i8]* @.str.1) %printf4 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str_s.2, i32 0, i32 0), i8* %elem_i) %printf5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str_s, i32 0, i32 0), [7 x i8]* @.str.3) %printf6 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str_s.2, i32 0, i32 0), i8* %elem_i2) %strtol = call i32 (i8*, i8**, i32, ...) @strtol(i8* %elem_i, i8** null, i32 10) %strtol7 = call i32 (i8*, i8**, i32, ...) @strtol(i8* %elem_i2, i8** null, i32 10) %7 = call i32 (i32, i32, ...) @gcd(i32 %strtol, i32 %strtol7) %printf8 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str_s, i32 0, i32 0), [8 x i8]* @.str.4) %printf9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str_d, i32 0, i32 0), i32 %7) ret i32 0 } declare dso_local i32 @printf(i8*, ...) declare dso_local i32 @strtol(i8*, i8**, i32, ...)
Command build.bat clean test
produces the following output (arguments 12
and 4
are hard-coded in subcommand test
):
> build clean test argc=3 argv1=12 argv2=4 result=4
And obviously, we can also run the generated executable directly with two numbers of our choice as arguments:
> build\tut2.exe 210 45 argc=3 argv1=210 argv2=45 result=15
Example llvm-hello\
is based on the simple C++ example from Ildar Musin (February 2016).
It generates a file program.ll
which simply prints message "hello world!"
to the console (source: main.cpp
).
> build clean run Generate file program.ll > type program.ll ; ModuleID = 'top' source_filename = "top" @0 = private unnamed_addr constant [14 x i8] c"hello world!\0A\00", align 1 define i32 @main() { entrypoint: %0 = call i32 @puts(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0)) ret i32 0 } declare i32 @puts(i8*)
Command lli program.ll
prints the message "hello world !"
to the console:
> lli program.ll hello world!
Footnotes ▴
[1] C++ Standards ↩
-
Clang and LLVM are using C++14 since August 14, 2019 (see Bastien's post on the llvm-dev mailing list). We thus specify either C++14 (
GNU Make
) or C++17 (MSBuild
) in our CMake configuration files.
[2] Coding conventions ↩
-
Out batch files (eg.
build.bat
) do obey the following coding conventions: -
- We use at most 80 characters per line. In practice we observe that 80 characters fit well with 4:3 screens and 100 characters fit well with 16:9 screens (Google's convention is 100 characters).
- We organize our code in 4 sections:
Environment setup
,Main
,Subroutines
andCleanups
. - We write exactly one exit instruction (label
end
in sectionCleanups
). - We adopt the following naming conventions for variables: global variables start with character
_
(shell variables defined in the user environment start with a letter) and local variables (e.g. inside subroutines orif/for
constructs) start with__
(two_
characters).
-
@echo off setlocal enabledelayedexpansion @rem ######################################################################### @rem ## Environment setup set _EXITCODE=0 call :env if not %_EXITCODE%==0 goto end call :props if not %_EXITCODE%==0 goto end call :args %* if not %_EXITCODE%==0 goto end @rem ######################################################################### @rem ## Main for %%i in (%_COMMANDS%) do ( call :%%i if not !_EXITCODE!==0 goto end ) goto end @rem ######################################################################### @rem ## Subroutines :env ... (project property) ... goto :eof :props ... (read property file) ... goto :eof :args ... (handle program arguments) ... goto :eof :clean ... goto :eof :lint ... goto :eof :compile ... goto :eof :doc ... goto :eof :run ... goto :eof @rem ######################################################################### @rem ## Cleanups :end ... exit /b %_EXITCODE%