LLVMExamples.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Tue, 26 Jan 2016 23:45:52 +0000
changeset 52 64d32e0b879a
parent 42 23ae490859cd
child 55 41b3437f1fc7
permissions -rw-r--r--
Moved LLVMModuleTests to package jv:llvm_s/tests

"
    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''

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: / 08-08-2015 / 04:33:22 / 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''

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: / 10-08-2015 / 18:58:10 / 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>"
!

example5_factorial
    "A simple factorial using recursive 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) _:i.
    asm store:(LLVMConstant sintptr: 1) _: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)) _:result.
    asm store:(asm sub:(asm load:i) _:(LLVMConstant sintptr:1)) _: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: / 17-08-2015 / 07:39:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

example6_factorial_with_overflow
    "
     Simple factorial using recursive 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) _:i.
    asm store:(LLVMConstant sintptr: 1) _: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) _:result.
    asm store:(asm sub:(asm load:i) _:(LLVMConstant sintptr:1)) _: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: / 17-08-2015 / 07:40:02 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

example7_factorial_with_debug_info
    "A simple factorial using recursive 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 createTypeFunctionIn: fileDI 
                                parameterTypes: { intptrDI }.
    function := module addFunctionNamed:'factorial' type:functionType.
    functionDI := dib createFunction: 'factorial' in: compilationUnitDI file: fileDI line: 03 type: functionTypeDI function: function 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) _:i.

    asm line: 7 column: 3 scope: functionDI.
    asm store:(LLVMConstant sintptr: 1) _: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)) _:result.
    asm line: 10 column: 3 scope: functionDI.
    asm store:(asm sub:(asm load:i) _:(LLVMConstant sintptr:1)) _: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: / 17-09-2015 / 17:13:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !