packages feed

rail-compiler-editor-0.3.0.0: src/RailCompiler/linked_stack.ll

; Module      : LLVM backend - linked stack implementation/reference counting
; Description : Contains our linked stack implementation and its reference counting
;               routines.
; Maintainers : Tilman Blumenbach et al.
; License     : MIT
;
; These functions are used by our LLVM backend and most of them operate directly on
; the stack. Many also directly crash (in Rail terms: properly exit) the program.


; Types

; A "real" stack element that is stored on the stack -- but only
; indirectly, see %stack_wrapper.
;
; The fields are:
;  * i8 dataType: Type of the data stored in dataPtr.
;    * 0 means string. dataPtr points to a null-terminated string.
;    * 1 means list. Note that there is no seperate type for empty lists,
;      those are represented with type == 1 and dataPtr == null.
;      For non-empty lists, dataPtr points to another stack_wrapper which
;      is the head of the (linked) list.
;    * 2 means lambda.
;  * void *dataPtr: Points to type-specific data. May be null.
;  * i32 refCount: The element's reference count. When this reaches 0, the element
;    is free'd.
%stack_element = type { i8, i8*, i32 }

; A "volatile", non-reused stack element wrapper which is used for the
; actual stack elements, i. e. the stack is a linked list of this type.
;
; This also double as a list element since lists need to be
; able to store all the types that can be pushed onto the stack.
;
; This struct contains two pointers which point (in this order):
;   a) to the real %stack_element in question (first member) and
;   b) to the next %stack_wrapper element in the linked list that makes up
;      the stack (second member).
;
; This is all needed because e. g. Rail variables can be used to
; push the same %stack_element onto the stack multiple times -- while
; the data and the reference count need to be shared by all these
; %stack_element structs, they need to have different nextElement pointers
; (so that the linked list can be a proper linked list). This wrapper type
; solves that issue by introducing yet another layer of abstraction, allowing
; us to reference the same %stack_element multiple times, while keeping a proper
; linked list.
%stack_wrapper = type { %stack_element*, %stack_wrapper* }

; Definitions for lambda push and pop
; The first Element is a pointer to the lambda funtion, the second is a
; pointer to the symbol table for the lambda 
%struct.table = type { i8*, %stack_element*, %struct.table* }
%lambda_element = type {i32 (%struct.table*)**, %struct.table*}

; Global variables
@stack = global %stack_wrapper* null  ; Linked list of stack_element structs.
@stack_size = global i64 0            ; Current number of elements on the stack.


; Constants
@err_type_mismatch = private unnamed_addr constant [16 x i8] c"Type mismatch!\0A\00"
@err_unhandled_type = private unnamed_addr constant [30 x i8] c"Cannot unref unhandled type!\0A\00"
@err_not_bool = private unnamed_addr constant [29 x i8] c"Stack value was not 0 or 1!\0A\00"
@err_empty_list = private unnamed_addr constant [13 x i8] c"Empty list!\0A\00"
@err_num_conv = constant [42 x i8] c"Cannot convert stack element to integer!\0A\00"
@type_string = unnamed_addr constant [7 x i8] c"string\00"
@type_lambda = unnamed_addr constant [7 x i8] c"lambda\00"
@type_list = unnamed_addr constant [5 x i8] c"list\00"
@type_nil = unnamed_addr constant [4 x i8] c"nil\00"

; External declarations

; C standard library variables/functions
declare void @free(i8*)
declare i8* @malloc(i16 zeroext) ; void *malloc(size_t) and size_t is 16 bits long (SIZE_MAX)
declare signext i32 @snprintf(i8*, ...)
declare signext i32 @strtol(i8*, i8**, i32 signext)
declare i8* @xcalloc(i16 zeroext, i16 zeroext)
declare i8* @xstrdup(i8*)

; Own external LLVM variables/functions
@float_to_str = external global [3 x i8]
@int_to_str = external global [3 x i8]

declare void @crash(i1)
declare void @underflow_assert()
declare void @list_unref_elements(%stack_element*)


; Function definitions

