Beispiel #1
0
    def __init__(self):
        self.module = Module('hello')
        self._print_int = Function(self.module,
                                   FunctionType(void_type, [int_type]),
                                   name='_print_int')

        self.function = Function(self.module,
                                 FunctionType(int_type, []),
                                 name='main')
        self.block = self.function.append_basic_block('entry')
        self.builder = IRBuilder(self.block)

        self.stack = []
        self.vars = {}
Beispiel #2
0
    def declare_runtime_library(self):
        # Certain functions such as I/O and string handling are often easier
        # to implement in an external C library.  This method should make
        # the LLVM declarations for any runtime functions to be used
        # during code generation.    Please note that runtime function
        # functions are implemented in C in a separate file wabbitrt.c

        self.runtime = {}

        # Declare runtime functions
        functions = [
            ('_print_int', void_type, [int_type]),
            ('_print_float', void_type, [float_type]),
            ('_print_byte', void_type, [int_type]),
            ('_grow', int_type, [int_type]),
            ('_peeki', int_type, [int_type]),
            ('_peekf', float_type, [int_type]),
            ('_peekb', int_type, [int_type]),
            ('_pokei', void_type, [int_type, int_type]),
            ('_pokef', void_type, [int_type, float_type]),
            ('_pokeb', void_type, [int_type, int_type]),
            ]
        for name, rettype, args in functions:
            self.runtime[name] = Function(self.module,
                                          FunctionType(rettype, args),
                                          name=name)
Beispiel #3
0
    def __init__(self):
        # Perform the basic LLVM initialization.  You need the following parts:
        #
        #    1.  A top-level Module object
        #    2.  A Function instance in which to insert code
        #    3.  A Builder instance to generate instructions
        #
        # Note: For project 5, we don't have any user-defined
        # functions so we're just going to emit all LLVM code into a top
        # level function void main() { ... }.   This will get changed later.

        self.module = Module('module')
        self.function = Function(self.module,
                                 FunctionType(void_type, []),
                                 name='main')

        self.block = self.function.append_basic_block('entry')
        self.builder = IRBuilder(self.block)

        # Dictionary that holds all of the global variable/function declarations.
        # Any declaration in the Gone source code is going to get an entry here
        self.vars = {}

        # Dictionary that holds all of the temporary registers created in
        # the intermediate code.
        self.temps = {}

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()
Beispiel #4
0
    def declare_function(self, funcname, argtypes, rettype):
        self.function = Function(self.module,
                                 FunctionType(rettype, argtypes),
                                 name=funcname)

        # Insert a reference in global namespace
        self.globals[funcname] = self.function
Beispiel #5
0
    def declare_runtime_library(self):
        self.runtime = {}

        self.runtime['_print_int'] = Function(self.module,
                                              FunctionType(
                                                  void_type, [int_type]),
                                              name="_print_int")

        self.runtime['_print_float'] = Function(self.module,
                                                FunctionType(
                                                    void_type, [float_type]),
                                                name="_print_float")

        self.runtime['_print_byte'] = Function(self.module,
                                               FunctionType(
                                                   void_type, [byte_type]),
                                               name="_print_byte")
Beispiel #6
0
    def visit_Writeln(self, node: Writeln) -> None:
        """Converts the contents of the command writeln to LLVM ir code and adds the
        print call to the operating system.

        Args:
            node (Writeln): content passed in the command writeln
        """

        self.printf_counter += 1
        output_operation_type = "%s"

        if isinstance(node.content[0], BinaryOperator):
            self._create_instruct("BinaryOperator", is_printf=True)

        writeln_content = self.visit(node.content[0])

        if isinstance(writeln_content, VarSymbol):
            content = self.GLOBAL_MEMORY[writeln_content.name]
        else:
            content = writeln_content

        content_type = type(content.type).__name__

        if self.builder.block.is_terminated:
            self._create_instruct(typ=content_type, is_printf=True)

        if isinstance(content.type, DoubleType):
            output_operation_type = "%f"

        output_format = f"{output_operation_type}\n\0"
        printf_format = Constant(
            ArrayType(IntType(8), len(output_format)),
            bytearray(output_format.encode("utf8")),
        )

        fstr = GlobalVariable(self.module,
                              printf_format.type,
                              name=f"fstr_{self.printf_counter}")
        fstr.linkage = "internal"
        fstr.global_constant = True
        fstr.initializer = printf_format

        writeln_type = FunctionType(IntType(32), [], var_arg=True)
        writeln = Function(
            self.module,
            writeln_type,
            name=f"printf_{content_type}_{self.printf_counter}",
        )

        body = self.builder.alloca(content.type)
        temp_loaded = self.builder.load(body)
        self.builder.store(temp_loaded, body)

        void_pointer_type = IntType(8).as_pointer()
        casted_arg = self.builder.bitcast(fstr, void_pointer_type)
        self.builder.call(writeln, [casted_arg, body])
        self.builder.ret_void()
Beispiel #7
0
    def declare_runtime_library(self):
        # Certain functions such as I/O and string handling are often easier
        # to implement in an external C library.  This method should make
        # the LLVM declarations for any runtime functions to be used
        # during code generation.    Please note that runtime function
        # functions are implemented in C in a separate file gonert.c

        self.runtime = {}

        # Declare printing functions
        self.runtime['_print_int'] = Function(self.module,
                                              FunctionType(void_type, [int_type]),
                                              name="_print_int")

        self.runtime['_print_float'] = Function(self.module,
                                                FunctionType(void_type, [float_type]),
                                                name="_print_float")

        self.runtime['_print_byte'] = Function(self.module,
                                                FunctionType(void_type, [byte_type]),
                                                name="_print_byte")
Beispiel #8
0
    def generate_code(self, ir_function):
        # Given a sequence of SSA intermediate code tuples, generate LLVM
        # instructions using the current builder (self.builder).  Each
        # opcode tuple (opcode, args) is dispatched to a method of the
        # form self.emit_opcode(args)
        # print(ir_function)
        self.function = Function(
            self.module,
            FunctionType(LLVM_TYPE_MAPPING[ir_function.return_type], [
                LLVM_TYPE_MAPPING[ptype] for _, ptype in ir_function.parameters
            ]),
            name=ir_function.name)

        self.block = self.function.append_basic_block('entry')
        self.builder = IRBuilder(self.block)

        # Save the function as a global to be referenced later on another
        # function
        self.globals[ir_function.name] = self.function

        # All local variables are stored here
        self.locals = {}

        self.vars = ChainMap(self.locals, self.globals)

        # Dictionary that holds all of the temporary registers created in
        # the intermediate code.
        self.temps = {}

        # Setup the function parameters
        for n, (pname, ptype) in enumerate(ir_function.parameters):
            self.vars[pname] = self.builder.alloca(LLVM_TYPE_MAPPING[ptype],
                                                   name=pname)
            self.builder.store(self.function.args[n], self.vars[pname])

        # Allocate the return value and return_block
        if ir_function.return_type:
            self.vars['return'] = self.builder.alloca(
                LLVM_TYPE_MAPPING[ir_function.return_type], name='return')
        self.return_block = self.function.append_basic_block('return')

        for opcode, *args in ir_function.code:
            if hasattr(self, 'emit_' + opcode):
                getattr(self, 'emit_' + opcode)(*args)
            else:
                print('Warning: No emit_' + opcode + '() method')

        if not self.block.is_terminated:
            self.builder.branch(self.return_block)

        self.builder.position_at_end(self.return_block)
        self.builder.ret(self.builder.load(self.vars['return'], 'return'))
Beispiel #9
0
    def generate_functions(self, functions):
        type_dict = {
            'int': int_type,
            'float': float_type,
            'byte': byte_type,
            'bool': int_type,
            'void': void_type
        }
        for function in functions:
            # register function
            name = function.name if function.name != 'main' else '_gone_main'
            return_type = type_dict[function.return_type]
            param_types = [type_dict[t] for t in function.param_types]
            function_type = FunctionType(return_type, param_types)
            self.function = Function(self.module, function_type, name=name)
            self.globals[name] = self.function
            self.blocks = {}
            self.block = self.function.append_basic_block('entry')
            self.blocks['entry'] = self.block
            self.builder = IRBuilder(self.block)

            # local scope
            self.vars = self.vars.new_child()
            self.temps = {}
            for n, (param_name, param_type) in enumerate(
                    zip(function.param_names, param_types)):
                var = self.builder.alloca(param_type, name=param_name)
                var.initializer = Constant(param_type, 0)
                self.vars[param_name] = var
                self.builder.store(self.function.args[n],
                                   self.vars[param_name])

            # alloc return var / block
            if function.return_type != 'void':
                self.vars['return'] = self.builder.alloca(return_type,
                                                          name='return')
            self.return_block = self.function.append_basic_block('return')

            # generate instructions
            self.generate_code(function)

            # return
            if not self.block.is_terminated:
                self.builder.branch(self.return_block)

            self.builder.position_at_end(self.return_block)
            if function.return_type != 'void':
                self.builder.ret(
                    self.builder.load(self.vars['return'], 'return'))
            else:
                self.builder.ret_void()
            self.vars = self.vars.parents
Beispiel #10
0
    def _create_instruct(self, typ: str, is_printf: bool = False) -> None:
        """Create a new Function instruction and attach it to a new Basic Block Entry.

        Args:
            typ (str): node type.
            is_printf (bool, optional): Defaults to False.
        """

        if is_printf or typ in ["String", "ArrayType"]:
            self.str_counter += 1
            self.func_name = f"_{typ}.{self.str_counter}"
            func_type = FunctionType(VoidType(), [])
        elif typ == "Boolean":
            self.bool_counter += 1
            self.func_name = f"_{typ}.{self.bool_counter}"
            func_type = FunctionType(IntType(1), [])
        else:
            self.expr_counter += 1
            self.func_name = f"_{typ}_Expr.{self.expr_counter}"
            func_type = FunctionType(DoubleType(), [])

        main_func = Function(self.module, func_type, self.func_name)
        bb_entry = main_func.append_basic_block("entry")
        self.builder = IRBuilder(bb_entry)
Beispiel #11
0
    def emit_FUNCTION(self, f_name, arguments, return_type):
        arguments = eval(arguments)
        argtypes = list(map(lambda z: z[0], arguments))

        f_func = Function(self.module,
                          FunctionType(
                            type_lookup[return_type],
                            argtypes
                          ),
                          name=f_name)
        self.current_func = f_func

        self.block = self.current_func.append_basic_block('entry')

        self.return_block = self.current_func.append_basic_block('return')
        self.return_var = self.builder.alloca(type_lookup[return_type], name='return')
        self.funcs[f_name] = self.current_func
Beispiel #12
0
    def generate_code(self, ir_function):
        self.function = Function(
            self.module,
            FunctionType(LLVM_TYPE_MAPPING[ir_function.return_type], [
                LLVM_TYPE_MAPPING[ptype] for _, ptype in ir_function.parameters
            ]),
            name=ir_function.name)

        self.block = self.function.append_basic_block('entry')
        self.builder = IRBuilder(self.block)

        self.globals[ir_function.name] = self.function

        self.locals = {}

        self.vars = ChainMap(self.locals, self.globals)

        self.temps = {}

        for n, (pname, ptype) in enumerate(ir_function.parameters):
            self.vars[pname] = self.builder.alloca(LLVM_TYPE_MAPPING[ptype],
                                                   name=pname)
            self.builder.store(self.function.args[n], self.vars[pname])

        if ir_function.return_type:
            self.vars['return'] = self.builder.alloca(
                LLVM_TYPE_MAPPING[ir_function.return_type], name='return')
        self.return_block = self.function.append_basic_block('return')

        for opcode, *args in ir_function.code:
            if hasattr(self, 'emit_' + opcode):
                getattr(self, 'emit_' + opcode)(*args)
            else:
                print('Warning: No emit_' + opcode + '() method')

        if not self.block.is_terminated:
            self.builder.branch(self.return_block)

        self.builder.position_at_end(self.return_block)
        self.builder.ret(self.builder.load(self.vars['return'], 'return'))
Beispiel #13
0
    def generate_function(self, name, return_type, arg_types, arg_names,
                          ircode):
        self.function = Function(self.module,
                                 FunctionType(return_type, arg_types),
                                 name=name)

        self.globals[name] = self.function

        self.block = self.function.append_basic_block('entry')
        self.builder = IRBuilder(self.block)

        self.locals = {}
        self.vars = ChainMap(self.locals, self.globals)

        # Have to declare local variables for holding function arguments
        for n, (name, ty) in enumerate(zip(arg_names, arg_types)):
            self.locals[name] = self.builder.alloca(ty, name=name)
            self.builder.store(self.function.args[n], self.locals[name])

        # Dictionary that holds all of the temporary registers created in
        # the intermediate code.
        self.temps = {}

        # Make the exit block for function return
        self.return_block = self.function.append_basic_block('return')
        if return_type != void_type:
            self.return_var = self.builder.alloca(return_type, name='return')

        self.generate_code(ircode)

        if not self.block.is_terminated:
            self.builder.branch(self.return_block)

        self.builder.position_at_end(self.return_block)
        if return_type != void_type:
            self.builder.ret(self.builder.load(self.return_var))
        else:
            self.builder.ret_void()
Beispiel #14
0
# hellollvm.py

from llvmlite.ir import (
    Module, Function, FunctionType, IntType,
    Constant, IRBuilder
    )

mod = Module('hello')
hello_func = Function(mod, 
                      FunctionType(IntType(32), []), 
                      name='hello')
block = hello_func.append_basic_block('entry')
builder = IRBuilder(block)
builder.ret(Constant(IntType(32), 37))

# A user-defined function
from llvmlite.ir import DoubleType

ty_double = DoubleType()
dsquared_func = Function(mod, 
                         FunctionType(ty_double, [ty_double, ty_double]), 
                         name='dsquared')
block = dsquared_func.append_basic_block('entry')
builder = IRBuilder(block)

# Get the function args
x, y = dsquared_func.args

# Compute temporary values for x*x and y*y
xsquared = builder.fmul(x, x)
ysquared = builder.fmul(y, y)
# hello_llvm.py
from llvmlite.ir import (Module, Function, FunctionType, IntType, IRBuilder,
                         Constant)

mod = Module('hello')
int_type = IntType(32)
hello_func = Function(mod, FunctionType(int_type, []), name='hello')
block = hello_func.append_basic_block('entry')
builder = IRBuilder(block)
builder.ret(Constant(IntType(32), 37))
print(mod)
Beispiel #16
0
# A sample of executing an integer < in LLVM

from llvmlite.ir import (Module, Function, FunctionType, IntType, IRBuilder)

mod = Module('example')

# Define a function:
#
#    func lessthan(x int, y int) bool {
#         return x < y;
#    }
#
# Note: The result of boolean operations is a 1-bit integer

func = Function(mod,
                FunctionType(IntType(1),
                             [IntType(32), IntType(32)]),
                name='lessthan')

block = func.append_basic_block("entry")
builder = IRBuilder(block)

# The icmp_signed() instruction is used for all comparisons on
# integers.  For floating point, use fcmp_ordered().  The compare operation
# takes an operator expressed as a string such as '<', '>', '==',
# etc.

result = builder.icmp_signed('<', func.args[0], func.args[1])
builder.ret(result)

# ---- Test Code
Beispiel #17
0
# hellollvm.py

from llvmlite.ir import (Module, Function, FunctionType, IntType, Constant,
                         IRBuilder)
from llvmlite.ir import DoubleType

mod = Module('hello')

hello_func = Function(mod, FunctionType(IntType(32), []), name='hello')
block = hello_func.append_basic_block('entry')
builder = IRBuilder(block)
builder.ret(Constant(IntType(32), 37))

ty_double = DoubleType()
dsquared_func = Function(mod,
                         FunctionType(ty_double, [ty_double, ty_double]),
                         name='dsquared')
block = dsquared_func.append_basic_block('entry')
builder = IRBuilder(block)

# Get the function args
x, y = dsquared_func.args

# Compute temporary values for x*x and y*y
xsquared = builder.fmul(x, x)
ysquared = builder.fmul(y, y)

# Sum the values and return the result
d2 = builder.fadd(xsquared, ysquared)
builder.ret(d2)
Beispiel #18
0
    def external_function_decl(self, tree: Tree):
        nodes = tree.children
        modifier: DeclarationModifier = nodes[0]

        if isinstance(modifier, MetadataToken):
            return tree

        if modifier.static:
            raise PermissionError("External functions cannot be static")

        access_modifier: AccessModifier = modifier.access_modifier
        unsafe: bool = modifier.unsafe
        name = nodes[2].value
        llvm_return_type = self.module.get_definition(nodes[1])

        if isinstance(llvm_return_type, RIALIdentifiedStructType):
            return_type = llvm_return_type.name
        else:
            return_type = map_shortcut_to_type('.'.join(nodes[1]))

        # Builtins can be passed as raw values
        if is_builtin_type(return_type):
            llvm_return_type = llvm_return_type
        # Pointer to first element for still normal arrays
        elif re.match(r".+\[[0-9]+\]$", return_type) is not None:
            llvm_return_type = llvm_return_type.as_pointer()
        elif isinstance(llvm_return_type, RIALIdentifiedStructType):
            llvm_return_type = llvm_return_type.as_pointer()
        else:
            llvm_return_type = llvm_return_type

        # External functions cannot be declared in a struct
        if self.module.current_struct is not None:
            log_fail(
                f"External function {name} cannot be declared inside a class!")
            raise Discard()

        args: List[RIALVariable] = self.visit(nodes[3])

        llvm_args = list()

        # Map RIAL args to llvm arg types
        for arg in args:
            if arg.name.endswith("..."):
                continue

            # Pointer for array and struct types
            if isinstance(arg.llvm_type,
                          RIALIdentifiedStructType) or isinstance(
                              arg.llvm_type, ArrayType):
                llvm_type = arg.llvm_type.as_pointer()
            else:
                llvm_type = arg.llvm_type
            llvm_args.append(llvm_type)

        var_args = len(args) > 0 and args[-1].name.endswith("...")

        # Hasn't been declared previously, redeclare the function type here
        func_type = FunctionType(llvm_return_type, llvm_args, var_args)

        # Create function definition
        func = RIALFunction(self.module, func_type, name, name)
        func.linkage = "external"
        func.calling_convention = "ccc"
        func.definition = FunctionDefinition(return_type,
                                             access_modifier,
                                             args,
                                             unsafe=True)
        func.attributes = self.attributes

        # Update args
        for i, arg in enumerate(func.args):
            arg.name = args[i].name
            args[i].value = arg

        raise Discard()
