LLVMExamples.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Mon, 10 Aug 2015 19:26:29 +0100
changeset 24 7e7ddd55174c
parent 23 0744cd9c0acc
child 28 97013ae2abae
permissions -rw-r--r--
Added support for intrinsics. As access to intrinsics is not exposed by LLVM-C API, a custom C++ library exposing those hase been added - llvm_c_ext. Smalltalk binds to this library in addition to LLVM's. In a future this library will expose whatever C++ API will be needed in scope of this project. However, it's designed to usable standalone, i.e., it contains no Smalltalk-specic code.

"
    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'
	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
    "
    S simple factorial using recursive algorithm.
    No negative argument or overflow 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 := LLVMBuilder new.
    entry := function entry.
    loop  := function addBasicBlockNamed: 'loop'.
    loopBody  := function addBasicBlockNamed: 'loopBody'.
    exit  := function addBasicBlockNamed: 'exit'.

    "/ 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: (function parameterAt: 1) _: result.
    asm br: loop.

    "/ Generate loop that computes the factorial
    "/ 
    "/     while ( i > 1 ) {
    "/       result = result * i;
    "/       i = i - 1.
    "/     }
    "/ 
    "/ 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
    "/ 
    "/     return result;
    "/ 
    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 example3_cond
    "

    "Created: / 10-08-2015 / 09:46:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

example5_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 := LLVMBuilder 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: (function parameterAt: 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: / 10-08-2015 / 17:12:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 10-08-2015 / 18:58:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !