Exemplo n.º 1
0
def get_bool(scope_stack, builder: ir.IRBuilder, value):
    value = get_value(scope_stack, builder, value)
    if isinstance(value, ir.Constant):
        return bool_type(1 if value.constant else 0)
    if isinstance(value.type, ir.DoubleType):
        return builder.fcmp_ordered('!=', value, value.type(0))
    if isinstance(value.type, ir.IntType) and value.type.width != 1:
        return builder.icmp_signed('!=', value, value.type(0))
    return value
Exemplo n.º 2
0
def opt_cmp(builder: ir.IRBuilder, cmpop, lhs, rhs):
    if isinstance(lhs, ir.Constant):
        if type(lhs.type) != type(rhs.type):
            lhs = rhs.type(get_type_rank(rhs.type)[1](lhs.constant))
        if isinstance(rhs.type, ir.DoubleType):
            return builder.fcmp_ordered(cmpop, lhs, rhs)
        elif isinstance(rhs.type, ir.IntType):
            return builder.icmp_signed(cmpop, lhs, rhs)
    elif isinstance(rhs, ir.Constant):
        if type(rhs.type) != type(lhs.type):
            rhs = lhs.type(get_type_rank(lhs.type)[1](rhs.constant))
        if isinstance(lhs.type, ir.DoubleType):
            return builder.fcmp_ordered(cmpop, lhs, rhs)
        elif isinstance(lhs.type, ir.IntType):
            return builder.icmp_signed(cmpop, lhs, rhs)
    else:
        lhs, rhs = type_check(builder, lhs, rhs)
        if isinstance(lhs.type, ir.DoubleType) and isinstance(
                rhs.type, ir.DoubleType):
            return builder.fcmp_ordered(cmpop, lhs, rhs)
        elif isinstance(lhs.type, ir.IntType) and isinstance(
                rhs.type, ir.IntType):
            return builder.icmp_signed(cmpop, lhs, rhs)