Beispiel #19
0
    def extension_function_decl(self, tree: Tree):
        nodes = tree.children
        modifier: DeclarationModifier = nodes[0]

        if isinstance(modifier, MetadataToken):
            return tree

        if modifier.static:
            raise PermissionError("Extension functions cannot be static")

        access_modifier: AccessModifier = modifier.access_modifier
        unsafe: bool = modifier.unsafe
        linkage = access_modifier.get_linkage()
        name = nodes[2].value
        llvm_return_type = self.module.get_definition(nodes[1])

        if isinstance(llvm_return_type, RIALIdentifiedStructType):
            return_type = llvm_return_type.name
        else:
            return_type = map_shortcut_to_type('.'.join(nodes[1]))

        # Builtins can be passed as raw values
        if is_builtin_type(return_type):
            llvm_return_type = llvm_return_type
        # Pointer to first element for still normal arrays
        elif re.match(r".+\[[0-9]+\]$", return_type) is not None:
            llvm_return_type = llvm_return_type.as_pointer()
        elif isinstance(llvm_return_type, RIALIdentifiedStructType):
            llvm_return_type = llvm_return_type.as_pointer()
        else:
            llvm_return_type = llvm_return_type

        # Extension functions cannot be declared inside other classes.
        if self.module.current_struct is not None:
            log_fail(
                f"Extension function {name} cannot be declared inside another class!"
            )
            raise Discard()

        args: List[RIALVariable] = list()
        this_type = self.module.get_definition(nodes[3])

        if isinstance(this_type, RIALIdentifiedStructType):
            rial_type = this_type.name
        else:
            rial_type = map_shortcut_to_type('.'.join(nodes[3]))

        args.append(RIALVariable(nodes[4].value, rial_type, this_type, None))

        if isinstance(nodes[5],
                      Tree) and nodes[5].data == "function_decl_args":
            args.extend(self.visit(nodes[5]))
            body_start = 6
        else:
            body_start = 5

        # If the function has the NoMangleAttribute we need to use the normal name
        if 'noduplicate' in self.attributes:
            full_function_name = name
        else:
            full_function_name = self.module.get_unique_function_name(
                name, [arg.rial_type for arg in args])

        llvm_args = list()

        # Map RIAL args to llvm arg types
        for arg in args:
            if arg.name.endswith("..."):
                continue

            # Builtins can be passed as raw values
            if is_builtin_type(arg.rial_type):
                llvm_args.append(arg.llvm_type)
            # Pointer to first element for still normal arrays
            elif re.match(r".+\[[0-9]+\]$", arg.rial_type) is not None:
                llvm_args.append(arg.llvm_type.as_pointer())
            elif isinstance(arg.llvm_type, RIALIdentifiedStructType):
                llvm_args.append(arg.llvm_type.as_pointer())
            else:
                llvm_args.append(arg.llvm_type)

        # Hasn't been declared previously, redeclare the function type here
        func_type = FunctionType(llvm_return_type, llvm_args)

        # Create the actual function in IR
        func = RIALFunction(self.module, func_type, full_function_name, name)
        func.linkage = linkage
        func.calling_convention = self.default_cc
        func.definition = FunctionDefinition(return_type, access_modifier,
                                             args, args[0].rial_type, unsafe)

        # Update args
        for i, arg in enumerate(func.args):
            arg.name = args[i].name
            args[i].value = arg

        # If it has no body we can discard it now.
        if len(nodes) <= body_start:
            with self.module.create_or_enter_function_body(func):
                self.module.builder.ret_void()
            raise Discard()

        token = nodes[2]
        metadata_token = MetadataToken(token.type, token.value)
        metadata_token.metadata["func"] = func
        metadata_token.metadata["body_start"] = body_start
        nodes.remove(token)
        nodes.insert(0, metadata_token)

        return Tree('function_decl', nodes)
