Skip to content

Latest commit

 

History

History

examples

LLVM examples on Windows

LLVM project 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

Example hello\ simply prints the message "Hello world !" to the console (sources: hello.c or 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-1.9.8"
[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-1.9.8"
[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-1.9.8"
[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-1.9.8"
[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

JITTutorial1 Example

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).

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-1.9.8"
[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 adding 2>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 Example

JITTutorial1_main\ is our extended version of previous example JITTutorial1:

  • it defines the same function mul_add as in example JITTutorial1,
  • 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 and tut1.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 are 10, 2 and 3; 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 Example

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 Example

JITTutorial2_main\ is our extended version of previous example JITTutorial2:

  • it defines the same function gcd as in example JITTutorial2,
  • it defines a main function with parameters argc and argv 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 in tut2.h resp. tut2.cpp
  • The printf functions are defined/implemented in utils.h resp. utils.cpp
  • The main source file main.cpp is thus more readable (e.g. function emitMain).

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 fields target datalayout and target triple into the generated IR code (see below); that solves the warning "warning: overriding the module target triple" we encountered in example JITTutorial1_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

llvm-hello Example

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 and Cleanups.
  • We write exactly one exit instruction (label end in section Cleanups).
  • 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 or if/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%

mics/October 2023