; Get number of element on the stack
define i64 @stack_get_size() {
  %sz = load i64* @stack_size
  ret i64 %sz
}

; Creates a new stack_element with a reference count of 1.
define %stack_element* @stack_element_new(i8 %dataType, i8* %dataPtr) {
  ; How many bytes do we need to allocate for a single stack element struct?
  ; getelementptr abuse taken from:
  ; http://nondot.org/sabre/LLVMNotes/SizeOf-OffsetOf-VariableSizedStructs.txt
  %elem_size0 = getelementptr %stack_element* null, i32 1
  %elem_size1 = ptrtoint %stack_element* %elem_size0 to i16

  ; Now we can allocate the memory.
  %element0 = call i8* @xcalloc(i16 1, i16 %elem_size1)
  %element1 = bitcast i8* %element0 to %stack_element*

  ; %element1 now can be treated like an element struct. Yay!
  call void @stack_element_set_type(%stack_element* %element1, i8 %dataType)
  call void @stack_element_set_data(%stack_element* %element1, i8* %dataPtr)

  ; Finally, increment the reference count so that it is exactly 1.
  call void @stack_element_ref(%stack_element* %element1)

  ; That's it!
  ret %stack_element* %element1
}

; Decrement refcount of stack element
; If new refcount is zero, free the stack element and it's data
define void @stack_element_unref(%stack_element* %element) {
  %refcount = call i32(%stack_element*)* @stack_element_get_refcount(%stack_element* %element)
  %refcount_1 = sub i32 %refcount, 1
  %cond = icmp eq i32 %refcount_1, 0
  br i1 %cond, label %free_data, label %update_refcount

free_data:
  %data = call i8* @stack_element_get_data(%stack_element* %element)
  %type = call i8 @stack_element_get_type(%stack_element* %element)
  switch i8 %type, label %unhandled_type
    [
      i8 0, label %free_string
      i8 1, label %free_list
      i8 2, label %free_lambda
    ]

unhandled_type:
  %err_unhandled_type = getelementptr [30 x i8]* @err_unhandled_type, i8 0, i8 0
  call %stack_element* @push_string_cpy(i8* %err_unhandled_type)
  call void @crash(i1 0)
  ret void

free_string:
  call void @free(i8* %data)
  br label %free_element

free_list:
  call void @list_unref_elements(%stack_element* %element)
  br label %free_element

free_lambda:
  call void @free(i8* %data)
  br label %free_element

free_element:
  %mem = bitcast %stack_element* %element to i8*
  call void @free(i8* %mem)
  br label %finished

update_refcount:
  call void(%stack_element*, i32)* @stack_element_set_refcount(%stack_element* %element, i32 %refcount_1)
  br label %finished

finished:
  ret void
}

; free() a stack element and optionally, free the data it contains as well
; (i. e. the memory pointed to by the dataPtr member).
;
; Returns the dataPtr if %free_data == 1 and null otherwise.
;
; TODO: This should probably decrement the reference count and only do something
;       if it is 0 after decrementing.
define i8* @stack_element_free(%stack_element* %element, i1 %free_data) {
top:
  %data = call i8* @stack_element_get_data(%stack_element* %element)
  br i1 %free_data, label %do_free_data, label %free_stack_struct

do_free_data:
  ; TODO: Check type here and free lists (type 1) correctly, i. e. iteratively.
  ;       (Or rather: Decrement the reference count of each list element)
  call void @free(i8* %data)

  br label %free_stack_struct

free_stack_struct:
  %ret = phi i8* [ %data, %top ], [ null, %do_free_data ]

  %mem = bitcast %stack_element* %element to i8*
  call void @free(i8* %mem)

  ret i8* %ret
}

; Increment the reference count of a stack_element.
define void @stack_element_ref(%stack_element* %element) {
  %refCount = call i32 @stack_element_get_refcount(%stack_element* %element)
  %newRefCount = add i32 %refCount, 1
  call void @stack_element_set_refcount(%stack_element* %element, i32 %newRefCount)

  ret void
}

; Get the type of the data in a stack_element struct.
;
; See the definition of %stack_element for a description of
; possible type values.
define i8 @stack_element_get_type(%stack_element* %element) {
  ; dataType is member #0
  %dataType0 = getelementptr %stack_element* %element, i32 0, i32 0
  %dataType1 = load i8* %dataType0
  ret i8 %dataType1
}

; Set the type of the data in a stack_element struct.
;
; See the definition of %stack_element for a description of
; possible type values.
define void @stack_element_set_type(%stack_element* %element, i8 %type) {
  ; dataType is member #0
  %dataTypeDestPtr = getelementptr %stack_element* %element, i32 0, i32 0
  store i8 %type, i8* %dataTypeDestPtr

  ret void
}

; Get the raw, uncasted data pointer of a stack_element struct.
;
; Crashes the program on errors.
define i8* @stack_element_get_data(%stack_element* %element) {
  ; dataPtr is member #1
  %dataPtr0 = getelementptr %stack_element* %element, i32 0, i32 1
  %dataPtr1 = load i8** %dataPtr0
  ret i8* %dataPtr1
}

; Get data from stack as an in integer numeral
define i64 @stack_element_get_int_data(%stack_element* %element) {
  ; Make sure we are operating on a string.
  call void @stack_element_assert_type(%stack_element* %element, i8 0)

  ; get raw data...
  %data = call i8*(%stack_element*)* @stack_element_get_data(%stack_element* %element)

  ; ...and convert it to an integer/long.
  %endptrptr = alloca i8*
  store i8* null, i8** %endptrptr
  %int0 = call i32 @strtol(i8* %data, i8** %endptrptr, i32 10)
  %int1 = sext i32 %int0 to i64

  ; Was everything converted?
  %endptr = load i8** %endptrptr
  %not_null0 = icmp ne i8* %endptr, null
  br i1 %not_null0, label %error_check, label %okay

error_check:
  ; Need to check if the first byte is 0, i. e. if everything
  ; up to the terminating null byte has been converted.
  %first_byte = load i8* %endptr
  %not_null1 = icmp ne i8 %first_byte, 0
  br i1 %not_null1, label %bail_out, label %okay

bail_out:
  ; Error -- crash!
  %msg = getelementptr [42 x i8]* @err_num_conv, i8 0, i8 0
  call %stack_element* @push_string_cpy(i8* %msg)
  call void @crash(i1 0)

  ret i64 -1

okay:
 ret i64 %int1
}

; Set the raw data pointer of a stack_element struct.
define void @stack_element_set_data(%stack_element* %element, i8* %data) {
  ; dataPtr is member #1
  %dataPtr = getelementptr %stack_element* %element, i32 0, i32 1
  store i8* %data, i8** %dataPtr

  ret void
}

; Set the reference count of a stack_element struct.
define void @stack_element_set_refcount(%stack_element* %element, i32 %refCount) {
  ; refCount is member #2
  %refCountDestPtr = getelementptr %stack_element* %element, i32 0, i32 2
  store i32 %refCount, i32* %refCountDestPtr

  ret void
}

; Get the reference count of a stack_element struct.
define i32 @stack_element_get_refcount(%stack_element* %element) {
  ; refCount is member #2
  %refCount0 = getelementptr %stack_element* %element, i32 0, i32 2
  %refCount1 = load i32* %refCount0

  ret i32 %refCount1
}

; Create a new %stack_wrapper struct.
define %stack_wrapper* @stack_wrapper_new(%stack_element* %stackElementPtr, %stack_wrapper* %nextWrapperPtr) {
  ; How many bytes do we need to allocate for a single stack wrapper struct?
  ; getelementptr abuse taken from:
  ; http://nondot.org/sabre/LLVMNotes/SizeOf-OffsetOf-VariableSizedStructs.txt
  %wrap_size0 = getelementptr %stack_wrapper* null, i32 1
  %wrap_size1 = ptrtoint %stack_wrapper* %wrap_size0 to i16

  ; Now we can allocate the memory.
  %wrapper0 = call i8* @xcalloc(i16 1, i16 %wrap_size1)
  %wrapper1 = bitcast i8* %wrapper0 to %stack_wrapper*

  ; %wrapper1 now can be treated like a wrapper struct.
  call void @stack_wrapper_set_element(%stack_wrapper* %wrapper1, %stack_element* %stackElementPtr)
  call void @stack_wrapper_set_next(%stack_wrapper* %wrapper1, %stack_wrapper* %nextWrapperPtr)

  ; That's it!
  ret %stack_wrapper* %wrapper1
}