Beispiel #20
0
    def function_decl(self, tree: Tree):
        nodes = tree.children
        modifier: DeclarationModifier = nodes[0]

        if isinstance(modifier, MetadataToken):
            return tree

        access_modifier: AccessModifier = modifier.access_modifier
        unsafe: bool = modifier.unsafe
        linkage = access_modifier.get_linkage()
        name = nodes[2].value
        llvm_return_type = self.module.get_definition(nodes[1])

        if isinstance(llvm_return_type, RIALIdentifiedStructType):
            return_type = llvm_return_type.name
        else:
            return_type = map_shortcut_to_type('.'.join(nodes[1]))

        # Builtins can be passed as raw values
        if is_builtin_type(return_type):
            llvm_return_type = llvm_return_type
        # Pointer to first element for still normal arrays
        elif re.match(r".+\[[0-9]+\]$", return_type) is not None:
            llvm_return_type = llvm_return_type.as_pointer()
        elif isinstance(llvm_return_type, RIALIdentifiedStructType):
            llvm_return_type = llvm_return_type.as_pointer()
        else:
            llvm_return_type = llvm_return_type

        args: List[RIALVariable] = list()

        # Add class as implicit parameter
        if self.module.current_struct is not None and not modifier.static:
            args.append(
                RIALVariable("this", self.module.current_struct.name,
                             self.module.current_struct, None))

        args.extend(self.visit(nodes[3]))

        llvm_args = list()

        # Map RIAL args to llvm arg types
        for arg in args:
            if arg.name.endswith("..."):
                continue

            # Pointer for array and struct types
            if isinstance(arg.llvm_type,
                          RIALIdentifiedStructType) or isinstance(
                              arg.llvm_type, ArrayType):
                llvm_type = arg.llvm_type.as_pointer()
            else:
                llvm_type = arg.llvm_type
            llvm_args.append(llvm_type)

        # If the function has the NoMangleAttribute we need to use the normal name
        if 'noduplicate' in self.attributes:
            if modifier.static:
                raise PermissionError(
                    "NoMangle for static function doesn't work")
            full_function_name = name
        else:
            if modifier.static:
                if self.module.current_struct is None:
                    raise PermissionError(
                        "Static functions can only be declared in types")
                full_function_name = self.module.get_unique_function_name(
                    f"{self.module.current_struct.name}::{name}",
                    [arg.rial_type for arg in args])
            else:
                full_function_name = self.module.get_unique_function_name(
                    name, [arg.rial_type for arg in args])

        # Check if main method
        if name.endswith("main") and self.module.name.endswith("main"):
            self.attributes.add('alwaysinline')

            # Check that main method returns either Int32 or void
            if return_type != "Int32" and return_type != "Void":
                log_fail(
                    f"Main method must return an integer status code or void, {return_type} given!"
                )

        # Hasn't been declared previously, redeclare the function type here
        func_type = FunctionType(llvm_return_type, llvm_args, False)

        # Create the actual function in IR
        func = RIALFunction(self.module, func_type, full_function_name, name)
        func.linkage = linkage
        func.calling_convention = self.default_cc
        func.definition = FunctionDefinition(
            return_type, access_modifier, args,
            self.module.current_struct is not None
            and self.module.current_struct or "", unsafe)
        func.attributes = self.attributes

        # Update args
        for i, arg in enumerate(func.args):
            arg.name = args[i].name
            args[i].value = arg

        # If it has no body we do not need to go through it later as it's already declared with this method.
        if not len(nodes) > 4:
            with self.module.create_or_enter_function_body(func):
                self.module.builder.ret_void()
            raise Discard()

        token = nodes[2]
        metadata_token = MetadataToken(token.type, token.value)
        metadata_token.metadata["func"] = func
        metadata_token.metadata["body_start"] = 4
        nodes.remove(token)
        nodes.insert(0, metadata_token)

        return Tree('function_decl', nodes)