"
Copyright (C) 2015-now Jan Vrany
This code is not an open-source (yet). You may use this code
for your own experiments and projects, given that:
* all modification to the code will be sent to the
original author for inclusion in future releases
* this is not used in any commercial software
This license is provisional and may (will) change in
a future.
"
"{ Package: 'jv:llvm_s' }"
"{ NameSpace: Smalltalk }"
TestCase subclass:#LLVMExamples
instanceVariableNames:''
classVariableNames:''
poolDictionaries:'LLVMIntPredicate LLVMDWARFEncoding LLVMDWARFLamguage'
category:'LLVM-S-Core-Examples'
!
!LLVMExamples class methodsFor:'documentation'!
copyright
"
Copyright (C) 2015-now Jan Vrany
This code is not an open-source (yet). You may use this code
for your own experiments and projects, given that:
* all modification to the code will be sent to the
original author for inclusion in future releases
* this is not used in any commercial software
This license is provisional and may (will) change in
a future.
"
! !
!LLVMExamples class methodsFor:'accessing'!
isTestSelector:aSelector
^ (super isTestSelector:aSelector) or:[ aSelector startsWith: 'example' ]
"Created: / 03-08-2015 / 09:25:22 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!LLVMExamples methodsFor:'examples'!
example1_sum
"
Creates a simple function taking two arguments (as intptr_t) and returning
their sum (as intptr_t).
"
| module functionType function asm jit externalFunction |
"/ 1) create a module to which the function would belong. A module is
"/ a set of functions and globals that are compiled at once by the MCJIT. Once
"/ module is compiled, no more methods or clobals can be added.
module := LLVMModule newWithName: testSelector.
"/ 2) Define a function within the module with type (intptr_t, intptr_t) -> intptr_t
functionType := LLVMType function: { LLVMType intptr . LLVMType intptr } returning: LLVMType intptr.
function := module addFunctionNamed: 'sum' type: functionType.
"/ 2) To emit LLVM IR, get an IR builder for the function.
"/ The LLVMFunction>>builder returns a builder on
"/ function's entry point (basic block, strictly speaking)
asm := function builder.
asm ret: (asm add:(function parameterAt:1) _:(function parameterAt:2)).
"/ Now, the module should look like
self assert: (module dumpString =
'; ModuleID = ''example1_sum''
source_filename = "example1_sum"
define i64 @sum(i64, i64) {
entry:
%2 = add i64 %0, %1
ret i64 %2
}
').
"/ 3) To compile a function (strictly speaking, whole module) at runtime,
"/ create a jit object (called ExecutionEngine in LLVM)
jit := LLVMExecutionEngine newForModule: module.
"/ 4) Finally, obtain a reference to the function. This cause
"/ the module to be closed and compiled to machine code.
externalFunction := jit externalOfFunction: function.
self assert: (externalFunction callWith: 3 with: 4) == 7
"
LLVMExamples example1_sum
"
"Created: / 17-07-2015 / 11:47:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 11-08-2016 / 09:06:09 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example2_function_call
"
Creates a module with two functions, @sum and @sum_caller. The latter
calls the former.
"
| module calleeFunctionType calleeFunction callerFunctionType callerFunction asm jit externalFunction |
module := LLVMModule newWithName: testSelector.
calleeFunctionType := LLVMType function: { LLVMType intptr . LLVMType intptr } returning: LLVMType intptr.
calleeFunction := module addFunctionNamed: 'sum' type: calleeFunctionType.
asm := calleeFunction builder.
asm ret: (asm add:(calleeFunction parameterAt:1) _:(calleeFunction parameterAt:2)).
callerFunctionType := LLVMType function: { LLVMType intptr . LLVMType intptr } returning: LLVMType intptr.
callerFunction := module addFunctionNamed: 'sum_caller' type: callerFunctionType.
asm := callerFunction builder.
asm ret: (asm call: calleeFunction _: { (callerFunction parameterAt: 1) . (callerFunction parameterAt: 2) }).
self assert: (module dumpString =
'; ModuleID = ''example2_function_call''
source_filename = "example2_function_call"
define i64 @sum(i64, i64) {
entry:
%2 = add i64 %0, %1
ret i64 %2
}
define i64 @sum_caller(i64, i64) {
entry:
%2 = call i64 @sum(i64 %0, i64 %1)
ret i64 %2
}
').
jit := LLVMExecutionEngine newForModule: module.
externalFunction := jit externalOfFunction: callerFunction.
self assert: (externalFunction callWith: 3 with: 4) == 7.
"
LLVMExamples example2_function_call
"
"Created: / 17-07-2015 / 12:45:08 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 11-08-2016 / 09:06:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example3_hello_world
"
Creates a function @main() which calls @printf() to print
a famous 'Hello World!!' message on stdout
"
| module printfFunctionType printfFunction
helloWorldString
mainFunctionType mainFunction
asm jit externalFunction |
module := LLVMModule newWithName: testSelector.
printfFunctionType := LLVMType function: { LLVMType char pointer } varargs: true returning: LLVMType int32.
printfFunction := module addFunctionNamed: 'printf' type: printfFunctionType.
helloWorldString := module addGlobalNamed: '.str' value: (LLVMConstant string: 'Hello World!!' , Character lf).
mainFunctionType := LLVMType function: #() returning: LLVMType int32.
mainFunction := module addFunctionNamed: 'main' type: mainFunctionType.
asm := mainFunction builder.
asm call: printfFunction _: { asm gep: helloWorldString at: #(0 0) }.
asm ret: (LLVMConstant sint32: 0).
"
module writeBitcodeToFile: '/tmp/main.bc'
"
jit := LLVMExecutionEngine newForModule: module.
externalFunction := jit externalOfFunction: mainFunction.
externalFunction call.
Stdout flush
"
LLVMExamples example3_hello_world
"
"Created: / 03-08-2015 / 10:28:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 10-08-2015 / 18:58:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example4_cond
"
Creates a function @even(intpr) which returns
1 if the parameter is even or 0 if not.
"
| module
functionType function asm isOdd ifOdd ifEven jit externalFunction |
module := LLVMModule newWithName: testSelector.
functionType := LLVMType function: { LLVMType intptr } returning: LLVMType intptr.
function := module addFunctionNamed: 'even' type: functionType.
asm := function builder.
isOdd := asm icmp: (asm and: (function parameterAt: 1) _: (LLVMConstant uintptr: 1)) _: (LLVMConstant uintptr: 1) cond: LLVMIntEQ.
ifOdd := function addBasicBlock.
ifOdd builder
ret: (LLVMConstant uintptr: 0).
ifEven := function addBasicBlock.
ifEven builder
ret: (LLVMConstant uintptr: 1).
asm if: isOdd then: ifOdd else: ifEven.
jit := LLVMExecutionEngine newForModule: module.
externalFunction := jit externalOfFunction: function.
self assert: (externalFunction callWith: 10) == 1.
self assert: (externalFunction callWith: 11) == 0.
"
LLVMExamples example3_cond
"
"Created: / 08-08-2015 / 04:16:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example4_cond_easy
"
Creates a function @even(intpr) which returns
1 if the parameter is even or 0 if not.
This demonstrate usage of blocks with
LLVMIRBuilder>>if:then:else: which is easier to use
than fiddling about basic blocks manually.
"
| module
functionType function asm isOdd jit externalFunction |
module := LLVMModule newWithName: testSelector.
functionType := LLVMType function: { LLVMType intptr } returning: LLVMType intptr.
function := module addFunctionNamed: 'even' type: functionType.
asm := function builder.
isOdd := asm icmp: (asm and: (function parameterAt: 1) _: (LLVMConstant uintptr: 1)) _: (LLVMConstant uintptr: 1) cond: LLVMIntEQ.
asm if: isOdd then: [
asm ret: (LLVMConstant uintptr: 0).
] else: [
asm ret: (LLVMConstant uintptr: 1).
].
jit := LLVMExecutionEngine newForModule: module.
externalFunction := jit externalOfFunction: function.
self assert: (externalFunction callWith: 10) == 1.
self assert: (externalFunction callWith: 11) == 0.
"
LLVMExamples example3_cond
"
"Created: / 22-04-2016 / 10:22:05 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example5_factorial
"A simple factorial using iterative algorithm.
No overflow or negative value checks"
| module functionType function asm
"Variables" result i
"Blocks" entry loop loopBody exit jit externalFunction |
module := LLVMModule newWithName:testSelector.
functionType := LLVMType function:{ LLVMType intptr } returning:LLVMType intptr.
function := module addFunctionNamed:'factorial' type:functionType.
asm := LLVMIRBuilder new.
entry := function entry.
loop := function addBasicBlockNamed:'loop'.
loopBody := function addBasicBlockNamed:'loopBody'.
exit := function addBasicBlockNamed:'exit'.
"/ Generate function setup
"/
"/ 03 function f(v) {
"/ 04 var result;
"/ 05 var i;
"/ 06 result = 0;
"/ 07 i := v;
asm block:entry.
result := asm alloca:LLVMType intptr as:'result'.
i := asm alloca:LLVMType intptr as:'i'.
asm store:(function parameterAt:1) at:i.
asm store:(LLVMConstant sintptr: 1) at:result.
asm br:loop.
"/ Generate loop that computes the factorial
"/
"/ 08 while ( i > 1 ) {
"/ 09 result = result * i;
"/ 10 i = i - 1.
"/ 11 }
"/
"/ Note, that unlike 'traditional' assemblers, there's no
"/ fall-through instruction, so we have to introduce a block
"/ loop's body which will become a target for conditional's
"/ then-branch.
asm block:loop.
asm
if:(asm
icmp:(asm load:i)
_:(LLVMConstant sintptr:1)
cond:LLVMIntSGT)
then:loopBody
else:exit.
asm block:loopBody.
asm store:(asm mul:(asm load:result) _:(asm load:i)) at:result.
asm store:(asm sub:(asm load:i) _:(LLVMConstant sintptr:1)) at:i.
asm br:loop.
"/ Generate return from function
"/
"/ 12 return result;
"/ 13 }
"/
asm block:exit.
asm ret:(asm load:result).
jit := LLVMExecutionEngine newForModule:module.
externalFunction := jit externalOfFunction:function.
self assert:(externalFunction callWith:5) == 120.
self assert:(externalFunction callWith:1) == 1.
"
LLVMExamples run: example5_factorial_with_debug_info
"
"Created: / 10-08-2015 / 09:46:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 11-08-2016 / 09:06:54 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example5_factorial_phi
"A simple factorial using recursive algorithm.
No overflow or negative value checks.
Demonstrates usage of phi node"
| module functionType function asm
"Values" paramIsOneVal paramIsNotOneVal
"Blocks" entry paramIsOne paramIsNotOne exit jit externalFunction |
module := LLVMModule newWithName:testSelector.
functionType := LLVMType function:{ LLVMType intptr } returning:LLVMType intptr.
function := module addFunctionNamed:'factorial' type:functionType.
asm := LLVMIRBuilder new.
entry := function entry.
paramIsOne := function addBasicBlockNamed:'paramIsOne'.
paramIsNotOne := function addBasicBlockNamed:'paramIsNotOne'.
exit := function addBasicBlockNamed:'exit'.
"/ Generate function setup
"/
"/ 03 function f(v) {
"/ 04 return (v == 1
"/ 05 ? 1
"/ 07 : v * f(v - 1)
"/ 08 )
asm block:entry.
asm if: (asm icmp:(function parameterAt: 1) _:(LLVMConstant sintptr:1) cond:LLVMIntEQ)
then: paramIsOne
else: paramIsNotOne.
asm block: paramIsOne.
paramIsOneVal := LLVMConstant sintptr: 1.
asm br: exit.
asm block: paramIsNotOne.
paramIsNotOneVal := asm mul: (asm call: function _: { asm sub: (function parameterAt: 1) _: (LLVMConstant sintptr: 1) })
_: (function parameterAt: 1).
asm br: exit.
asm block: exit.
asm ret: (asm phi: { paramIsOne -> paramIsOneVal . paramIsNotOneVal }).
jit := LLVMExecutionEngine newForModule:module.
externalFunction := jit externalOfFunction:function.
self assert:(externalFunction callWith:5) == 120.
self assert:(externalFunction callWith:1) == 1.
"
LLVMExamples run: example5_factorial_with_debug_info
"
"Created: / 21-04-2016 / 22:14:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 22-04-2016 / 09:10:21 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example6_factorial_with_overflow
"
Simple factorial using iterative algorithm.
This one checks for overflow, if overflow happens,
return -1"
| module functionType function asm "Variables"
result i "Blocks"
entry loop loopBody1 loopBody2 exit overflow smulWithOverflow smulWithOverflowValue jit externalFunction |
module := LLVMModule newWithName:testSelector.
functionType := LLVMType function:{
LLVMType intptr
}
returning:LLVMType intptr.
function := module addFunctionNamed:'factorial' type:functionType.
asm := LLVMIRBuilder new.
entry := function entry.
loop := function addBasicBlockNamed:'loop'.
loopBody1 := function addBasicBlockNamed:'loopBody1'.
loopBody2 := function addBasicBlockNamed:'loopBody2'.
exit := function addBasicBlockNamed:'exit'.
overflow := function addBasicBlockNamed:'overflow'.
"/ Generate function setup
"/
"/ function f(v) {
"/ var result;
"/ var i;
"/ result = 0;
"/ i := v;
asm block:entry.
result := asm alloca:LLVMType intptr as:'result'.
i := asm alloca:LLVMType intptr as:'i'.
asm store:(function parameterAt:1) at:i.
asm store:(LLVMConstant sintptr: 1) at:result.
asm br:loop.
"/ Generate loop that computes the factorial
"/
"/ while ( i > 1 ) {
"/ result = result * i if overflow goto overflow;
"/ i = i - 1.
"/ }
"/
"/ First, get the llvm.smul.with.overflow intrinsic:
smulWithOverflow := module getIntrinsicNamed:'llvm.smul.with.overflow'
types:{
LLVMType intptr
}.
"/ Now code the loop
asm block:loop.
asm
if:(asm
icmp:(asm load:i)
_:(LLVMConstant sintptr:1)
cond:LLVMIntSGT)
then:loopBody1
else:exit.
asm block:loopBody1.
smulWithOverflowValue := asm call:smulWithOverflow
_:{
(asm load:result).
(asm load:i)
}.
asm
if:(asm extractvalue:smulWithOverflowValue at:1)
then:overflow
else:loopBody2.
asm block:loopBody2.
asm store:(asm extractvalue:smulWithOverflowValue at:0) at:result.
asm store:(asm sub:(asm load:i) _:(LLVMConstant sintptr:1)) at:i.
asm br:loop.
"/ Generate return from function
"/
"/ return result;
"/
asm block:exit.
asm ret:(asm load:result).
"/ Generate overflow handler
"/ overflow:
"/ return -1;
"/
asm block:overflow.
asm ret:(LLVMConstant sintptr:-1).
jit := LLVMExecutionEngine newForModule:module.
externalFunction := jit externalOfFunction:function.
self assert:(externalFunction callWith:5) == 120.
self assert:(externalFunction callWith:1) == 1.
self assert:(externalFunction callWith:120) == -1.
"sly LLVMExamples example3_cond"
"Created: / 14-08-2015 / 06:46:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 10-08-2016 / 23:01:35 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example7_factorial_with_debug_info
"A simple factorial using iterative algorithm
with debug info attached.
"
| module functionType function asm
"Variables" result i
"Blocks" entry loop loopBody exit
"Debug Info Metadata" dib fileDI compilationUnitDI functionTypeDI functionDI intptrDI resultDI iDI
jit externalFunction |
module := LLVMModule newWithName:testSelector.
dib := module debugInfoBuilder.
fileDI := dib createFile: Filename currentDirectory / 'factorial.lang'.
compilationUnitDI := dib createCompilationUnit: Filename currentDirectory / 'factorial.lang' language: LLVM_DW_LANG_lo_user + 10 producer: self class name.
functionType := LLVMType function:{ LLVMType intptr } returning:LLVMType intptr.
intptrDI := dib createTypeScalar: 'intptr' type: LLVMType intptr encoding: LLVM_DW_ATE_signed.
functionTypeDI := dib createTypeFunction: { intptrDI }.
function := module addFunctionNamed:'factorial' type:functionType.
functionDI := dib createFunction: 'factorial' in: compilationUnitDI file: fileDI line: 03 type: functionTypeDI local: false definition: true optimized: false.
dib createParameterVariable: 'v' in: functionDI file: fileDI line: 03 type: intptrDI flags: 0 index: 1.
asm := LLVMIRBuilder new.
entry := function entry.
loop := function addBasicBlockNamed:'loop'.
loopBody := function addBasicBlockNamed:'loopBody'.
exit := function addBasicBlockNamed:'exit'.
"/ Generate function setup
"/
"/ 03 function f(v) {
"/ 04 var result;
"/ 05 var i;
"/ 06 result = 0;
"/ 07 i := v;
asm block:entry.
asm line: 4 column: 3 scope: functionDI.
result := asm alloca:LLVMType intptr as:'result'.
resultDI := dib createAutomaticVariable: 'result' in: functionDI file: fileDI line: 4 type: intptrDI.
dib insertDeclare: result variable: resultDI expression: dib createExpression location: asm location atEndOf: entry.
asm line: 5 column: 3 scope: functionDI.
i := asm alloca:LLVMType intptr as:'i'.
iDI := dib createAutomaticVariable: 'i' in: functionDI file: fileDI line: 4 type: intptrDI.
dib insertDeclare: i variable: iDI expression: dib createExpression location: asm location atEndOf: entry.
asm line: 6 column: 3 scope: functionDI.
asm store:(function parameterAt:1) at:i.
asm line: 7 column: 3 scope: functionDI.
asm store:(LLVMConstant sintptr: 1) at:result.
asm br:loop.
"/ Generate loop that computes the factorial
"/
"/ 08 while ( i > 1 ) {
"/ 09 result = result * i;
"/ 10 i = i - 1.
"/ 11 }
"/
"/ Note, that unlike 'traditional' assemblers, there's no
"/ fall-through instruction, so we have to introduce a block
"/ loop's body which will become a target for conditional's
"/ then-branch.
asm block:loop.
asm line: 8 column: 3 scope: functionDI.
asm
if:(asm
icmp:(asm load:i)
_:(LLVMConstant sintptr:1)
cond:LLVMIntSGT)
then:loopBody
else:exit.
asm block:loopBody.
asm line: 9 column: 3 scope: functionDI.
asm store:(asm mul:(asm load:result) _:(asm load:i)) at:result.
asm line: 10 column: 3 scope: functionDI.
asm store:(asm sub:(asm load:i) _:(LLVMConstant sintptr:1)) at:i.
asm line: 11 column: 3 scope: functionDI.
asm br:loop.
"/ Generate return from function
"/
"/ 12 return result;
"/ 13 }
"/
asm block:exit.
asm line: 12 column: 3 scope: functionDI.
asm ret:(asm load:result).
dib finish.
self halt.
"
To generate stand-alone executable do the following:
1) Execute following to write LLVM IR to /tmp/factorial.ll
module writeBitcodeToFile: '/tmp/factorial.bc'
2) Compile it into an object file
llc-3.8 --filetype=obj factorial.bc
3) Write a simple main.c to call factorial():
'/tmp/main.c' asFilename writingFileDo:[:f | f nextPutAll: 'int main(int argc, char **argv) { factorial(5); exit(0); }' ].
4) Compile main.c and link it with factorial.o
gcc -o main main.c factorial.o
To actually debug it, first write the pseudo-code for the factorial:
'/tmp/factorial.lang' asFilename writingFileDo:[ :f|
f nextPutLine: ''.
f nextPutLine: ''.
f nextPutLine: 'function factorial(v) {'.
f nextPutLine: ' var result;'.
f nextPutLine: ' var i;'.
f nextPutLine: ' result = 0;'.
f nextPutLine: ' i := v;'.
f nextPutLine: ' while ( i > 1 ) {'.
f nextPutLine: ' result = result * i;'.
f nextPutLine: ' i = i - 1.'.
f nextPutLine: ' }'.
f nextPutLine: ' return result;'.
f nextPutLine: '}'.
]
Then debug it using gdb:
gdb main
"
jit := LLVMExecutionEngine newForModule:module.
externalFunction := jit externalOfFunction:function.
self assert:(externalFunction callWith:5) == 120.
self assert:(externalFunction callWith:1) == 1.
"
LLVMExamples example7_factorial_with_debug_info"
"Created: / 14-08-2015 / 06:46:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 06-02-2016 / 15:28:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified (comment): / 21-04-2016 / 21:37:35 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example8_data_at_fixed_address
"
Creates a function that returns an int value at fixed address
(known at compilation time)
"
| module
functionType function asm glb val |
module := LLVMModule newWithName: testSelector.
functionType := LLVMType function: { } returning: LLVMType int32.
function := module addFunctionNamed: 'test' type: functionType.
asm := function builder.
glb := asm int: (LLVMConstant uintptr: 1234) toPtr: LLVMType int32 pointer.
val := asm load: (asm gep: glb at: #(1)).
asm ret: val.
self halt.
"
LLVMExamples new example8_data_at_fixed_address
"
"Created: / 11-02-2016 / 20:15:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 12-02-2016 / 11:11:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
example9_memset
"
Creates a function that clears alloca()ted structure,
Shows use of llvm.memset intrinsic.
"
| module functionType function asm pointTy point |
module := LLVMModule newWithName: testSelector ? 'test'.
pointTy := LLVMType named: 'point_t'.
pointTy elementTypes: { LLVMType int32 . LLVMType int32 }.
functionType := LLVMType function: { } returning: LLVMType void.
function := module addFunctionNamed: 'test' type: functionType.
asm := function builder.
point := asm alloca: pointTy as: 'point'.
asm memset: point _: (LLVMConstant uint8: 0) _: (LLVMConstant uint64: (LLVMType int32 sizeInBytes * 2)) _: 1 _: false.
asm ret.
module verify.
self halt.
"
LLVMExamples new example9_memset
"
"Created: / 06-07-2016 / 00:01:45 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 08-07-2016 / 15:44:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!LLVMExamples class methodsFor:'documentation'!
version_HG
^ '$Changeset: <not expanded> $'
! !