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
}