; Free a %stack_wrapper struct.
;
; Does not free the %stack_element struct hidden by the wrapper.
define void @stack_wrapper_free(%stack_wrapper* %wrapper) {
    %mem = bitcast %stack_wrapper* %wrapper to i8*
    call void @free(i8* %mem)

    ret void
}

; Get the "real" %stack_element behind a stack_wrapper struct.
define %stack_element* @stack_wrapper_get_element(%stack_wrapper* %wrapper) {
    ; stackElementPtr is member #0
    %stackElement0 = getelementptr %stack_wrapper* %wrapper, i32 0, i32 0
    %stackElement1 = load %stack_element** %stackElement0

    ret %stack_element* %stackElement1
}

; Set the "real" %stack_element behind a stack_wrapper struct.
define void @stack_wrapper_set_element(%stack_wrapper* %wrapper, %stack_element* %element) {
    ; stackElementPtr is member #0
    %stackElementPtr = getelementptr %stack_wrapper* %wrapper, i32 0, i32 0
    store %stack_element* %element, %stack_element** %stackElementPtr

    ret void
}

; Get the "next wrapper" pointer of a stack_wrapper struct.
define %stack_wrapper* @stack_wrapper_get_next(%stack_wrapper* %wrapper) {
  ; nextWrapperPtr is member #1
  %nextWrapper0 = getelementptr %stack_wrapper* %wrapper, i32 0, i32 1
  %nextWrapper1 = load %stack_wrapper** %nextWrapper0

  ret %stack_wrapper* %nextWrapper1
}

; Set the "next wrapper" pointer of a stack_wrapper struct.
define void @stack_wrapper_set_next(%stack_wrapper* %wrapper, %stack_wrapper* %next) {
  ; nextWrapperPtr is member #1
  %nextPtr = getelementptr %stack_wrapper* %wrapper, i32 0, i32 1
  store %stack_wrapper* %next, %stack_wrapper** %nextPtr

  ret void
}

; Assert that the data in the stack_element has the passed type.
;
; If the types do not match, crash the program with an appropriate error message.
; This actually checks if the dataType member is equal to %want_type.
define void @stack_element_assert_type(%stack_element* %element, i8 %want_type) {
  %actual_type = call i8 @stack_element_get_type(%stack_element* %element)
  %is_valid = icmp eq i8 %actual_type, %want_type
  br i1 %is_valid, label %valid_type, label %invalid_type

valid_type:
  ; All good. Do nothing.
  ret void

invalid_type:
  ; Bail out!
  %err_type_mismatch = getelementptr [16 x i8]* @err_type_mismatch, i8 0, i8 0
  call %stack_element* @push_string_cpy(i8* %err_type_mismatch)
  call void @crash(i1 0)

  ret void
}

; Assert that the data in the stack_element is a non-empty list.
;
; Crashes the program if the assertion fails.
define void @stack_element_assert_is_non_empty_list(%stack_element* %element) {
  ; Type 1 is list.
  call void @stack_element_assert_type(%stack_element* %element, i8 1)

  %data = call i8* @stack_element_get_data(%stack_element* %element)
  %is_null = icmp eq i8* %data, null
  br i1 %is_null, label %l_empty_list, label %l_non_empty_list

l_empty_list:
  ; Bad. Crash.
  %err_empty_list = getelementptr [13 x i8]* @err_empty_list, i8 0, i8 0
  call %stack_element* @push_string_cpy(i8* %err_empty_list)
  call void @crash(i1 0)

  ret void

l_non_empty_list:
    ; All good.
    ret void
}

; Get (but do not remove) the topmost %stack_wrapper struct.
;
; Crashes the program if the stack is empty.
define %stack_wrapper* @peek_wrapper() {
  ; 1. Make sure we can peek something.
  call void @underflow_assert()

  ; 2. Do the actual peek.
  %stack = load %stack_wrapper** @stack

  ret %stack_wrapper* %stack
}

; Pop a stack_element struct from the stack.
define %stack_element* @pop_struct() {
  ; 1. Pop the stack and get the topmost wrapper.
  %stack = call %stack_wrapper* @peek_wrapper()
  %next = call %stack_wrapper* @stack_wrapper_get_next(%stack_wrapper* %stack)
  store %stack_wrapper* %next, %stack_wrapper** @stack

  ; Get the wrapped stack_element and free the wrapper.
  %element = call %stack_element* @stack_wrapper_get_element(%stack_wrapper* %stack)
  call void @stack_wrapper_free(%stack_wrapper* %stack)

  ; 2. Decrement the stack size.
  %stack_size0 = load i64* @stack_size
  %stack_size1 = sub i64 %stack_size0, 1
  store i64 %stack_size1, i64* @stack_size

  ; 3. That's it!
  ret %stack_element* %element
}

; Push a stack_element struct onto the stack
define void @push_struct(%stack_element* %element) {
  ; 1. Push new element by creating and pushing a new wrapper,
  ;    updating its "next" pointer to the first element of the current stack.
  ;
  ;    NB: Do NOT use peek_wrapper() here since that will crash the program
  ;        if the stack is empty -- making it impossible to push a struct
  ;        onto the empty stack.
  %curr_head = load %stack_wrapper** @stack
  %new_head = call %stack_wrapper* @stack_wrapper_new(%stack_element* %element, %stack_wrapper* %curr_head)
  store %stack_wrapper* %new_head, %stack_wrapper** @stack

  ; 2. Increment stack size.
  %stack_size0 = call i64 @stack_get_size()
  %stack_size1 = add i64 %stack_size0, 1
  store i64 %stack_size1, i64* @stack_size

  ret void
}

; Pop a string from the stack.
;
; Crashes if the type of the topmost element is not "string".
;
; XXX: THIS IS A LEGACY FUNCTION. DO NOT USE IT IN NEW CODE.
;      New code should use proper reference counting.
define i8* @pop_string() {
  ; 1. Pop the stack.
  %stack = call %stack_element* @pop_struct()

  ; 2. Is the type string? If not, crash.
  call void @stack_element_assert_type(%stack_element* %stack, i8 0)

  ; 3. It's a string, everything is fine. Extract the string.
  %buf = call i8* @stack_element_get_data(%stack_element* %stack)

  ; 4. Finally, free the stack element.
  call i8* @stack_element_free(%stack_element* %stack, i1 0)

  ret i8 *%buf
}

; Push a string onto the stack, creating a new stack_element struct
; with a reference count of 1.
;
; The string must already be allocated _ON THE HEAP_.
define %stack_element* @push_string_ptr(i8* %str) {
  ; 1. Create and push a new stack_element.
  ;    NB: Stack size is incremented by push_struct().
  %elem = call %stack_element* @stack_element_new(i8 0, i8* %str)
  call void @push_struct(%stack_element* %elem)

  ; 2. That's it!
  ret %stack_element* %elem
}

; strdup() a string and push it onto the stack, creating a new stack_element struct
; with a reference count of 1.
define %stack_element* @push_string_cpy(i8* %str) {
  %str_copied = call i8* @xstrdup(i8* %str)
  %ret = call %stack_element* @push_string_ptr(i8* %str_copied)

  ret %stack_element* %ret
}

; pops element from stack and converts to integer
; returns the element, in case of error crashes the program
define i64 @pop_int() {
  ; Get top element of stack.
  %top = call %stack_element* @pop_struct()

  ; Now convert it to an int.
  %int = call i64 @stack_element_get_int_data(%stack_element* %top)

  ; Decrement refcount and return
  call void @stack_element_unref(%stack_element* %top)
  ret i64 %int
}