Exemplo n.º 3
0
class GenerateLLVM(object):

    def __init__(self, name='module'):
        # 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(name)
        # self.function = Function(self.module,
        #                         FunctionType(void_type, []),
        #                         name='main')

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

        # 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 = {}
        self.globals = {}
        self.locals = {}

        # Dictionary that holds all of the temporary variables created in
        # the intermediate code.   For example, if you had an expression
        # like this:
        #
        #      a = b + c*d
        #
        # The corresponding intermediate code might look like this:
        #
        #      ('load_int', 'b', 'int_1')
        #      ('load_int', 'c', 'int_2')
        #      ('load_int', 'd', 'int_3')
        #      ('mul_int', 'int_2','int_3','int_4')
        #      ('add_int', 'int_1','int_4','int_5')
        #      ('store_int', 'int_5', 'a')
        #
        # The self.temp dictionary below is used to map names such as 'int_1',
        # 'int_2' to their corresponding LLVM values.  Essentially, every time
        # you make anything in LLVM, it gets stored here.
        self.temps = {}

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()

        self.last_branch = None

    def start_function(self, name, rettypename, parmtypenames):
        rettype = typemap[rettypename]
        parmtypes = [typemap[pname] for pname in parmtypenames]
        # Type.function(rettype, parmtypes, False)
        func_type = FunctionType(rettype, parmtypes)

        # Create the function for which we're generating code
        # Function.new(self.module, func_type, name)
        self.function = Function(self.module, func_type, name=name)

        # Make the builder and entry block
        self.block = self.function.append_basic_block("entry")
        self.builder = IRBuilder(self.block)

        # Make the exit block
        self.exit_block = self.function.append_basic_block("exit")

        # Clear the local vars and temps
        self.locals = {}
        self.temps = {}

        # Make the return variable
        if rettype is not void_type:
            self.locals['return'] = self.builder.alloca(rettype, name="return")

        # Put an entry in the globals
        self.globals[name] = self.function

    def new_basic_block(self, name=''):
        self.builder = IRBuilder(self.block.instructions)
        return self.function.append_basic_block(name)

    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_bool'] = Function(self.module,
                                               FunctionType(
                                                   void_type, [int_type]),
                                               name="_print_bool")

    def generate_code(self, ircode):
        # 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)

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

        # Add a return statement.  Note, at this point, we don't really have
        # user-defined functions so this is a bit of hack--it may be removed
        # later.
        # self.builder.ret_void()

    def terminate(self):
        # Add a return statement. This connects the last block to the exit
        # block.
        # The return statement is then emitted
        if self.last_branch != self.block:
            self.builder.branch(self.exit_block)
        self.builder.position_at_end(self.exit_block)

        if 'return' in self.locals:
            self.builder.ret(self.builder.load(self.locals['return']))
        else:
            self.builder.ret_void()

    def add_block(self, name):
        # Add a new block to the existing function
        return self.function.append_basic_block(name)

    def set_block(self, block):
        # Sets the current block for adding more code
        self.block = block
        self.builder.position_at_end(block)

    def cbranch(self, testvar, true_block, false_block):
        self.builder.cbranch(self.temps[testvar], true_block, false_block)

    def branch(self, next_block):
        if self.last_branch != self.block:
            self.builder.branch(next_block)
        self.last_branch = self.block
    # ----------------------------------------------------------------------
    # Opcode implementation.   You must implement the opcodes.  A few
    # sample opcodes have been given to get you started.
    # ----------------------------------------------------------------------

    # Creation of literal values.  Simply define as LLVM constants.
    def emit_literal_int(self, value, target):
        self.temps[target] = Constant(int_type, value)

    def emit_literal_float(self, value, target):
        self.temps[target] = Constant(float_type, value)

    def emit_literal_bool(self, value, target):
        self.temps[target] = Constant(bool_type, value)

    # def emit_literal_string(self, value, target):
    #     self.temps[target] = Constant(string_type, value)

    # Allocation of variables.  Declare as global variables and set to
    # a sensible initial value.
    def emit_alloc_int(self, name):
        var = self.builder.alloca(int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.locals[name] = var

    def emit_alloc_float(self, name):
        var = self.builder.alloca(float_type, name=name)
        var.initializer = Constant(float_type, 0)
        self.locals[name] = var

    def emit_alloc_bool(self, name):
        var = self.builder.alloca(bool_type, name=name)
        var.initializer = Constant(bool_type, 0)
        self.locals[name] = var

    def emit_global_int(self, name):
        var = GlobalVariable(self.module, int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.globals[name] = var

    def emit_global_float(self, name):
        var = GlobalVariable(self.module, float_type, name=name)
        var.initializer = Constant(float_type, 0)
        self.globals[name] = var

    def emit_global_bool(self, name):
        var = GlobalVariable(self.module, bool_type, name=name)
        var.initializer = Constant(bool_type, 0)
        self.globals[name] = var

    # def emit_alloc_string(self, name):
    #     var = GlobalVariable(self.module, string_type, name=name)
    #     var.initializer = Constant(string_type, "")
    #     self.vars[name] = var

    # Load/store instructions for variables.  Load needs to pull a
    # value from a global variable and store in a temporary. Store
    # goes in the opposite direction.
    def lookup_var(self, name):
        if name in self.locals:
            return self.locals[name]
        else:
            return self.globals[name]

    def emit_load_int(self, name, target):
        # print('LOADINT %s, %s' % (name, target))
        # print('GLOBALS %s' % self.globals)
        # print('LOCALS %s' % self.locals)
        self.temps[target] = self.builder.load(self.lookup_var(name), target)

    def emit_load_float(self, name, target):
        self.temps[target] = self.builder.load(self.lookup_var(name), target)

    def emit_load_bool(self, name, target):
        self.temps[target] = self.builder.load(self.lookup_var(name), target)

    def emit_store_int(self, source, target):
        self.builder.store(self.temps[source], self.lookup_var(target))

    def emit_store_float(self, source, target):
        self.builder.store(self.temps[source], self.lookup_var(target))

    def emit_store_bool(self, source, target):
        self.builder.store(self.temps[source], self.lookup_var(target))

    # Binary + operator
    def emit_add_int(self, left, right, target):
        self.temps[target] = self.builder.add(
            self.temps[left], self.temps[right], target)

    def emit_add_float(self, left, right, target):
        self.temps[target] = self.builder.fadd(
            self.temps[left], self.temps[right], target)

    # Binary - operator
    def emit_sub_int(self, left, right, target):
        self.temps[target] = self.builder.sub(
            self.temps[left], self.temps[right], target)

    def emit_sub_float(self, left, right, target):
        self.temps[target] = self.builder.fsub(
            self.temps[left], self.temps[right], target)

    # Binary * operator
    def emit_mul_int(self, left, right, target):
        self.temps[target] = self.builder.mul(
            self.temps[left], self.temps[right], target)

    def emit_mul_float(self, left, right, target):
        self.temps[target] = self.builder.fmul(
            self.temps[left], self.temps[right], target)

    # Binary / operator
    def emit_div_int(self, left, right, target):
        self.temps[target] = self.builder.sdiv(
            self.temps[left], self.temps[right], target)

    def emit_div_float(self, left, right, target):
        self.temps[target] = self.builder.fdiv(
            self.temps[left], self.temps[right], target)

    # Unary + operator
    def emit_uadd_int(self, source, target):
        self.temps[target] = self.builder.add(
            Constant(int_type, 0),
            self.temps[source],
            target)

    def emit_uadd_float(self, source, target):
        self.temps[target] = self.builder.fadd(
            Constant(float_type, 0.0),
            self.temps[source],
            target)

    # Unary - operator
    def emit_usub_int(self, source, target):
        self.temps[target] = self.builder.sub(
            Constant(int_type, 0),
            self.temps[source],
            target)

    def emit_usub_float(self, source, target):
        self.temps[target] = self.builder.fsub(
            Constant(float_type, 0.0),
            self.temps[source],
            target)

    # Binary < operator
    def emit_lt_int(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '<', self.temps[left], self.temps[right], target)

    def emit_lt_float(self, left, right, target):
        self.temps[target] = self.builder.fcmp_ordered(
            '<', self.temps[left], self.temps[right], target)

    # Binary <= operator
    def emit_le_int(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '<=', self.temps[left], self.temps[right], target)

    def emit_le_float(self, left, right, target):
        self.temps[target] = self.builder.fcmp_ordered(
            '<=', self.temps[left], self.temps[right], target)

    # Binary > operator
    def emit_gt_int(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '>', self.temps[left], self.temps[right], target)

    def emit_gt_float(self, left, right, target):
        self.temps[target] = self.builder.fcmp_ordered(
            '>', self.temps[left], self.temps[right], target)

    # Binary >= operator
    def emit_ge_int(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '>=', self.temps[left], self.temps[right], target)

    def emit_ge_float(self, left, right, target):
        self.temps[target] = self.builder.fcmp_ordered(
            '>=', self.temps[left], self.temps[right], target)

    # Binary == operator
    def emit_eq_int(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '==', self.temps[left], self.temps[right], target)

    def emit_eq_bool(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '==', self.temps[left], self.temps[right], target)

    def emit_eq_float(self, left, right, target):
        self.temps[target] = self.builder.fcmp_ordered(
            '==', self.temps[left], self.temps[right], target)

    # Binary != operator
    def emit_ne_int(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '!=', self.temps[left], self.temps[right], target)

    def emit_ne_bool(self, left, right, target):
        self.temps[target] = self.builder.icmp_signed(
            '!=', self.temps[left], self.temps[right], target)

    def emit_ne_float(self, left, right, target):
        self.temps[target] = self.builder.fcmp_ordered(
            '!=', self.temps[left], self.temps[right], target)

    # Binary && operator
    def emit_and_bool(self, left, right, target):
        self.temps[target] = self.builder.and_(
            self.temps[left], self.temps[right], target)

    # Binary || operator
    def emit_or_bool(self, left, right, target):
        self.temps[target] = self.builder.or_(
            self.temps[left], self.temps[right], target)

    # Unary ! operator
    def emit_not_bool(self, source, target):
        self.temps[target] = self.builder.icmp_signed(
            '==', self.temps[source], Constant(bool_type, 0), target)
    # Print statements

    def emit_print_int(self, source):
        self.builder.call(self.runtime['_print_int'], [self.temps[source]])

    def emit_print_float(self, source):
        self.builder.call(self.runtime['_print_float'], [self.temps[source]])

    def emit_print_bool(self, source):
        self.builder.call(self.runtime['_print_bool'], [
                          self.builder.zext(self.temps[source], int_type)])

    # Extern function declaration.
    def emit_extern_func(self, name, rettypename, *parmtypenames):
        rettype = typemap[rettypename]
        parmtypes = [typemap[pname] for pname in parmtypenames]
        func_type = FunctionType(rettype, parmtypes)
        self.globals[name] = Function(self.module, func_type, name=name)

    # Call an external function.
    def emit_call_func(self, funcname, *args):
        target = args[-1]
        func = self.globals[funcname]
        argvals = [self.temps[name] for name in args[:-1]]
        self.temps[target] = self.builder.call(func, argvals)

    # Function parameter declarations.  Must create as local variables
    def emit_parm_int(self, name, num):
        var = self.builder.alloca(int_type, name=name)
        self.builder.store(self.function.args[num], var)
        self.locals[name] = var

    def emit_parm_float(self, name, num):
        var = self.builder.alloca(float_type, name=name)
        self.builder.store(self.function.args[num], var)
        self.locals[name] = var

    def emit_parm_bool(self, name, num):
        var = self.builder.alloca(bool_type, name=name)
        self.builder.store(self.function.args[num], var)
        self.locals[name] = var

    # Return statements
    def emit_return_int(self, source):
        self.builder.store(self.temps[source], self.locals['return'])
        self.branch(self.exit_block)

    def emit_return_float(self, source):
        self.builder.store(self.temps[source], self.locals['return'])
        self.branch(self.exit_block)

    def emit_return_bool(self, source):
        self.builder.store(self.temps[source], self.locals['return'])
        self.branch(self.exit_block)

    def emit_return_void(self):
        self.branch(self.exit_block)
Exemplo n.º 4
0
class GenerateLLVM(object):
    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')

        # All globals variables and function definitions go here
        self.globals = {}

        self.blocks = {}

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()

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

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

    def get_block(self, block_name):
        block = self.blocks.get(block_name)
        if block is None:
            block = self.function.append_basic_block(block_name)
            self.blocks[block_name] = block

        return block

    # ----------------------------------------------------------------------
    # Opcode implementation.   You must implement the opcodes.  A few
    # sample opcodes have been given to get you started.
    # ----------------------------------------------------------------------

    # Creation of literal values.  Simply define as LLVM constants.
    def emit_MOV(self, value, target, val_type):
        self.temps[target] = Constant(val_type, value)

    emit_MOVI = partialmethod(emit_MOV, val_type=int_type)
    emit_MOVF = partialmethod(emit_MOV, val_type=float_type)
    emit_MOVB = partialmethod(emit_MOV, val_type=byte_type)

    # Allocation of GLOBAL variables.  Declare as global variables and set to
    # a sensible initial value.
    def emit_VAR(self, name, var_type):
        var = GlobalVariable(self.module, var_type, name=name)
        var.initializer = Constant(var_type, 0)
        self.globals[name] = var

    emit_VARI = partialmethod(emit_VAR, var_type=int_type)
    emit_VARF = partialmethod(emit_VAR, var_type=float_type)
    emit_VARB = partialmethod(emit_VAR, var_type=byte_type)

    # Allocation of LOCAL variables.  Declare as local variables and set to
    # a sensible initial value.
    def emit_ALLOC(self, name, var_type):
        self.locals[name] = self.builder.alloca(var_type, name=name)

    emit_ALLOCI = partialmethod(emit_ALLOC, var_type=int_type)
    emit_ALLOCF = partialmethod(emit_ALLOC, var_type=float_type)
    emit_ALLOCB = partialmethod(emit_ALLOC, var_type=byte_type)

    # Load/store instructions for variables.  Load needs to pull a
    # value from a global variable and store in a temporary. Store
    # goes in the opposite direction.
    def emit_LOADI(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], name=target)

    emit_LOADF = emit_LOADI
    emit_LOADB = emit_LOADI

    def emit_STOREI(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    emit_STOREF = emit_STOREI
    emit_STOREB = emit_STOREI

    # Binary + operator
    def emit_ADDI(self, left, right, target):
        self.temps[target] = self.builder.add(self.temps[left],
                                              self.temps[right],
                                              name=target)

    def emit_ADDF(self, left, right, target):
        self.temps[target] = self.builder.fadd(self.temps[left],
                                               self.temps[right],
                                               name=target)

    # Binary - operator
    def emit_SUBI(self, left, right, target):
        self.temps[target] = self.builder.sub(self.temps[left],
                                              self.temps[right],
                                              name=target)

    def emit_SUBF(self, left, right, target):
        self.temps[target] = self.builder.fsub(self.temps[left],
                                               self.temps[right],
                                               name=target)

    # Binary * operator
    def emit_MULI(self, left, right, target):
        self.temps[target] = self.builder.mul(self.temps[left],
                                              self.temps[right],
                                              name=target)

    def emit_MULF(self, left, right, target):
        self.temps[target] = self.builder.fmul(self.temps[left],
                                               self.temps[right],
                                               name=target)

    # Binary / operator
    def emit_DIVI(self, left, right, target):
        self.temps[target] = self.builder.sdiv(self.temps[left],
                                               self.temps[right],
                                               name=target)

    def emit_DIVF(self, left, right, target):
        self.temps[target] = self.builder.fdiv(self.temps[left],
                                               self.temps[right],
                                               name=target)

    # Print statements
    def emit_PRINT(self, source, runtime_name):
        self.builder.call(self.runtime[runtime_name], [self.temps[source]])

    emit_PRINTI = partialmethod(emit_PRINT, runtime_name="_print_int")
    emit_PRINTF = partialmethod(emit_PRINT, runtime_name="_print_float")
    emit_PRINTB = partialmethod(emit_PRINT, runtime_name="_print_byte")

    def emit_CMPI(self, operator, left, right, target):
        tmp = self.builder.icmp_signed(operator, self.temps[left],
                                       self.temps[right], 'tmp')
        # LLVM compares produce a 1-bit integer as a result.  Since our IRcode using integers
        # for bools, need to sign-extend the result up to the normal int_type to continue
        # with further processing (otherwise you'll get a LLVM type error).
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    def emit_CMPF(self, operator, left, right, target):
        tmp = self.builder.fcmp_ordered(operator, self.temps[left],
                                        self.temps[right], 'tmp')
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    emit_CMPB = emit_CMPI

    # Logical ops
    def emit_AND(self, left, right, target):
        self.temps[target] = self.builder.and_(self.temps[left],
                                               self.temps[right], target)

    def emit_OR(self, left, right, target):
        self.temps[target] = self.builder.or_(self.temps[left],
                                              self.temps[right], target)

    def emit_XOR(self, left, right, target):
        self.temps[target] = self.builder.xor(self.temps[left],
                                              self.temps[right], target)

    def emit_LABEL(self, lbl_name):
        self.block = self.get_block(lbl_name)
        self.builder.position_at_end(self.block)

    def emit_BRANCH(self, dst_label):
        if not self.block.is_terminated:
            self.builder.branch(self.get_block(dst_label))

    def emit_CBRANCH(self, test_target, true_label, false_label):
        true_block = self.get_block(true_label)
        false_block = self.get_block(false_label)
        testvar = self.temps[test_target]
        self.builder.cbranch(self.builder.trunc(testvar, IntType(1)),
                             true_block, false_block)

    def emit_RET(self, register):
        self.builder.store(self.temps[register], self.vars['return'])
        self.builder.branch(self.return_block)

    def emit_CALL(self, func_name, *registers):
        # print(self.globals)
        args = [self.temps[r] for r in registers[:-1]]
        target = registers[-1]
        self.temps[target] = self.builder.call(self.globals[func_name], args)
Exemplo n.º 5
0
class GenerateLLVM(object):
    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.globals = {}

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()

    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()

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

    def generate_code(self, ircode):
        # 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)

        # Collect and create all basic blocks
        labels = [instr[1] for instr in ircode if instr[0] == 'LABEL']
        self.basicblocks = {
            name: self.function.append_basic_block(name)
            for name in labels
        }

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

    # ----------------------------------------------------------------------
    # Opcode implementation.   You must implement the opcodes.  A few
    # sample opcodes have been given to get you started.
    # ----------------------------------------------------------------------

    # Creation of literal values.  Simply define as LLVM constants.
    def emit_MOVI(self, value, target):
        self.temps[target] = Constant(int_type, value)

    def emit_MOVF(self, value, target):
        self.temps[target] = Constant(float_type, value)

        pass  # You must implement

    def emit_MOVB(self, value, target):
        self.temps[target] = Constant(byte_type, value)

    # Allocation of variables.  Declare as global variables and set to
    # a sensible initial value.
    def emit_VARI(self, name):
        var = GlobalVariable(self.module, int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.globals[name] = var

    def emit_VARF(self, name):
        var = GlobalVariable(self.module, float_type, name=name)
        var.initializer = Constant(float_type, 0.0)
        self.globals[name] = var

    def emit_VARB(self, name):
        var = GlobalVariable(self.module, byte_type, name=name)
        var.initializer = Constant(byte_type, 0)
        self.globals[name] = var

    def emit_ALLOCI(self, name):
        self.locals[name] = self.builder.alloca(int_type, name=name)

    def emit_ALLOCF(self, name):
        self.locals[name] = self.builder.alloca(float_type, name=name)

    def emit_ALLOCB(self, name):
        self.locals[name] = self.builder.alloca(byte_type, name=name)

    # Load/store instructions for variables.  Load needs to pull a
    # value from a global variable and store in a temporary. Store
    # goes in the opposite direction.
    def emit_LOADI(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)

    def emit_LOADF(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)

    def emit_LOADB(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)
        pass  # You must implement

    def emit_STOREI(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    def emit_STOREF(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    def emit_STOREB(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    # Binary + operator
    def emit_ADDI(self, left, right, target):
        self.temps[target] = self.builder.add(self.temps[left],
                                              self.temps[right], target)

    def emit_ADDF(self, left, right, target):
        self.temps[target] = self.builder.fadd(self.temps[left],
                                               self.temps[right], target)
        pass  # You must implement

    # Binary - operator
    def emit_SUBI(self, left, right, target):
        self.temps[target] = self.builder.sub(self.temps[left],
                                              self.temps[right], target)
        pass  # You must implement

    def emit_SUBF(self, left, right, target):
        self.temps[target] = self.builder.fsub(self.temps[left],
                                               self.temps[right], target)
        pass  # You must implement

    # Binary * operator
    def emit_MULI(self, left, right, target):
        self.temps[target] = self.builder.mul(self.temps[left],
                                              self.temps[right], target)
        pass  # You must implement

    def emit_MULF(self, left, right, target):
        self.temps[target] = self.builder.fmul(self.temps[left],
                                               self.temps[right], target)
        pass  # You must implement

    # Binary / operator
    def emit_DIVI(self, left, right, target):
        self.temps[target] = self.builder.sdiv(self.temps[left],
                                               self.temps[right], target)
        pass  # You must implement

    def emit_DIVF(self, left, right, target):
        self.temps[target] = self.builder.fdiv(self.temps[left],
                                               self.temps[right], target)
        pass  # You must implement

    def emit_CMPI(self, op, left, right, target):
        result = self.builder.icmp_signed(op, self.temps[left],
                                          self.temps[right], '_temp')
        self.temps[target] = self.builder.zext(result, int_type, target)

    def emit_CMPF(self, op, left, right, target):
        result = self.builder.fcmp_ordered(op, self.temps[left],
                                           self.temps[right], '_temp')
        self.temps[target] = self.builder.zext(result, int_type, target)

    def emit_CMPB(self, op, left, right, target):
        result = self.builder.icmp_signed(op, self.temps[left],
                                          self.temps[right], '_temp')
        self.temps[target] = self.builder.zext(result, int_type, target)

    def emit_AND(self, left, right, target):
        self.temps[target] = self.builder.and_(self.temps[left],
                                               self.temps[right], target)

    def emit_OR(self, left, right, target):
        self.temps[target] = self.builder.or_(self.temps[left],
                                              self.temps[right], target)

    def emit_XOR(self, left, right, target):
        self.temps[target] = self.builder.xor(self.temps[left],
                                              self.temps[right], target)

    # Print statements
    def emit_PRINTI(self, source):
        self.builder.call(self.runtime['_print_int'], [self.temps[source]])

    def emit_PRINTF(self, source):
        self.builder.call(self.runtime['_print_float'], [self.temps[source]])
        pass  # You must implement

    def emit_PRINTB(self, source):
        self.builder.call(self.runtime['_print_byte'], [self.temps[source]])
        pass  # You must implement

    def emit_LABEL(self, name):
        self.block = self.basicblocks[name]
        self.builder.position_at_end(self.block)

    def emit_CBRANCH(self, test, true_label, false_label):
        true_block = self.basicblocks[true_label]
        false_block = self.basicblocks[false_label]
        self.builder.cbranch(self.builder.trunc(self.temps[test], IntType(1)),
                             true_block, false_block)

    def emit_BRANCH(self, next_label):
        target = self.basicblocks[next_label]
        if not self.block.is_terminated:
            self.builder.branch(target)

    def emit_CALL(self, name, *extra):
        *args, target = extra
        args = [self.temps[a] for a in args]
        func = self.vars[name]
        self.temps[target] = self.builder.call(func, args)

    def emit_RET(self, source):
        self.builder.store(self.temps[source], self.return_var)
        self.builder.branch(self.return_block)
Exemplo n.º 6
0
class GenerateLLVM(object):
    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')

        # 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.globals = {}
        self.vars = ChainMap(self.globals)

        # Dictionary that holds all of the temporary registers created in
        # the intermediate code.

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()

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

    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

    def generate_code(self, ircode):
        # 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)
        for instr in ircode:
            if instr[0] == 'LABEL':
                self.blocks[instr[1]] = self.function.append_basic_block(
                    instr[1])

        for opcode, *args in ircode:
            if opcode == 'CALL':
                self.emit_CALL(*args[:-1], target=args[-1])
            elif hasattr(self, 'emit_' + opcode):
                getattr(self, 'emit_' + opcode)(*args)
            else:
                print('Warning: No emit_' + opcode + '() method')

    # ----------------------------------------------------------------------
    # Opcode implementation.   You must implement the opcodes.  A few
    # sample opcodes have been given to get you started.
    # ----------------------------------------------------------------------

    # Creation of literal values.  Simply define as LLVM constants.
    def emit_MOVI(self, value, target):
        self.temps[target] = Constant(int_type, value)

    def emit_MOVF(self, value, target):
        self.temps[target] = Constant(float_type, value)

    def emit_MOVB(self, value, target):
        self.temps[target] = Constant(byte_type, value)

    # Allocation of variables.  Declare as global variables and set to
    # a sensible initial value.
    def emit_VARI(self, name):
        var = GlobalVariable(self.module, int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.globals[name] = var

    def emit_VARF(self, name):
        var = GlobalVariable(self.module, float_type, name=name)
        var.initializer = Constant(float_type, 0.0)
        self.globals[name] = var

    def emit_VARB(self, name):
        var = GlobalVariable(self.module, byte_type, name=name)
        var.initializer = Constant(byte_type, 0)
        self.globals[name] = var

    # Load/store instructions for variables.  Load needs to pull a
    # value from a global variable and store in a temporary. Store
    # goes in the opposite direction.
    def emit_LOADI(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)

    def emit_LOADF(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)

    def emit_LOADB(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)

    def emit_STOREI(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    def emit_STOREF(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    def emit_STOREB(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    # Binary + operator
    def emit_ADDI(self, left, right, target):
        self.temps[target] = self.builder.add(self.temps[left],
                                              self.temps[right], target)

    def emit_ADDF(self, left, right, target):
        self.temps[target] = self.builder.fadd(self.temps[left],
                                               self.temps[right], target)

    # Binary - operator
    def emit_SUBI(self, left, right, target):
        self.temps[target] = self.builder.sub(self.temps[left],
                                              self.temps[right], target)

    def emit_SUBF(self, left, right, target):
        self.temps[target] = self.builder.fsub(self.temps[left],
                                               self.temps[right], target)

    # Binary * operator
    def emit_MULI(self, left, right, target):
        self.temps[target] = self.builder.mul(self.temps[left],
                                              self.temps[right], target)

    def emit_MULF(self, left, right, target):
        self.temps[target] = self.builder.fmul(self.temps[left],
                                               self.temps[right], target)

    # Binary / operator
    def emit_DIVI(self, left, right, target):
        self.temps[target] = self.builder.sdiv(self.temps[left],
                                               self.temps[right], target)

    def emit_DIVF(self, left, right, target):
        self.temps[target] = self.builder.fdiv(self.temps[left],
                                               self.temps[right], target)

    def emit_CMPI(self, op, left, right, target):
        tmp = self.builder.icmp_signed(op, self.temps[left], self.temps[right],
                                       'tmp')
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    def emit_CMPF(self, op, left, right, target):
        tmp = self.builder.fcmp_ordered(op, self.temps[left],
                                        self.temps[right], 'tmp')
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    def emit_CMPB(self, op, left, right, target):
        tmp = self.builder.icmp_signed(op, self.temps[left], self.temps[right],
                                       'tmp')
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    # Logical ops
    def emit_AND(self, left, right, target):
        self.temps[target] = self.builder.and_(self.temps[left],
                                               self.temps[right], target)

    def emit_OR(self, left, right, target):
        self.temps[target] = self.builder.or_(self.temps[left],
                                              self.temps[right], target)

    # control flow
    def emit_LABEL(self, label):
        self.block = self.blocks[label]
        self.builder.position_at_end(self.blocks[label])

    def emit_BRANCH(self, label):
        if not self.block.is_terminated:
            self.builder.branch(self.blocks[label])

    def emit_CBRANCH(self, test, label1, label2):
        tmp = self.builder.trunc(self.temps[test], IntType(1), 'tmp')
        self.builder.cbranch(tmp, self.blocks[label1], self.blocks[label2])

    # functions
    def emit_ALLOCI(self, name):
        var = self.builder.alloca(int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.vars[name] = var

    def emit_ALLOCF(self, name):
        var = self.builder.alloca(float_type, name=name)
        var.initializer = Constant(float_type, 0)
        self.vars[name] = var

    def emit_ALLOCB(self, name):
        var = self.builder.alloca(byte_type, name=name)
        var.initializer = Constant(byte_type, 0)
        self.vars[name] = var

    def emit_RET(self, source):
        self.builder.store(self.temps[source], self.vars['return'])
        self.builder.branch(self.return_block)

    def emit_CALL(self, func_name, *args, target):
        func = self.vars[func_name]
        args = [self.temps[arg] for arg in args]
        self.temps[target] = self.builder.call(func, args)

    # Print statements
    def emit_PRINTI(self, source):
        self.builder.call(self.runtime['_print_int'], [self.temps[source]])

    def emit_PRINTF(self, source):
        self.builder.call(self.runtime['_print_float'], [self.temps[source]])

    def emit_PRINTB(self, source):
        self.builder.call(self.runtime['_print_byte'], [self.temps[source]])
Exemplo n.º 7
0
class GenerateLLVM(object):
    def __init__(self):
        self.module = Module('module')
        self.globals = {}
        self.blocks = {}
        self.declare_runtime_library()

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

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

    def get_block(self, block_name):
        block = self.blocks.get(block_name)
        if block is None:
            block = self.function.append_basic_block(block_name)
            self.blocks[block_name] = block

        return block

    def emit_MOV(self, value, target, val_type):
        self.temps[target] = Constant(val_type, value)

    emit_MOVI = partialmethod(emit_MOV, val_type=int_type)
    emit_MOVF = partialmethod(emit_MOV, val_type=float_type)
    emit_MOVB = partialmethod(emit_MOV, val_type=byte_type)

    def emit_VAR(self, name, var_type):
        var = GlobalVariable(self.module, var_type, name=name)
        var.initializer = Constant(var_type, 0)
        self.globals[name] = var

    emit_VARI = partialmethod(emit_VAR, var_type=int_type)
    emit_VARF = partialmethod(emit_VAR, var_type=float_type)
    emit_VARB = partialmethod(emit_VAR, var_type=byte_type)

    def emit_ALLOC(self, name, var_type):
        self.locals[name] = self.builder.alloca(var_type, name=name)

    emit_ALLOCI = partialmethod(emit_ALLOC, var_type=int_type)
    emit_ALLOCF = partialmethod(emit_ALLOC, var_type=float_type)
    emit_ALLOCB = partialmethod(emit_ALLOC, var_type=byte_type)

    def emit_LOADI(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], name=target)

    emit_LOADF = emit_LOADI
    emit_LOADB = emit_LOADI

    def emit_STOREI(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    emit_STOREF = emit_STOREI
    emit_STOREB = emit_STOREI

    def emit_ADDI(self, left, right, target):
        self.temps[target] = self.builder.add(self.temps[left],
                                              self.temps[right],
                                              name=target)

    def emit_ADDF(self, left, right, target):
        self.temps[target] = self.builder.fadd(self.temps[left],
                                               self.temps[right],
                                               name=target)

    def emit_SUBI(self, left, right, target):
        self.temps[target] = self.builder.sub(self.temps[left],
                                              self.temps[right],
                                              name=target)

    def emit_SUBF(self, left, right, target):
        self.temps[target] = self.builder.fsub(self.temps[left],
                                               self.temps[right],
                                               name=target)

    def emit_MULI(self, left, right, target):
        self.temps[target] = self.builder.mul(self.temps[left],
                                              self.temps[right],
                                              name=target)

    def emit_MULF(self, left, right, target):
        self.temps[target] = self.builder.fmul(self.temps[left],
                                               self.temps[right],
                                               name=target)

    def emit_DIVI(self, left, right, target):
        self.temps[target] = self.builder.sdiv(self.temps[left],
                                               self.temps[right],
                                               name=target)

    def emit_DIVF(self, left, right, target):
        self.temps[target] = self.builder.fdiv(self.temps[left],
                                               self.temps[right],
                                               name=target)

    def emit_PRINT(self, source, runtime_name):
        self.builder.call(self.runtime[runtime_name], [self.temps[source]])

    emit_PRINTI = partialmethod(emit_PRINT, runtime_name="_print_int")
    emit_PRINTF = partialmethod(emit_PRINT, runtime_name="_print_float")
    emit_PRINTB = partialmethod(emit_PRINT, runtime_name="_print_byte")

    def emit_CMPI(self, operator, left, right, target):
        if operator == "=":
            operator = "=="

        tmp = self.builder.icmp_signed(operator, self.temps[left],
                                       self.temps[right], 'tmp')
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    def emit_CMPF(self, operator, left, right, target):
        if operator == "=":
            operator = "=="

        tmp = self.builder.fcmp_ordered(operator, self.temps[left],
                                        self.temps[right], 'tmp')
        self.temps[target] = self.builder.zext(tmp, int_type, target)

    emit_CMPB = emit_CMPI

    def emit_AND(self, left, right, target):
        self.temps[target] = self.builder.and_(self.temps[left],
                                               self.temps[right], target)

    def emit_OR(self, left, right, target):
        self.temps[target] = self.builder.or_(self.temps[left],
                                              self.temps[right], target)

    def emit_XOR(self, left, right, target):
        self.temps[target] = self.builder.xor(self.temps[left],
                                              self.temps[right], target)

    def emit_LABEL(self, lbl_name):
        self.block = self.get_block(lbl_name)
        self.builder.position_at_end(self.block)

    def emit_BRANCH(self, dst_label):
        if not self.block.is_terminated:
            self.builder.branch(self.get_block(dst_label))

    def emit_CBRANCH(self, test_target, true_label, false_label):
        true_block = self.get_block(true_label)
        false_block = self.get_block(false_label)
        testvar = self.temps[test_target]
        self.builder.cbranch(self.builder.trunc(testvar, IntType(1)),
                             true_block, false_block)

    def emit_RET(self, register):
        self.builder.store(self.temps[register], self.vars['return'])
        self.builder.branch(self.return_block)

    def emit_CALL(self, func_name, *registers):
        args = [self.temps[r] for r in registers[:-1]]
        target = registers[-1]
        self.temps[target] = self.builder.call(self.globals[func_name], args)
Exemplo n.º 8
0
    def compile(self, module: ir.Module, builder: ir.IRBuilder,
                symbols: SymbolTable) -> ir.Value:
        bop = self.bop

        exprL = self.exprL.compile(module, builder, symbols)
        exprL_type = self.exprL.type_of()
        exprL_int = exprL_type in IntTypes

        exprR = self.exprR.compile(module, builder, symbols)
        exprR_type = self.exprR.type_of()
        exprR_int = exprR_type in IntTypes

        if bop == '+':
            if exprL_int and exprR_int:
                return builder.add(exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprR_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprR, exprL_type.ir_type)

            return builder.fadd(exprL, exprR)

        if bop == '-':
            if exprL_int and exprR_int:
                return builder.sub(exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.fsub(exprL, exprR)

        if bop == '*':
            if exprL_int and exprR_int:
                return builder.mul(exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.fmul(exprL, exprR)

        if bop == '/':
            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.fdiv(exprL, exprR)

        if bop == '//':
            return builder.sdiv(exprL, exprR)

        if bop == '%':
            if exprL_int and exprR_int:
                return builder.srem(exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.frem(exprL, exprR)

        if bop in ['&', '&&']:
            return builder.and_(exprL, exprR)

        if bop in ['|', '||']:
            return builder.or_(exprL, exprR)

        if bop == '^':
            return builder.xor(exprL, exprR)

        if bop in ['<', '<=', '>=', '>']:
            if exprL_int and exprR_int:
                return builder.icmp_signed(bop, exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.fcmp_ordered(bop, exprL, exprR)

        if bop == '==':
            if exprL_int and exprR_int:
                return builder.icmp_signed(bop, exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.fcmp_ordered(bop, exprL, exprR)

        if bop == '!=':
            if exprL_int and exprR_int:
                return builder.icmp_signed(bop, exprL, exprR)

            if exprL_int:
                exprL = builder.sitofp(exprL, exprL_type.ir_type)

            if exprR_int:
                exprR = builder.sitofp(exprL, exprR_type.ir_type)

            return builder.fcmp_unordered(bop, exprL, exprR)

        # if bop == '**':

        # if bop in ['**', '*', '%', '-', '<<', '>>', '>>>', '<=', '>=', '<',
        #            '>', '&', '^', '|', '==', '!=', '&&', '||', '=', '+=',
        #            '-=', '*=', '&=', '|=', '^=', '<<=', '>>=', '>>>=', '%=']:
        #     data = (exprL, bop, exprR)

        #     if minify:
        #         return "({}{}{})".format(*data)

        #     return "({} {} {})".format(*data)

        # converter = {
        #     '<=>': '<>=',
        #     '===': '==',
        #     '!==': '!=',
        #     '~=': '~==',
        #     '//=': '/='
        # }
        # if bop in converter.keys():
        #     data = (exprL, converter[bop], exprR)

        #     if minify:
        #         return "({}{}{})".format(*data)

        #     return "({} {} {})".format(*data)

        # if bop == '+':
        #     if self.exprL.type_of() == StringType:
        #         return "{}..{}".format(exprL, exprR)

        #     return "{}+{}".format(exprL, exprR)

        # if bop == '/':
        #     return "({}/double({}))".format(exprL, exprR)

        # if bop == '//':
        #     return "floor({}/{})".format(exprL, exprR)

        # if bop == 'is':
        #     return "{} is '{}'".format(exprL, exprR)

        # if bop == '|>':
        #     return "({}({}))".format(exprR, exprL)

        # if bop == '**=':
        #     data = (exprL, exprR)

        #     if minify:
        #         return "({0}={0}**{1})".format(*data)

        #     return "({0} = {0} ** {1})".format(*data)

        # if bop == '/=':
        #     data = (exprL, exprR)

        #     if minify:
        #         return "({0}={0}/double({1}))".format(*data)

        #     return "({0} = {0} / double({1}))".format(*data)

        WappaException(
            "FATAL", "Unhandled Binary Operator {}".format(bop), self.tok)
        return bop
Exemplo n.º 9
0
class GenerateLLVM(object):
    def __init__(self):
        # Perform the basic LLVM initialization.  You need the following parts:
        #
        #    1.  A top-level Module object
        #    2.  A dictionary of global declarations
        #    3.  Initialization of runtime functions (for printing)
        #
        self.module = Module('module')

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

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()

    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)

    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

    def generate_function(self, funcname, argnames, ircode):
        # Generate code for a single Wabbit function. Each opcode
        # tuple (opcode, args) is dispatched to a method of the form
        # self.emit_opcode(args). Function should already be declared 
        # using declare_function.
        
        self.function = self.globals[funcname]
        self.block = self.function.append_basic_block('entry')
        self.builder = IRBuilder(self.block)

        # Stack of LLVM temporaries
        self.stack = [] 

        # Dictionary of local variables
        self.locals = { }

        # Combined symbol table
        self.symbols = ChainMap(self.locals, self.globals)

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

        # Stack of blocks
        self.blocks = [ ]

        for opcode, *opargs in ircode:
            if hasattr(self, 'emit_'+opcode):
                getattr(self, 'emit_'+opcode)(*opargs)
            else:
                print('Warning: No emit_'+opcode+'() method')

        # Add a return statement to void functions.
        if self.function.function_type.return_type == void_type:
            self.builder.ret_void()

    # Helper methods for LLVM temporary stack manipulation
    def push(self, value):
        self.stack.append(value)

    def pop(self):
        return self.stack.pop()

    def set_block(self, block):
        self.block = block
        self.builder.position_at_end(self.block)

    # ----------------------------------------------------------------------
    # Opcode implementation.   You must implement the opcodes.  A few
    # sample opcodes have been given to get you started.
    # ----------------------------------------------------------------------

    # Creation of literal values.  Simply define as LLVM constants.
    def emit_CONSTI(self, value):
        self.push(Constant(int_type, value))

    def emit_CONSTF(self, value):
        self.push(Constant(float_type, value))

    # Allocation of variables.  Declare as global variables and set to
    # a sensible initial value.
    def emit_VARI(self, name):
        self.locals[name] = self.builder.alloca(int_type, name=name)

    def emit_VARF(self, name):
        self.locals[name] = self.builder.alloca(float_type, name=name)

    # Allocation of globals
    def emit_GLOBALI(self, name):
        var = GlobalVariable(self.module, int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.globals[name] = var

    def emit_GLOBALF(self, name):
        var = GlobalVariable(self.module, float_type, name=name)
        var.initializer = Constant(float_type, 0.0)
        self.globals[name] = var
        
    # Load/store instructions for variables.  Load needs to pull a
    # value from a global variable and store in a temporary. Store
    # goes in the opposite direction.
    def emit_LOAD(self, name):
        self.push(self.builder.load(self.symbols[name], name))

    def emit_STORE(self, target):
        self.builder.store(self.pop(), self.symbols[target])

    # Binary + operator
    def emit_ADDI(self):
        self.push(self.builder.add(self.pop(), self.pop()))

    def emit_ADDF(self):
        self.push(self.builder.fadd(self.pop(), self.pop()))

    # Binary - operator
    def emit_SUBI(self):
        right = self.pop()
        left = self.pop()
        self.push(self.builder.sub(left, right))

    def emit_SUBF(self):
        right = self.pop()
        left = self.pop()
        self.push(self.builder.fsub(left, right))

    # Binary * operator
    def emit_MULI(self):
        self.push(self.builder.mul(self.pop(), self.pop()))

    def emit_MULF(self):
        self.push(self.builder.fmul(self.pop(), self.pop()))

    # Binary / operator
    def emit_DIVI(self):
        right = self.pop()
        left = self.pop()
        self.push(self.builder.sdiv(left, right))

    def emit_DIVF(self):
        right = self.pop()
        left = self.pop()
        self.push(self.builder.fdiv(left, right))

    # Conversion
    def emit_ITOF(self):
        self.push(self.builder.sitofp(self.pop(), float_type))

    def emit_FTOI(self):
        self.push(self.builder.fptosi(self.pop(), int_type))

    # Comparison operators
    def emit_LEI(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.icmp_signed('<=', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_LTI(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.icmp_signed('<', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_GEI(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.icmp_signed('>=', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_GTI(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.icmp_signed('>', left, right)
        self.push(self.builder.zext(result, int_type))
        
    def emit_EQI(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.icmp_signed('==', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_NEI(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.icmp_signed('!=', left, right)
        self.push(self.builder.zext(result, int_type))

    # Comparison operators
    def emit_LEF(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.fcmp_ordered('<=', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_LTF(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.fcmp_ordered('<', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_GEF(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.fcmp_ordered('>=', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_GTF(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.fcmp_ordered('>', left, right)
        self.push(self.builder.zext(result, int_type))
        
    def emit_EQF(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.fcmp_ordered('==', left, right)
        self.push(self.builder.zext(result, int_type))

    def emit_NEF(self):
        right = self.pop()
        left = self.pop()
        result = self.builder.fcmp_ordered('!=', left, right)
        self.push(self.builder.zext(result, int_type))

    # Bitwise operations

    def emit_ANDI(self):
        right = self.pop()
        left = self.pop()
        self.push(self.builder.and_(left, right))

    def emit_ORI(self):
        right = self.pop()
        left = self.pop()
        self.push(self.builder.or_(left, right))
        
    # Print statements
    def emit_PRINTI(self):
        self.builder.call(self.runtime['_print_int'], [self.pop()])

    def emit_PRINTF(self):
        self.builder.call(self.runtime['_print_float'], [self.pop()])

    def emit_PRINTB(self):
        self.builder.call(self.runtime['_print_byte'], [self.pop()])

    # Memory statements
    def emit_GROW(self):
        self.push(self.builder.call(self.runtime['_grow'], [self.pop()]))

    def emit_PEEKI(self):
        self.push(self.builder.call(self.runtime['_peeki'], [self.pop()]))

    def emit_PEEKF(self):
        self.push(self.builder.call(self.runtime['_peekf'], [self.pop()]))

    def emit_PEEKB(self):
        self.push(self.builder.call(self.runtime['_peekb'], [self.pop()]))

    def emit_POKEI(self):
        value = self.pop()
        addr = self.pop()
        self.builder.call(self.runtime['_pokei'], [addr, value])

    def emit_POKEF(self):
        value = self.pop()
        addr = self.pop()
        self.builder.call(self.runtime['_pokef'], [addr, value])

    def emit_POKEB(self):
        value = self.pop()
        addr = self.pop()
        self.builder.call(self.runtime['_pokeb'], [addr, value])

    # Control flow
    def emit_IF(self):
        then_block = self.function.append_basic_block()
        else_block = self.function.append_basic_block()
        exit_block = self.function.append_basic_block()
        self.builder.cbranch(self.builder.trunc(self.pop(), IntType(1)), then_block, else_block)
        self.set_block(then_block)
        self.blocks.append([then_block, else_block, exit_block])

    def emit_ELSE(self):
        if not self.block.is_terminated:
            self.builder.branch(self.blocks[-1][2])
        self.set_block(self.blocks[-1][1])

    def emit_ENDIF(self):
        if not self.block.is_terminated:
            self.builder.branch(self.blocks[-1][2])
        self.set_block(self.blocks[-1][2])
        self.blocks.pop()

    def emit_LOOP(self):
        top_block = self.function.append_basic_block()
        exit_block = self.function.append_basic_block()
        self.builder.branch(top_block)
        self.set_block(top_block)
        self.blocks.append([top_block, exit_block])

    def emit_CBREAK(self):
        next_block = self.function.append_basic_block()
        self.builder.cbranch(self.builder.trunc(self.pop(), IntType(1)), self.blocks[-1][1], next_block)
        self.set_block(next_block)
        
    def emit_ENDLOOP(self):
        if not self.block.is_terminated:
            self.builder.branch(self.blocks[-1][0])
        self.set_block(self.blocks[-1][1])
        self.blocks.pop()

    def emit_RETURN(self):
        self.builder.ret(self.pop())

    def emit_CALL(self, name):
        func = self.globals[name]
        args = [self.pop() for _ in range(len(func.args))][::-1]
        self.push(self.builder.call(func, args))
Exemplo n.º 10
0
class GenerateLLVM(object):
    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 = ChainMap()

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

        self.blocks = { }

        self.funcs = { }


        self.current_func = None

        # Initialize the runtime library functions (see below)
        self.declare_runtime_library()


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

    def generate_code(self, ircode):
        # 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)

        # first collect all the blocks
        for instr in ircode:
            if instr[0] == 'BLK':
                self.blocks[instr[1]] = self.function.append_basic_block(instr[1])


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

        # Add a return statement.  Note, at this point, we don't really have
        # user-defined functions so this is a bit of hack--it may be removed later.
        self.builder.ret_void()

    # ----------------------------------------------------------------------
    # Opcode implementation.   You must implement the opcodes.  A few
    # sample opcodes have been given to get you started.
    # ----------------------------------------------------------------------

    def emit_FUNCTION_END(self, f_name):

        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.return_var))


    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

    def emit_CALL(self, f_name, sources, target):
        #import pdb
        #pdb.set_trace()
        f = self.funcs[f_name]
        self.temps[target] = self.builder.call(f,
            [self.temps[r] for r in eval(sources)]
        )

    def emit_RETURN(self, var):
        self.builder.ret(self.vars[var])


    # Creation of literal values.  Simply define as LLVM constants.
    def emit_MOVI(self, value, target):
        self.temps[target] = Constant(int_type, value)

    def emit_MOVF(self, value, target):
        self.temps[target] = Constant(float_type, value)

    def emit_MOVB(self, value, target):
        self.temps[target] = Constant(byte_type, value)

    # Allocation of variables.  Declare as global variables and set to
    # a sensible initial value.
    def emit_VARI(self, name):
        var = GlobalVariable(self.module, int_type, name=name)
        var.initializer = Constant(int_type, 0)
        self.vars[name] = var

    def emit_VARF(self, name):
        var = GlobalVariable(self.module, float_type, name=name)
        var.initializer = Constant(float_type, 0)
        self.vars[name] = var

    def emit_VARB(self, name):
        var = GlobalVariable(self.module, byte_type, name=name)
        var.initializer = Constant(byte_type, 0)
        self.vars[name] = var

    def emit_ALLOCI(self, name):
        var = self.builder.alloca(IntType(32), name=name)
        self.builder.store(Constant(int_type, 0), var)
        self.vars[name] = var

    # Load/store instructions for variables.  Load needs to pull a
    # value from a global variable and store in a temporary. Store
    # goes in the opposite direction.
    def emit_LOADI(self, name, target):
        self.temps[target] = self.builder.load(self.vars[name], target)

    emit_LOADB = emit_LOADI
    emit_LOADF = emit_LOADI

    def emit_STOREI(self, source, target):
        self.builder.store(self.temps[source], self.vars[target])

    emit_STOREF = emit_STOREI
    emit_STOREB = emit_STOREI

    # Binary + operator
    def emit_ADDI(self, left, right, target):
        self.temps[target] = self.builder.add(self.temps[left], self.temps[right], target)

    # Binary + operator
    def emit_ADDF(self, left, right, target):
        self.temps[target] = self.builder.fadd(self.temps[left], self.temps[right], target)

    # Binary - operator
    def emit_SUBI(self, left, right, target):
        self.temps[target] = self.builder.sub(self.temps[left], self.temps[right], target)

    def emit_SUBF(self, left, right, target):
        self.temps[target] = self.builder.fsub(self.temps[left], self.temps[right], target)

    # Binary * operator
    def emit_MULI(self, left, right, target):
        self.temps[target] = self.builder.mul(self.temps[left], self.temps[right], target)

    def emit_MULF(self, left, right, target):
        self.temps[target] = self.builder.fmul(self.temps[left], self.temps[right], target)

    # Binary / operator
    def emit_DIVI(self, left, right, target):
        self.temps[target] = self.builder.sdiv(self.temps[left], self.temps[right], target)

    def emit_DIVF(self, left, right, target):
        self.temps[target] = self.builder.fdiv(self.temps[left], self.temps[right], target)

    def emit_AND(self, left, right, target):
        self.temps[target] = self.builder.and_(self.temps[left], self.temps[right], target)

    def emit_OR(self, left, right, target):
        self.temps[target] = self.builder.or_(self.temps[left], self.temps[right], target)

    def emit_CMPF(self, op, left, right, target):
        temp = self.builder.fcmp_ordered(op, self.temps[left], self.temps[right], 'temp')
        self.temps[target] = self.builder.zext(temp, int_type)

    def emit_CMPI(self, op, left, right, target):
        temp = self.builder.icmp_signed(op, self.temps[left], self.temps[right], 'temp')
        self.temps[target] = self.builder.zext(temp, int_type)

    def cast_to_int(self, value):
        return self.builder.zext(value, int_type)

    def cast_to_byte(self, value):
        return self.builder.trunc(value, IntType(1))

    # Print statements
    def emit_PRINTI(self, source):
        self.builder.call(self.runtime['_print_int'], [self.cast_to_int(self.temps[source])])

    def emit_PRINTF(self, source):
        self.builder.call(self.runtime['_print_float'], [self.temps[source]])

    def emit_PRINTB(self, source):
        self.builder.call(self.runtime['_print_byte'], [self.temps[source]])


    # conditionals and branches

    def emit_CJMP(self, target, true_branch, false_branch):
        self.builder.cbranch(self.cast_to_byte(self.temps[target]), self.blocks[true_branch], self.blocks[false_branch])

    def emit_JMP(self, target):
        self.builder.branch(self.blocks[target])

    def emit_BLK(self, target):
        self.builder.position_at_end(self.blocks[target])