define i64 @pop_bool(){
  ;pop an int element from stack
  %top = call i64 @pop_int()

  ;check whether it is 0 or 1
  switch i64 %top, label %error [ i64 0, label %its_bool
                                  i64 1, label %its_bool ]

error:
  ; Bail out!
  %err_not_bool = getelementptr [29 x i8]* @err_not_bool, i8 0, i8 0
  call %stack_element* @push_string_cpy(i8* %err_not_bool)
  call void @crash(i1 0)
  ret i64 -1

its_bool:
  ret i64 %top
}

define void @push_int(i64 %top_int)
{
  ; allocate memory to store string in
  ; TODO: Make sure this is free()'d at _some_ point during
  ;       program execution.
  %buffer_addr = call i8* @malloc(i16 128)
  %to_str_ptr = getelementptr [3 x i8]* @int_to_str, i64 0, i64 0

  ; convert to string
  call i32(i8*, ...)* @snprintf(
          i8* %buffer_addr, i16 128, i8* %to_str_ptr, i64 %top_int)

  ; push on stack
  call %stack_element* @push_string_ptr(i8* %buffer_addr)

  ret void
}

define void @push_float(double %top_float)
{
  ; allocate memory to store string in
  ; TODO: Make sure this is free()'d at _some_ point during
  ;       program execution.
  %buffer_addr = call i8* @malloc(i16 128)
  %to_str_ptr = getelementptr [3 x i8]* @float_to_str, i64 0, i64 0

  ; convert to string
  call i32(i8*, ...)* @snprintf(
          i8* %buffer_addr, i16 128, i8* %to_str_ptr, double %top_float)

  ; push on stack
  call %stack_element* @push_string_ptr(i8* %buffer_addr)

  ret void
}

; takes a function pointer and a table pointer and pushes both as a struct onto the stack
define void @push_lambda(i32 (%struct.table*)** %function_ptr, %struct.table* %table_ptr)
{
  %l = call i8* @malloc(i16 16)
  %l_ptr = bitcast i8* %l to %lambda_element*
  ; store function pointer
  %l_ptr_func = getelementptr inbounds %lambda_element* %l_ptr, i32 0, i32 0
  store i32 (%struct.table*)** %function_ptr, i32 (%struct.table*)*** %l_ptr_func
  ; store tablre pointer
  %l_ptr_table = getelementptr inbounds %lambda_element* %l_ptr, i32 0, i32 1
  store %struct.table* %table_ptr, %struct.table** %l_ptr_table

  ; push element onto the stack
  %l_elem = call %stack_element* @stack_element_new(i8 2, i8* %l)
  call void @push_struct(%stack_element* %l_elem)
  ret void
}

; pops form stack and checks if stack_element is a lambda
; returns a pointer to the lambda element
define %lambda_element* @pop_lambda()
{
  %l_elem = call %stack_element* @pop_struct()
  
  ; check if struct is a lambda, if not crash
  call void @stack_element_assert_type(%stack_element* %l_elem, i8 2)

  %l_ptr = call i8* @stack_element_get_data(%stack_element* %l_elem)
  %l_ptr_bitcast = bitcast i8* %l_ptr to %lambda_element*
  ret %lambda_element* %l_ptr_bitcast
}

; returns the pointer to the lambda function
define i32 (%struct.table*)* @get_lambda_pointer(%lambda_element* %l){
  %l_func_ptr = getelementptr inbounds %lambda_element* %l, i32 0, i32 0
  %l_func = load i32 (%struct.table*)*** %l_func_ptr
  %l_func2 = load i32 (%struct.table*)** %l_func
  ret i32 (%struct.table*)* %l_func2
}

; returns the pointer to the lambda symbol table
define %struct.table* @get_lambda_table(%lambda_element* %l){
  %l_table_ptr = getelementptr inbounds %lambda_element* %l, i32 0, i32 1
  %l_table = load %struct.table** %l_table_ptr
  ret %struct.table* %l_table
}