Exemple #1
0
 def codegen(self, builder: ir.IRBuilder, ctx: CodegenContext) -> None:
     if self.value is not None:
         value = self.value.codegen(builder, ctx)
     ctx.vars[self.symbol] = builder.alloca(
         self.symbol.get_type().llvm_type(), name=self.symbol.unique_name())
     if self.value is not None:
         builder.store(value, ctx.vars[self.symbol])
Exemple #2
0
    def _code_gen(self, builder: ir.IRBuilder) -> None:
        """Generating indexing llvm ir
        Note that indexing using numpy array is not recomended,
        because it generates static loops and will cause the generated
        llvm ir too large.
        """
        if isinstance(self.ind, slice):
            start, _, step = self.ind.indices(self.src.size)
            step_const = ir.Constant(int_type, step)
            src_index_ptr = builder.alloca(int_type, 1)
            builder.store(ir.Constant(int_type, start), src_index_ptr)

        with LoopCtx(self.name, builder, self.size) as loop:
            loop_inc = builder.load(loop.inc)
            if isinstance(self.ind, (ir.Constant, ir.Instruction)):
                src_index = self.ind
            elif isinstance(self.ind, slice):
                src_index = builder.load(src_index_ptr)
            elif isinstance(self.ind, Node):
                src_index = self.ind.get_ele(loop_inc, builder)[0]
            else:
                src_index_ptr = builder.gep(self.src_inds, [loop_inc])
                src_index = builder.load(src_index_ptr)

            src_nums = self.src.get_ele(src_index, builder)
            self._store_to_alloc(loop_inc, src_nums, builder)

            if isinstance(self.ind, slice):
                builder.store(builder.add(src_index, step_const),
                              src_index_ptr)
Exemple #3
0
    def compile(self, module: ll.Module, builder: ll.IRBuilder):
        inputs = []

        for i in range(self.num_inputs):
            path = self.get_pin(f'in{i}')
            inputs.append(module.get_global(path))

        output = module.get_global(self.get_pin('out'))

        output_v = ll.Constant(INT_TYPE, 0)

        if self.kind == 'and':
            output_v = builder.not_(output_v)

        for inp in inputs:
            v = builder.load(inp)
            if self.kind == 'and':
                output_v = builder.and_(output_v, v)
            elif self.kind == 'or':
                output_v = builder.or_(output_v, v)
            elif self.kind == 'xor':
                output_v = builder.xor(output_v, v)

        if self.negated:
            output_v = builder.not_(output_v)

        builder.store(output_v, output)
Exemple #4
0
 def codegen(self, builder: ir.IRBuilder, ctx: CodegenContext) -> ir.Value:
     left = self.left.codegen(builder, ctx)
     temp = builder.alloca(Bool_t.llvm_type())
     builder.store(left, temp)
     with builder.if_then(left):
         right = self.right.codegen(builder, ctx)
         builder.store(right, temp)
     return builder.load(temp)
Exemple #5
0
    def codegen(self, builder: IRBuilder, ctx: Context):
        ptr = builder.alloca(ir.ArrayType(ir.IntType(8), len(self.value) + 1))
        mapped = list(
            map(lambda x: ir.Constant(ir.IntType(8), ord(x)),
                list(self.value)))  # La trasformo in un array di char
        mapped.append(ir.Constant(ir.IntType(8), 0))  # Aggiungo il terminatore
        value = ir.Constant.literal_array(mapped)

        builder.store(value, ptr)
        return builder.bitcast(ptr, ir.PointerType(ir.IntType(8)))
Exemple #6
0
    def compile(self, module: ll.Module, builder: ll.IRBuilder):
        pin_in = self.get_pin('in')
        pin_out = self.get_pin('out')

        pin_in = module.get_global(pin_in)
        pin_out = module.get_global(pin_out)

        v = builder.load(pin_in)
        v = builder.not_(v)
        builder.store(v, pin_out)
Exemple #7
0
def opt_store(builder: ir.IRBuilder, value, ptr):
    assert isinstance(ptr.type, ir.PointerType)
    if isinstance(value,
                  ir.Constant) and type(value.type) != type(ptr.type.pointee):
        value = ptr.type(get_type_rank(ptr.type.pointee)[1](value.constant))
    else:
        proper_type = get_proper_type(value.type, ptr.type.pointee)
        if type(value.type) != type(proper_type[0]):
            value = convert(builder, value, proper_type[0])
    builder.store(value, ptr)
Exemple #8
0
 def compile(self, module: ll.Module, builder: ll.IRBuilder):
     for cname in self._toposort():
         cdesc = self.children[cname]
         cdesc.compile(module, builder)
         for pin1 in self._conns.get(cname, dict()):
             for desc2, pin2 in self._conns[cname][pin1]:
                 p1 = self.get_desc(cname).get_pin(pin1)
                 p2 = self.get_desc(desc2).get_pin(pin2)
                 p1 = module.get_global(p1)
                 p2 = module.get_global(p2)
                 v = builder.load(p1)
                 builder.store(v, p2)
Exemple #9
0
def impl_construct_dtype_on_stack(context: BaseContext, builder: ir.IRBuilder, sig, args):
    ty = sig.args[0].dtype_as_type()
    containing_size = find_size_for_dtype(sig.args[0].dtype)
    ptr = builder.alloca(ir.IntType(8), containing_size)
    for i, (name, mem_ty) in enumerate(ty.members):
        llvm_mem_ty = context.get_value_type(mem_ty)
        offset = ty.offset(name)
        v = builder.extract_value(args[1], i)
        v = context.cast(builder, v, sig.args[1][i], mem_ty)
        v_ptr_byte = builder.gep(ptr, (ir.Constant(ir.IntType(32), offset),), True)
        v_ptr = builder.bitcast(v_ptr_byte, llvm_mem_ty.as_pointer())
        builder.store(v, v_ptr)
    return ptr
Exemple #10
0
 def __compile_variable(self, builder: ir.IRBuilder, c: Union[SetVariable,
                                                              Variable]):
     if isinstance(c, SetVariable):
         value = self.__compile_value(builder, c.value)
         if c.name not in self.__variables:
             self.__variables[c.name] = builder.alloca(value.type)
         align = value.type.get_abi_alignment(self.__target_data)
         # print(f"Alignment: {align}")
         ptr = self.__variables[c.name] if not c.deref else builder.load(
             self.__variables[c.name])
         builder.store(value, ptr)
     elif isinstance(c, Variable):
         p = self.__variables[c.name]
         value = builder.load(p) if p.type.is_pointer else p
         return value
Exemple #11
0
 def _code_gen(self, builder: ir.IRBuilder) -> None:
     mod = builder.block.module
     # instr = ir.values.Function(mod, self.ftype, self.func_name)
     input_type = []
     for in_node in self.SRC:
         input_type.append(type_map_llvm[in_node.dtype])
     instr = mod.declare_intrinsic(self.func_name, input_type)
     params = []
     with LoopCtx(self.name, builder, self.size) as loop:
         index = builder.load(loop.inc)
         data_ptr = builder.gep(self.alloc, [index])
         for n in self.SRC:
             params.append(n.get_ele(index, builder)[0])
         res = builder.call(instr, params)
         builder.store(res, data_ptr)
Exemple #12
0
class LLVMGenerator:
    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 = {}

    def emit(self, code):
        for op, *opargs in code:
            getattr(self, f'emit_{op}')(*opargs)
        self.builder.ret(Constant(int_type, 0))
        return str(self.module)

    def push(self, item):
        self.stack.append(item)

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

    def emit_VARI(self, name):
        self.vars[name] = self.builder.alloca(int_type, name=name)

    def emit_CONSTI(self, value):
        self.push(Constant(int_type, value))

    def emit_STORE(self, name):
        self.builder.store(self.pop(), self.vars[name])

    def emit_LOAD(self, name):
        self.push(self.builder.load(self.vars[name]))

    def emit_ADDI(self):
        self.push(self.builder.add(self.pop(), self.pop()))

    def emit_MULI(self):
        self.push(self.builder.mul(self.pop(), self.pop()))

    def emit_PRINTI(self):
        self.builder.call(self._print_int, [self.pop()])
Exemple #13
0
    def code_gen(self, builder: ir.IRBuilder) -> None:
        """Wrapper of the code_gen function,
        Rcursively generates it's dependences and call itselves _code_gen core
        Note that only Lvalue node need generate llvm ir
        """
        if self.gened:
            return
        for dep in self.dependence:
            dep.code_gen(builder)
        self.gened = True

        if isinstance(self.ind, (list, tuple, np.ndarray)):
            for i in range(len(self.ind)):
                index = ir.Constant(int_type, self.ind[i])
                builder.store(
                    index,
                    builder.gep(self.src_inds, [ir.Constant(int_type, i)]))
        if self.vtype == LRValue.LEFT:
            self._code_gen(builder)
Exemple #14
0
 def codegen(self, builder: IRBuilder, ctx: Context):
     if ctx.is_global(self.name):
         if ctx.global_exists(self.name):
             gv = ctx.get_global(self.name)
             # Se e' globale e gia' esiste, carico l'oggetto dall'indirizzo della globale
             return builder.load(gv)
         else:
             gv = ir.GlobalVariable(ctx.module, layout.ObjectPtrType(),
                                    self.name)
             gv.linkage = "internal"
             address = builder.call(
                 ctx.get_function(fn.alloc_global_function), [])
             builder.store(address, gv)
             ctx.set_global(self.name, gv)
             # Se e' globale ma non esiste, la creo A = object**, alloco un B = object, ne prendo l'indirizzo e lo salvo dentro (*B = &A)
             return address
     else:
         var = ctx.get_variable(self.name)
         if var is None:
             raise UnknownVariableError(self.name)
         return var
Exemple #15
0
    def codegen(self, builder: IRBuilder, ctx: Context):
        self.check_children()
        if len(self.children) == 0:
            return  # Da vedere se fare un collect manuale

        value = self.children[0].codegen(builder, ctx)

        if ctx.is_global(self.name):
            dest = None
            if ctx.global_exists(self.name):
                gv = ctx.get_global(self.name)
                dest = builder.load(gv)
            else:
                # Se e' globale e non esiste, la creo su LLVM, poi la assegno un valore creato con alloc_global
                gv = ir.GlobalVariable(ctx.module, layout.ObjectPtrType(),
                                       self.name)
                gv.linkage = "internal"
                address = builder.call(
                    ctx.get_function(fn.alloc_global_function), [])
                ctx.set_global(self.name, gv)
                builder.store(address, gv)
                dest = address
            f = ctx.get_function(fn.copy_function)
            builder.call(f, [value, dest])
        elif not ctx.variable_exists(self.name):
            dest = None
            if isinstance(self.children[0], ReferenceNode):
                f = ctx.get_function(fn.assign_object_function)
                dest = builder.call(f, [value])
            else:
                dest = value
            ctx.set_variable(self.name, dest)
        else:
            if ctx.is_parameter(self.name):
                raise ParameterReferenceError(self.name)
            dest = ctx.get_variable(self.name)
            f = ctx.get_function(fn.copy_function)
            builder.call(f, [value, dest])
Exemple #16
0
 def _store_to_alloc(self, index: Union[ir.Constant, ir.Instruction],
                     src_nums: List[ir.LoadInstr],
                     builder: ir.IRBuilder) -> None:
     if not self.dtype in (DType.Complx, DType.DComplx):
         dest_ptr = builder.gep(self.alloc, [index])
         builder.store(src_nums[0], dest_ptr)
     else:
         cmplx_dest_index = builder.mul(index, ir.Constant(int_type, 2))
         dest_ptr_r = builder.gep(self.alloc, [cmplx_dest_index])
         builder.store(src_nums[0], dest_ptr_r)
         cmplx_dest_index = builder.add(cmplx_dest_index,
                                        ir.Constant(int_type, 1))
         dest_ptr_i = builder.gep(self.alloc, [cmplx_dest_index])
         builder.store(src_nums[1], dest_ptr_i)
Exemple #17
0
 def _gen_setitem(self, builder: ir.IRBuilder,
                  index: Union[int, Node, slice, list, tuple,
                               np.ndarray], val: Node) -> None:
     """When set index to one node, it must be LValue node,
     if not, the graph maintainer should modify its vtype to LEFT.
     Also, only when the array is required, will it be generated.
     """
     if isinstance(index, int):
         const0 = ir.Constant(int_type, 0)
         src_nums = val.get_ele(const0, builder)
         if val.dtype != self.dtype:
             src_nums = build_type_cast(builder, src_nums, val.dtype,
                                        self.dtype)
         index = ir.Constant(int_type, index)
         self._store_to_alloc(index, src_nums, builder)
     elif isinstance(index, slice):
         size = compute_size(index, self.size)
         start, _, step = index.indices(val.size)
         v_start = ir.Constant(int_type, start)
         v_step = ir.Constant(int_type, step)
         dest_index_ptr = builder.alloca(int_type, 1)
         builder.store(v_start, dest_index_ptr)
         with LoopCtx(self.name + "_set_slice", builder, size) as loop:
             loop_inc = builder.load(loop.inc)
             dest_index = builder.load(dest_index_ptr)
             src_nums = val.get_ele(loop_inc, builder)
             self._store_to_alloc(dest_index, src_nums, builder)
             builder.store(builder.add(dest_index, v_step), dest_index_ptr)
     elif isinstance(index, Node):
         with LoopCtx(self.name + "_set_slice", builder,
                      index.size) as loop:
             loop_inc = builder.load(loop.inc)
             dest_index = index.get_ele(loop_inc)[0]
             src_nums = val.get_ele(loop_inc, builder)
             self._store_to_alloc(dest_index, src_nums, builder)
     else:
         all_inds = builder.alloca(int_type, len(index))
         # TODO: change this to malloc function
         for i in range(len(index)):
             ind_ptr = builder.gep(all_inds, [ir.Constant(int_type, i)])
             builder.store(ir.Constant(int_type, index[i]), ind_ptr)
         with LoopCtx(self.name + "_set_slice", builder,
                      len(index)) as loop:
             loop_inc = builder.load(loop.inc)
             dest_index_ptr = builder.gep(all_inds, [loop_inc])
             dest_index = builder.load(dest_index_ptr)
             src_nums = val.get_ele(loop_inc)
             self._store_to_alloc(dest_index, src_nums, builder)
Exemple #18
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)
Exemple #19
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)
Exemple #20
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 = {}

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

    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)

        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.
    # ----------------------------------------------------------------------

    # 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.vars[name] = var

    def emit_VARF(self, name):
        var = GlobalVariable(self.module, float_type, name=name)
        var.initializer = Constant(float_type, 0.0)
        self.vars[name] = var
        pass  # You must implement

    def emit_VARB(self, name):
        var = GlobalVariable(self.module, byte_type, name=name)
        var.initializer = Constant(byte_type, 0)
        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)

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

    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])
        pass  # You must implement

    def emit_STOREB(self, source, target):

        self.builder.store(self.temps[source], self.vars[target])
        pass  # You must implement

    # 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

    # 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
Exemple #21
0
class LLVMGen:
    builder: Optional[IRBuilder]
    current_func: Optional[RIALFunction]
    conditional_block: Optional[LLVMBlock]
    end_block: Optional[LLVMBlock]
    current_block: Optional[LLVMBlock]
    current_struct: Optional[RIALIdentifiedStructType]
    currently_unsafe: bool

    def __init__(self):
        self.current_block = None
        self.conditional_block = None
        self.end_block = None
        self.current_func = None
        self.builder = None
        self.current_struct = None
        self.currently_unsafe = False

    def _get_by_identifier(self, identifier: str, variable: Optional = None) -> Optional:
        if isinstance(variable, RIALVariable):
            variable = variable.backing_value

        if not variable is None and hasattr(variable, 'type') and isinstance(variable.type, PointerType):
            if isinstance(variable.type.pointee, RIALIdentifiedStructType):
                struct = ParserState.find_struct(variable.type.pointee.name)

                if struct is None:
                    return None

                if not self.check_struct_access_allowed(struct):
                    raise PermissionError(f"Tried accesssing struct {struct.name}")

                prop = struct.definition.properties[identifier]

                if prop is None:
                    return None

                # Check property access
                if not self.check_property_access_allowed(struct, prop[1]):
                    raise PermissionError(f"Tried to access property {prop[1].name} but it was not allowed!")

                variable = self.builder.gep(variable, [ir.Constant(ir.IntType(32), 0),
                                                       ir.Constant(ir.IntType(32), prop[0])])
        else:
            # Search local variables
            variable = self.current_block.get_named_value(identifier)

            # Search for a global variable
            if variable is None:
                glob = ParserState.find_global(identifier)

                # Check if in same module
                if glob is not None:
                    if glob.backing_value.parent.name != ParserState.module().name:
                        glob_current_module = ParserState.module().get_global_safe(glob.name)

                        if glob_current_module is not None:
                            variable = glob_current_module
                        else:
                            # TODO: Check if global access is allowed
                            variable = self.gen_global(glob.name, None, glob.backing_value.type.pointee,
                                                       glob.access_modifier, "external",
                                                       glob.backing_value.global_constant)
                    else:
                        variable = glob.backing_value

        # If variable is none, just do a full function search
        if variable is None:
            variable = ParserState.find_function(identifier)

            if variable is None:
                variable = ParserState.find_function(mangle_function_name(identifier, []))

            # Check if in same module
            if variable is not None:
                if variable.module.name != ParserState.module().name:
                    variable_current_module = ParserState.module().get_global_safe(variable.name)

                    if variable_current_module is not None:
                        variable = variable_current_module
                    else:
                        variable = self.create_function_with_type(variable.name, variable.name, variable.function_type,
                                                                  variable.linkage,
                                                                  variable.calling_convention,
                                                                  variable.definition)

        return variable

    def get_exact_definition(self, identifier: str):
        identifiers = identifier.split('.')
        variable = None

        for ident in identifiers:
            variable = self._get_by_identifier(ident, variable)

        # Search for the full name
        if variable is None:
            variable = self._get_by_identifier(identifier, variable)

        if variable is None:
            return None

        if isinstance(variable, RIALFunction):
            return variable

        if isinstance(variable, RIALVariable):
            return variable

        return RIALVariable(identifier, map_llvm_to_type(variable.type.pointee), variable)

    def get_definition(self, identifier: str):
        variable = self.get_exact_definition(identifier)

        # Search with module specifier
        if variable is None:
            if ':' in identifier:
                parts = identifier.split(':')
                module_name = ':'.join(parts[0:-1])

                if CompilationManager.check_module_already_compiled(module_name):
                    return None

                if ParserState.add_dependency_and_wait(module_name):
                    return self.get_definition(identifier)

                return None

        return variable

    def gen_integer(self, number: int, length: int, unsigned: bool = False):
        return ir.Constant((unsigned and LLVMUIntType(length) or ir.IntType(length)), number)

    def gen_half(self, number: float):
        return ir.Constant(ir.HalfType(), number)

    def gen_float(self, number: float):
        return ir.Constant(ir.FloatType(), number)

    def gen_double(self, number: float):
        return ir.Constant(ir.DoubleType(), number)

    def gen_global(self, name: str, value: Optional[ir.Constant], ty: Type, access_modifier: RIALAccessModifier,
                   linkage: str,
                   constant: bool):
        rial_variable = ParserState.module().get_rial_variable(name)

        if rial_variable is not None:
            return rial_variable.backing_value

        glob = ir.GlobalVariable(ParserState.module(), ty, name=name)
        glob.linkage = linkage
        glob.global_constant = constant

        if value is not None:
            glob.initializer = value

        rial_variable = RIALVariable(name, map_llvm_to_type(ty), glob,
                                     access_modifier)
        ParserState.module().global_variables.append(rial_variable)

        return glob

    def gen_string_lit(self, name: str, value: str):
        value = eval("'{}'".format(value))

        # TODO: Remove the always-added \0 once we don't need that anymore
        arr = bytearray(value.encode("utf-8") + b"\x00")
        const_char_arr = ir.Constant(ir.ArrayType(ir.IntType(8), len(arr)), arr)
        glob = self.gen_global(name, const_char_arr, const_char_arr.type, RIALAccessModifier.PRIVATE, "private", True)

        return glob

    def gen_load_if_necessary(self, value):
        if isinstance(value, AllocaInstr) or isinstance(value, Argument) or isinstance(value, FormattedConstant) or (
                hasattr(value, 'type') and isinstance(value.type, PointerType)):
            return self.builder.load(value)
        return value

    def gen_var_if_necessary(self, value):
        # Check if variable is not:
        #   - Pointer
        #   - Alloca (Variable), inherently pointer
        #   - Argument (always pointer)
        #   - Type is pointer (e.g. getelementptr instruction)
        if not (isinstance(value, PointerType) or isinstance(value, AllocaInstr) or isinstance(value, Argument)
                or isinstance(value.type, PointerType)):
            allocad = self.builder.alloca(value.type)
            self.builder.store(value, allocad)
            return allocad
        return value

    def gen_addition(self, left, right):
        left = self.gen_load_if_necessary(left)
        right = self.gen_load_if_necessary(right)

        if isinstance(left.type, ir.IntType):
            return self.builder.add(left, right)

        if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType):
            return self.builder.fadd(left, right)

        return None

    def gen_subtraction(self, left, right):
        left = self.gen_load_if_necessary(left)
        right = self.gen_load_if_necessary(right)

        if isinstance(left.type, ir.IntType):
            return self.builder.sub(left, right)

        if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType):
            return self.builder.fsub(left, right)

        return None

    def gen_multiplication(self, left, right):
        left = self.gen_load_if_necessary(left)
        right = self.gen_load_if_necessary(right)

        if isinstance(left.type, ir.IntType):
            return self.builder.mul(left, right)

        if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType):
            return self.builder.fmul(left, right)

        return None

    def gen_division(self, left, right):
        left = self.gen_load_if_necessary(left)
        right = self.gen_load_if_necessary(right)

        if isinstance(left.type, LLVMUIntType):
            return self.builder.udiv(left, right)

        if isinstance(left.type, ir.IntType):
            return self.builder.sdiv(left, right)

        if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType):
            return self.builder.fdiv(left, right)

        return None

    def gen_comparison(self, comparison: str, left, right):
        left = left
        right = right

        if isinstance(left.type, LLVMUIntType) or isinstance(left.type, ir.PointerType):
            return self.builder.icmp_unsigned(comparison, left, right)

        if isinstance(left.type, ir.IntType):
            return self.builder.icmp_signed(comparison, left, right)

        if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType):
            return self.builder.fcmp_ordered(comparison, left, right)

        return None

    def gen_shorthand(self, variable, value, operation):
        loaded = self.builder.load(variable)
        value = self.gen_load_if_necessary(value)
        mathed = None

        if operation == "+":
            mathed = self.gen_addition(loaded, value)
        elif operation == "-":
            mathed = self.gen_subtraction(loaded, value)
        elif operation == "*":
            mathed = self.gen_multiplication(loaded, value)
        elif operation == "/":
            mathed = self.gen_division(loaded, value)

        self.builder.store(mathed, variable)

        return mathed

    def gen_function_call(self, possible_function_names: List[str],
                          llvm_args: List) -> Optional[CallInstr]:
        func = None
        # Check if it's actually a local variable
        for function_name in possible_function_names:
            var = self.get_definition(function_name)

            if var is not None:
                func = var

        # Try to find by function name
        if func is None:
            for function_name in possible_function_names:
                func = ParserState.find_function(function_name)

                if func is not None:
                    break

        # Try to find by function name but enable canonical name
        if func is None:
            rial_arg_types = [map_llvm_to_type(arg.type) for arg in llvm_args]

            for function_name in possible_function_names:
                func = ParserState.find_function(function_name, rial_arg_types)

                if func is not None:
                    break

        if func is None:
            return None

        if isinstance(func, RIALVariable):
            func = func.backing_value

        if isinstance(func, ir.PointerType) and isinstance(func.pointee, RIALFunction):
            func = func.pointee
        elif isinstance(func, GlobalVariable) or isinstance(func, AllocaInstr):
            loaded_func = self.builder.load(func)
            call = self.builder.call(loaded_func, llvm_args)
            return call

        # Check if call is allowed
        if not self.check_function_call_allowed(func):
            raise PermissionError(f"Tried calling function {func.name} from {self.current_func.name}")

        # Check if function is declared in current module
        if ParserState.module().get_global_safe(func.name) is None:
            func = self.create_function_with_type(func.name, func.canonical_name, func.function_type, func.linkage,
                                                  func.calling_convention, func.definition)

        args = list()

        # Gen a load if necessary
        for i, arg in enumerate(llvm_args):
            llvm_arg = func.definition.rial_args[i].llvm_type

            if llvm_arg == arg.type:
                args.append(arg)
                continue

            args.append(self.builder.load(arg))

        # Check type matching
        for i, arg in enumerate(args):
            if len(func.args) > i and arg.type != func.args[i].type:
                # Check for base types
                ty = isinstance(arg.type, PointerType) and arg.type.pointee or arg.type
                func_arg_type = isinstance(func.args[i].type, PointerType) and func.args[i].type.pointee or func.args[
                    i].type

                if isinstance(ty, RIALIdentifiedStructType):
                    struct = ParserState.find_struct(ty.name)

                    if struct is not None:
                        found = False

                        # Check if a base struct matches the type expected
                        # TODO: Recursive check
                        for base_struct in struct.definition.base_structs:
                            if base_struct == func_arg_type.name:
                                args.remove(arg)
                                args.insert(i, self.builder.bitcast(arg, ir.PointerType(base_struct)))
                                found = True
                                break
                        if found:
                            continue

                    # TODO: SLOC information
                raise TypeError(
                    f"Function {func.name} expects a {func.args[i].type} but got a {arg.type}")

        # Gen call
        return self.builder.call(func, args)

    def gen_no_op(self):
        self.gen_function_call(["rial:builtin:settings:nop_function"], [])

    def check_function_call_allowed(self, func: RIALFunction):
        # Unsafe in safe context is big no-no
        if func.definition.unsafe and not self.currently_unsafe:
            return False
        # Public is always okay
        if func.definition.access_modifier == RIALAccessModifier.PUBLIC:
            return True
        # Internal only when it's the same TLM
        if func.definition.access_modifier == RIALAccessModifier.INTERNAL:
            return func.module.name.split(':')[0] == ParserState.module().name.split(':')[0]
        # Private is harder
        if func.definition.access_modifier == RIALAccessModifier.PRIVATE:
            # Allowed if not in struct and in current module
            if func.definition.struct == "" and func.module.name == ParserState.module().name:
                return True
            # Allowed if in same struct irregardless of module
            if self.current_struct is not None and self.current_struct.name == func.definition.struct:
                return True
        return False

    def check_property_access_allowed(self, struct: RIALIdentifiedStructType, prop: RIALVariable):
        # Same struct, anything goes
        if self.current_struct is not None and self.current_struct.name == struct.name:
            return True

        # Unless it's private it's okay
        if prop.access_modifier == RIALAccessModifier.PRIVATE:
            return False

        return True

    def check_struct_access_allowed(self, struct: RIALIdentifiedStructType):
        # Public is always okay
        if struct.definition.access_modifier == RIALAccessModifier.PUBLIC:
            return True

        # Private and same module
        if struct.definition.access_modifier == RIALAccessModifier.PRIVATE:
            return struct.module_name == ParserState.module().name

        # Internal and same TLM
        if struct.definition.access_modifier == RIALAccessModifier.INTERNAL:
            return struct.module_name.split(':')[0] == ParserState.module().name.split(':')[0]

        return False

    def declare_nameless_variable_from_rial_type(self, rial_type: str, value):
        returned_type = ParserState.map_type_to_llvm_no_pointer(rial_type)

        if isinstance(returned_type, ir.VoidType):
            return value
        returned_value = self.builder.alloca(returned_type)

        if isinstance(ParserState.map_type_to_llvm(rial_type), PointerType):
            self.builder.store(self.builder.load(value), returned_value)
        else:
            self.builder.store(value, returned_value)

        return returned_value

    def assign_non_constant_global_variable(self, glob: GlobalVariable, value):
        if self.current_func.name != "global_ctor":
            raise PermissionError()

        if isinstance(value, AllocaInstr):
            value = self.builder.load(value)
        elif isinstance(value, PointerType):
            value = self.builder.load(value.pointee)
        elif isinstance(value, FormattedConstant):
            value = self.builder.load(value)

        self.builder.store(value, glob)

    def declare_non_constant_global_variable(self, identifier: str, value, access_modifier: RIALAccessModifier,
                                             linkage: str):
        """
        Needs to be called with create_in_global_ctor or otherwise the store/load operations are going to fail.
        :param identifier:
        :param value:
        :param access_modifier:
        :param linkage:
        :return:
        """
        if ParserState.module().get_global_safe(identifier) is not None:
            return None

        if self.current_func.name != "global_ctor":
            return None

        if isinstance(value, AllocaInstr):
            variable = self.gen_global(identifier, null(value.type.pointee), value.type.pointee, access_modifier,
                                       linkage, False)
        elif isinstance(value, PointerType):
            variable = self.gen_global(identifier, null(value.pointee.type), value.pointee.type, access_modifier,
                                       linkage, False)
        elif isinstance(value, FormattedConstant) or isinstance(value, AllocaInstr):
            variable = self.gen_global(identifier, null(value.type.pointee), value.type.pointee, access_modifier,
                                       linkage, False)
        elif isinstance(value, Constant):
            variable = self.gen_global(identifier, null(value.type), value.type, access_modifier, linkage, False)
        else:
            variable = self.gen_global(identifier, null(value.type), value.type, access_modifier, linkage, False)

        self.assign_non_constant_global_variable(variable, value)

        return variable

    def declare_variable(self, identifier: str, value) -> Optional[AllocaInstr]:
        variable = self.current_block.get_named_value(identifier)
        if variable is not None:
            return None

        if isinstance(value, AllocaInstr) or isinstance(value, PointerType):
            variable = value
            variable.name = identifier
        elif isinstance(value, CastInstr) and value.opname == "inttoptr":
            variable = self.builder.alloca(value.type.pointee)
            variable.name = identifier
            rial_type = f"{map_llvm_to_type(value.type)}"
            variable.set_metadata('type',
                                  ParserState.module().add_metadata((rial_type,)))
            self.builder.store(self.builder.load(value), variable)
        elif isinstance(value, FormattedConstant):
            variable = self.builder.alloca(value.type.pointee)
            variable.name = identifier
            rial_type = f"{map_llvm_to_type(value.type)}"
            variable.set_metadata('type',
                                  ParserState.module().add_metadata((rial_type,)))
            self.builder.store(self.builder.load(value), variable)
        elif isinstance(value, Constant):
            variable = self.builder.alloca(value.type)
            variable.name = identifier
            rial_type = f"{map_llvm_to_type(value.type)}"
            variable.set_metadata('type',
                                  ParserState.module().add_metadata((rial_type,)))
            self.builder.store(value, variable)
        else:
            variable = self.builder.alloca(value.type)
            variable.name = identifier
            rial_type = f"{map_llvm_to_type(value.type)}"
            variable.set_metadata('type', ParserState.module().add_metadata((rial_type,)))
            self.builder.store(value, variable)

        self.current_block.add_named_value(identifier, variable)

        return variable

    def assign_to_variable(self, identifier: Union[str, Any], value):
        if isinstance(identifier, str):
            variable = self.get_definition(identifier)
        else:
            variable = identifier

        if variable is None:
            return None

        if isinstance(value, AllocaInstr):
            value = self.builder.load(value)
        elif isinstance(value, PointerType) and not isinstance(variable, PointerType):
            value = self.builder.load(value.pointee)

        self.builder.store(value, variable)

        return variable

    def create_block(self, block_name: str, parent: Optional[LLVMBlock] = None,
                     sibling: Optional[LLVMBlock] = None) -> \
            Optional[LLVMBlock]:
        if parent is None and sibling is None and block_name != "entry" and len(self.current_func.basic_blocks) > 0:
            return None

        block = self.builder.append_basic_block(block_name)
        llvmblock = create_llvm_block(block, parent, sibling)

        return llvmblock

    def create_loop(self, base_block_name: str, parent: LLVMBlock):
        (conditional_llvm_block, body_llvm_block, end_llvm_block) = self.create_conditional_block(base_block_name,
                                                                                                  parent)

        self.conditional_block = conditional_llvm_block
        self.end_block = end_llvm_block

        return conditional_llvm_block, body_llvm_block, end_llvm_block,

    def create_switch_blocks(self, base_block_name: str, parent: LLVMBlock, count_of_cases: int,
                             default_case: bool) -> \
            List[LLVMBlock]:
        blocks = list()

        for i in range(0, count_of_cases):
            blocks.append(self.create_block(f"{base_block_name}.case.{i}", parent, None))

        if default_case:
            blocks.append(self.create_block(f"{base_block_name}.default", parent))

        return blocks

    def create_conditional_block(self, base_block_name: str, parent: LLVMBlock) -> Tuple[
        LLVMBlock, LLVMBlock, LLVMBlock]:
        # Create three blocks, one condition, one body, and one after the loop
        conditional_llvm_block = self.create_block(f"{base_block_name}.condition", parent, None)
        body_llvm_block = self.create_block(f"{base_block_name}.body", conditional_llvm_block, None)
        end_llvm_block = self.create_block(f"{base_block_name}.end", None, parent)

        return conditional_llvm_block, body_llvm_block, end_llvm_block,

    def create_conditional_block_with_else(self, base_block_name: str, parent: LLVMBlock):
        (conditional_block, body_block, else_block) = self.create_conditional_block(base_block_name, parent)

        # Transform end block into else block
        else_block.block.name = f"{base_block_name}.else"
        else_block.sibling = None
        else_block.parent = conditional_block

        # Create new end block
        end_block = self.create_block(f"{base_block_name}.if_else.end", None, conditional_block.parent)

        return conditional_block, body_block, else_block, end_block

    def create_jump_if_not_exists(self, target_block: LLVMBlock) -> Optional[Branch]:
        if self.current_block.block.terminator is None:
            return self.builder.branch(target_block.block)

        return None

    def create_conditional_jump(self, condition, true_block: LLVMBlock, false_block: LLVMBlock,
                                true_branch_weight: int = 50, false_branch_weight: int = 50) -> ConditionalBranch:
        # Check if condition is a variable, we need to load that for LLVM
        condition = self.gen_load_if_necessary(condition)

        cbranch = self.builder.cbranch(condition, true_block.block, false_block.block)

        cbranch.set_weights([true_branch_weight, false_branch_weight])

        return cbranch

    def create_jump(self, target_block: LLVMBlock):
        return self.builder.branch(target_block.block)

    def enter_block(self, llvmblock: LLVMBlock):
        self.current_block = llvmblock
        self.builder.position_at_start(self.current_block.block)

    def enter_block_end(self, llvmblock: LLVMBlock):
        self.current_block = llvmblock
        self.builder.position_at_end(self.current_block.block)

    def create_identified_struct(self, name: str, linkage: str,
                                 rial_access_modifier: RIALAccessModifier,
                                 base_llvm_structs: List[RIALIdentifiedStructType],
                                 body: List[RIALVariable]) -> RIALIdentifiedStructType:
        # Build normal struct and switch out with RIALStruct
        struct = ParserState.module().context.get_identified_type(name)
        rial_struct = RIALIdentifiedStructType(struct.context, struct.name, struct.packed)
        ParserState.module().context.identified_types[struct.name] = rial_struct
        struct = rial_struct
        ParserState.module().structs.append(struct)

        # Create metadata definition
        struct_def = StructDefinition(rial_access_modifier)

        # Build body and body definition
        props_def = dict()
        props = list()
        prop_offset = 0
        for deriv in base_llvm_structs:
            for prop in deriv.definition.properties.values():
                props.append(ParserState.map_type_to_llvm(prop[1].rial_type))
                props_def[prop[1].name] = (prop_offset, prop[1])
                prop_offset += 1

            struct_def.base_structs.append(deriv.name)
        for bod in body:
            props.append(ParserState.map_type_to_llvm(bod.rial_type))
            props_def[bod.name] = (prop_offset, bod)
            prop_offset += 1

        struct.set_body(*tuple(props))
        struct.module_name = ParserState.module().name
        self.current_struct = struct

        struct_def.properties = props_def

        # Store def in metadata
        struct.definition = struct_def

        return struct

    def finish_struct(self):
        self.current_struct = None
        self.current_func = None
        self.current_block = None

    def create_function_with_type(self, name: str, canonical_name: str, ty: FunctionType,
                                  linkage: str,
                                  calling_convention: str,
                                  function_def: FunctionDefinition) -> RIALFunction:
        """
        Creates an IR Function with the specified arguments. NOTHING MORE.
        :param canonical_name:
        :param canonical_name:
        :param function_def:
        :param name:
        :param ty:
        :param linkage:
        :param calling_convention:
        :return:
        """

        # Create function with specified linkage (internal -> module only)
        func = RIALFunction(ParserState.module(), ty, name=name, canonical_name=canonical_name)
        func.linkage = linkage
        func.calling_convention = calling_convention
        func.definition = function_def

        # Set argument names
        for i, arg in enumerate(func.args):
            arg.name = function_def.rial_args[i].name

        ParserState.module().rial_functions.append(func)

        return func

    def create_function_body(self, func: RIALFunction, rial_arg_types: List[str]):
        self.current_func = func

        # Create entry block
        bb = func.append_basic_block("entry")
        llvm_bb = create_llvm_block(bb)
        self.current_block = llvm_bb

        if self.builder is None:
            self.builder = IRBuilder(bb)

        self.builder.position_at_start(bb)

        # Allocate new variables for the passed arguments
        for i, arg in enumerate(func.args):
            # Don't copy variables that are a pointer
            if isinstance(arg.type, PointerType):
                variable = RIALVariable(arg.name, rial_arg_types[i], arg)
            else:
                allocated_arg = self.builder.alloca(arg.type)
                self.builder.store(arg, allocated_arg)
                variable = RIALVariable(arg.name, rial_arg_types[i], allocated_arg)
            self.current_block.add_named_value(arg.name, variable)

    def finish_current_block(self):
        if self.current_block.block.terminator is None:
            self.builder.position_at_end(self.current_block.block)
            self.builder.ret_void()
        self.current_block = self.current_block.parent

    def finish_current_func(self):
        # If we're in release mode
        # Reorder all possible allocas to the start of the function
        if CompilationManager.config.raw_opts.release:
            entry: Block = self.current_func.entry_basic_block
            pos = entry.instructions.index(
                next((instr for instr in reversed(entry.instructions) if isinstance(instr, AllocaInstr)),
                     entry.terminator))
            allocas: List[Tuple[AllocaInstr, Block]] = list()

            for block in self.current_func.blocks:
                block: Block
                # Skip first block
                if block == entry:
                    continue
                for instr in block.instructions:
                    if isinstance(instr, AllocaInstr):
                        allocas.append((instr, block))
            for instr_block in allocas:
                if instr_block is not None:
                    instr_block[1].instructions.remove(instr_block[0])
                    entry.instructions.insert(pos, instr_block[0])
                    pos += 1
                    # Insert nop if block is empty
                    if len(instr_block[1].instructions) == 0:
                        self.builder.position_before(instr_block[1].terminator)
                        self.gen_no_op()
        self.current_func = None

    def create_return_statement(self, statement=VoidType()):
        if isinstance(statement, VoidType):
            return self.builder.ret_void()

        return self.builder.ret(statement)

    def finish_loop(self):
        self.conditional_block = None
        self.end_block = None

    def create_function_type(self, llvm_return_type: Type, llvm_arg_types: List[Type], var_args: bool):
        return ir.FunctionType(llvm_return_type, tuple(llvm_arg_types), var_arg=var_args)

    @contextmanager
    def create_in_global_ctor(self):
        current_block = self.current_block
        current_func = self.current_func
        current_struct = self.current_struct
        conditional_block = self.conditional_block
        end_block = self.end_block
        pos = self.builder is not None and self.builder._anchor or 0

        func = ParserState.module().get_global_safe('global_ctor')

        if func is None:
            func_type = self.create_function_type(ir.VoidType(), [], False)
            func = self.create_function_with_type('global_ctor', 'global_ctor', func_type, "internal", "ccc",
                                                  FunctionDefinition('void'))
            self.create_function_body(func, [])
            struct_type = ir.LiteralStructType([ir.IntType(32), func_type.as_pointer(), ir.IntType(8).as_pointer()])
            glob_value = ir.Constant(ir.ArrayType(struct_type, 1), [ir.Constant.literal_struct(
                [ir.Constant(ir.IntType(32), 65535), func, NULL])])
            glob_type = ir.ArrayType(struct_type, 1)

            self.gen_global("llvm.global_ctors", glob_value, glob_type, RIALAccessModifier.PRIVATE, "appending", False)
        else:
            self.builder.position_before(func.entry_basic_block.terminator)
            self.current_func = func
            self.current_block = create_llvm_block(func.entry_basic_block)

        self.current_struct = None
        self.conditional_block = None
        self.end_block = None

        yield

        self.finish_current_block()
        self.finish_current_func()

        self.current_block = current_block
        self.current_func = current_func
        self.current_struct = current_struct
        self.conditional_block = conditional_block
        self.end_block = end_block
        self.builder._anchor = pos
        self.builder._block = self.current_block is not None and self.current_block.block or None

        return
Exemple #22
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)
Exemple #23
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))
Exemple #24
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]])
Exemple #25
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)
Exemple #26
0
# Perform the comparison
testvar = builder.icmp_signed('<', a_var, b_var)

# Make three blocks
then_block = f_func.append_basic_block('then')
else_block = f_func.append_basic_block('else')
merge_block = f_func.append_basic_block('merge')

# Emit the branch instruction
builder.cbranch(testvar, then_block, else_block)

# Generate code in the then-branch
builder.position_at_end(then_block)
result = builder.add(a_var, b_var)
builder.store(result, c_var)
builder.branch(merge_block)

# Generate code in the else-branch
builder.position_at_end(else_block)
result = builder.sub(a_var, b_var)
builder.store(result, c_var)
builder.branch(merge_block)

# Emit code after the if-else
builder.position_at_end(merge_block)
builder.ret(builder.load(c_var))

print(mod)

print(":::: RUNNING ::::")
Exemple #27
0
class GeneratorVisitor(SmallCVisitor):
    def __init__(self, output_file=None):
        super(SmallCVisitor, self).__init__()
        self.Module = Module(name=__file__)
        self.Module.triple = "x86_64-pc-linux-gnu"
        self.Builder = None
        self.function = None
        self.NamedValues = dict()
        self.counter = 0
        self.loop_stack = []
        self.signal_stack = []
        self.cond_stack = []
        self.var_stack = []
        self.cur_decl_type = None

        self.indentation = 0
        self.function_dict = dict()
        self.error_queue = list()
        self.output_file = output_file

    def print(self):
        if not self.output_file:
            print(self.Module)
        else:
            f = open(self.output_file, "w+")
            f.write(self.Module.__str__())
            f.close()

    def error(self, info):
        print("Error: ", info)
        return 0

    def toBool(self, value):
        zero = Constant(self.getType('bool'), 0)
        return self.Builder.icmp_signed('!=', value, zero)

    def getVal_of_expr(self, expr):
        temp = self.visit(expr)
        if isinstance(temp, Constant) or isinstance(
                temp, CallInstr) or isinstance(temp, LoadInstr) or isinstance(
                    temp, Instruction) or isinstance(temp, GlobalVariable):
            value = temp
        else:
            temp_val = self.getVal_local(temp.IDENTIFIER().getText())
            temp_ptr = temp_val['ptr']
            if temp.array_indexing():
                index = self.getVal_of_expr(temp.array_indexing().expr())
                temp_ptr = self.Builder.gep(temp_ptr,
                                            [Constant(IntType(32), 0), index],
                                            inbounds=True)
            # if isinstance(temp_val['type'],ArrayType):
            #     if temp.array_indexing():
            #         index = self.getVal_of_expr(temp.array_indexing().expr())
            #         temp_ptr = self.Builder.gep(temp_ptr, [Constant(IntType(32), 0), index], inbounds=True)
            #     elif temp.AMPERSAND():
            #         Constant(PointerType(IntType(8)), temp_ptr.getText())
            #     elif temp.ASTERIKS():
            #         pass
            #     else: #返回数组地址
            #         temp_ptr = self.Builder.gep(temp_ptr, [Constant(IntType(32), 0), Constant(IntType(32), 0)], inbounds=True)
            #         return temp_ptr
            value = self.Builder.load(temp_ptr)
        return value

    def getType(self, type):
        if type == 'int':
            return IntType(32)
        elif type == 'char':
            return IntType(8)
        elif type == 'float':
            return FloatType()
        elif type == 'bool':
            return IntType(1)
        elif type == 'void':
            return VoidType()
        else:
            self.error("type error in <getType>")

    def getVal_local(self, id):
        temp_maps = self.var_stack[::-1]
        for map in temp_maps:
            if id in map.keys():
                return map[id]
        self.error("value error in <getVal_local>")
        return None

    def visitFunction_definition(self,
                                 ctx: SmallCParser.Function_definitionContext):
        retType = self.getType(ctx.type_specifier().getText())
        if ctx.identifier().ASTERIKS():
            retType = retType.as_pointer()
        argsType = []
        argsName = []
        # args
        if ctx.param_decl_list():
            args = ctx.param_decl_list()
            var_arg = False
            for t in args.getChildren():
                if t.getText() != ',':
                    if t.getText() == '...':
                        var_arg = True
                        break
                    t_type = self.getType(t.type_specifier().getText())
                    if t.identifier().ASTERIKS():
                        t_type = t_type.as_pointer()
                    argsType.append(t_type)
                    argsName.append(t.identifier().IDENTIFIER().getText())
            funcType = FunctionType(retType, tuple(argsType), var_arg=var_arg)
        # no args
        else:
            funcType = FunctionType(retType, ())

        # function
        if ctx.identifier().IDENTIFIER().getText() in self.function_dict:
            func = self.function_dict[ctx.identifier().IDENTIFIER().getText()]
        else:
            func = Function(self.Module,
                            funcType,
                            name=ctx.identifier().IDENTIFIER().getText())
            self.function_dict[ctx.identifier().IDENTIFIER().getText()] = func
        # blocks or ;
        if ctx.compound_stmt():
            self.function = ctx.identifier().IDENTIFIER().getText()
            block = func.append_basic_block(
                ctx.identifier().IDENTIFIER().getText())
            varDict = dict()
            self.Builder = IRBuilder(block)
            for i, arg in enumerate(func.args):
                arg.name = argsName[i]
                alloca = self.Builder.alloca(arg.type, name=arg.name)
                self.Builder.store(arg, alloca)
                varDict[arg.name] = {
                    "id": arg.name,
                    "type": arg.type,
                    "value": None,
                    "ptr": alloca
                }
            self.var_stack.append(varDict)
            self.visit(ctx.compound_stmt())
            if isinstance(retType, VoidType):
                self.Builder.ret_void()
            self.var_stack.pop()
            self.function = None
        return

    def visitFunctioncall(self, ctx: SmallCParser.FunctioncallContext):
        var_map = self.var_stack[-1]
        function = self.function_dict[ctx.identifier().getText()]
        arg_types = function.args
        index = 0

        args = []
        if ctx.param_list():
            for param in ctx.param_list().getChildren():
                if (param.getText() == ','):
                    continue

                temp = self.getVal_of_expr(param)

                arg_type = None
                if index < len(arg_types):
                    arg_type = arg_types[index]
                ptr_flag = False
                if arg_type:
                    if isinstance(arg_type.type, PointerType):
                        ptr_flag = True
                elif self.getVal_local(temp.name):
                    temp_type = self.getVal_local(temp.name)['type']
                    if isinstance(temp_type, PointerType):
                        ptr_flag = True
                if not ptr_flag and not isinstance(temp, Constant):
                    temp = self.Builder.load(temp)
                args.append(temp)

                index += 1

        return self.Builder.call(function, args)

    # def visitVar_decl(self, ctx: SmallCParser.Var_declContext):
    #     type = self.getType(ctx.type_specifier())
    #     list = ctx.var_decl_list()
    #     for var in list.getChildren():
    #         if var.getText() != ',':
    #             if self.builder:
    #                 alloca = self.builder.alloca(type, name=var.identifier().getText())
    #                 self.builder.store(Constant(type, None), alloca)
    #                 self.var_stack[-1][var.identifier().getText()] = alloca
    #             else:
    #                 g_var = GlobalVariable(self.Module, type, var.identifier().getText())
    #                 g_var.initializer = Constant(type, None)
    #     return

    def visitStmt(self, ctx: SmallCParser.StmtContext):
        if ctx.RETURN():
            value = self.getVal_of_expr(ctx.expr())
            if isinstance(value.type, PointerType):
                value = self.Builder.load(value)
            return self.Builder.ret(value)
        elif ctx.CONTINUE():
            self.signal_stack[-1] = 1
            loop_blocks = self.loop_stack[-1]
            self.Builder.branch(loop_blocks['continue'])
            self.Builder.position_at_start(loop_blocks['buf'])
            return None
        elif ctx.BREAK():
            self.signal_stack[-1] = -1
            loop_blocks = self.loop_stack[-1]
            self.Builder.branch(loop_blocks['break'])
            self.Builder.position_at_start(loop_blocks['buf'])
        else:
            return self.visitChildren(ctx)

    def visitCompound_stmt(self, ctx: SmallCParser.Compound_stmtContext):
        # builder = IRBuilder(self.block_stack[-1])
        # block = self.Builder.append_basic_block()
        # self.block_stack.append(block)
        # with self.Builder.goto_block(block):
        result = self.visitChildren(ctx)
        # self.block_stack.pop()
        return result

    def visitAssignment(self, ctx: SmallCParser.AssignmentContext):
        value = self.getVal_of_expr(ctx.expr())
        identifier = ctx.identifier()
        identifier = self.getVal_local(identifier.IDENTIFIER().getText())
        if isinstance(identifier['type'], ArrayType):
            if ctx.identifier().array_indexing():
                index = self.getVal_of_expr(
                    ctx.identifier().array_indexing().expr())
                if isinstance(index.type, PointerType):
                    index = self.Builder.load(index)
            else:
                index = Constant(IntType(32), 0)
            tempPtr = self.Builder.gep(identifier['ptr'],
                                       [Constant(IntType(32), 0), index],
                                       inbounds=True)
            if isinstance(value.type, PointerType):
                value = self.Builder.load(value)
            return self.Builder.store(value, tempPtr)
        if isinstance(value.type, PointerType):
            value = self.Builder.load(value)
        return self.Builder.store(value, identifier['ptr'])

    def visitExpr(self, ctx: SmallCParser.ExprContext):
        if ctx.condition():
            return self.visit(ctx.condition())
        elif ctx.assignment():
            return self.visit(ctx.assignment())
        elif ctx.functioncall():
            return self.visit(ctx.functioncall())

    def visitCondition(self, ctx: SmallCParser.ConditionContext):
        if ctx.expr():
            disjunction = self.getVal_of_expr(ctx.disjunction())
            if isinstance(disjunction.type, PointerType):
                disjunction = self.Builder.load(disjunction)
            cond = self.Builder.icmp_signed('!=', disjunction,
                                            Constant(disjunction.type, 0))
            expr = self.getVal_of_expr(ctx.expr())
            if isinstance(expr.type, PointerType):
                expr = self.Builder.load(expr)
            condition = self.getVal_of_expr(ctx.condition())
            return self.Builder.select(cond, expr, condition)
        else:
            return self.getVal_of_expr(ctx.disjunction())

    def visitDisjunction(self, ctx: SmallCParser.DisjunctionContext):
        if ctx.disjunction():
            disjunction = self.getVal_of_expr(ctx.disjunction())
            if isinstance(disjunction.type, PointerType):
                disjunction = self.Builder.load(disjunction)
            conjunction = self.getVal_of_expr(ctx.conjunction())
            if isinstance(conjunction.type, PointerType):
                conjunction = self.Builder.load(conjunction)
            left = self.Builder.icmp_signed('!=', disjunction,
                                            Constant(disjunction.type, 0))
            right = self.Builder.icmp_signed('!=', conjunction,
                                             Constant(conjunction.type, 0))
            return self.Builder.or_(left, right)
        else:
            return self.getVal_of_expr(ctx.conjunction())

    def visitConjunction(self, ctx: SmallCParser.ConjunctionContext):
        if ctx.conjunction():
            conjunction = self.getVal_of_expr(ctx.conjunction())
            if isinstance(conjunction.type, PointerType):
                conjunction = self.Builder.load(conjunction)
            comparison = self.getVal_of_expr(ctx.comparison())
            if isinstance(comparison.type, PointerType):
                comparison = self.Builder.load(comparison)
            left = self.Builder.icmp_signed('!=', conjunction,
                                            Constant(conjunction.type, 0))
            right = self.Builder.icmp_signed('!=', comparison,
                                             Constant(comparison.type, 0))
            return self.Builder.and_(left, right)
        else:
            return self.getVal_of_expr(ctx.comparison())

    def visitComparison(self, ctx: SmallCParser.ComparisonContext):
        if ctx.EQUALITY():
            relation1 = self.getVal_of_expr(ctx.relation(0))
            if isinstance(relation1.type, PointerType):
                relation1 = self.Builder.load(relation1)
            relation2 = self.getVal_of_expr(ctx.relation(1))
            if isinstance(relation2.type, PointerType):
                relation2 = self.Builder.load(relation2)
            return self.Builder.icmp_signed('==', relation1, relation2)
        elif ctx.NEQUALITY():
            relation1 = self.getVal_of_expr(ctx.relation(0))
            if isinstance(relation1.type, PointerType):
                relation1 = self.Builder.load(relation1)
            relation2 = self.getVal_of_expr(ctx.relation(1))
            if isinstance(relation2.type, PointerType):
                relation2 = self.Builder.load(relation2)
            return self.Builder.icmp_signed('!=', relation1, relation2)
        else:
            return self.getVal_of_expr(ctx.relation(0))

    def visitRelation(self, ctx: SmallCParser.RelationContext):
        if len(ctx.equation()) > 1:
            equation1 = self.getVal_of_expr(ctx.equation(0))
            if isinstance(equation1.type, PointerType):
                equation1 = self.Builder.load(equation1)
            equation2 = self.getVal_of_expr(ctx.equation(1))
            if isinstance(equation2.type, PointerType):
                equation2 = self.Builder.load(equation2)
            if ctx.LEFTANGLE():
                value = self.Builder.icmp_signed('<', equation1, equation2)
            elif ctx.RIGHTANGLE():
                value = self.Builder.icmp_signed('>', equation1, equation2)
            elif ctx.LEFTANGLEEQUAL():
                value = self.Builder.icmp_signed('<=', equation1, equation2)
            elif ctx.RIGHTANGLEEQUAL():
                value = self.Builder.icmp_signed('>=', equation1, equation2)
            return value
        else:
            return self.getVal_of_expr(ctx.equation(0))

    def visitFor_stmt(self, ctx: SmallCParser.For_stmtContext):
        func = self.function_dict[self.function]

        end_block = func.append_basic_block()
        self.var_stack.append({})
        decl_block = func.append_basic_block()
        self.var_stack.append({})
        cond_block = func.append_basic_block()
        self.var_stack.append({})
        stmt_block = func.append_basic_block()
        self.var_stack.append({})
        loop_block = func.append_basic_block()
        # 1 -> continue, -1 -> break
        self.signal_stack.append(0)

        self.loop_stack.append({
            'continue': cond_block,
            'break': end_block,
            'buf': loop_block
        })

        with self.Builder.goto_block(decl_block):
            # self.Builder.position_at_start(end_block)
            if ctx.var_decl():
                self.visit(ctx.var_decl())
            elif ctx.var_decl_list():
                self.visit(ctx.var_decl_list())
            else:
                self.error("for error in <visitFor_stmt>")
            self.Builder.branch(cond_block)

        self.Builder.branch(decl_block)
        with self.Builder.goto_block(cond_block):
            # cond_expr
            cond_expr = ctx.expr(0)
            cond_expr = self.visit(cond_expr)
            cond_expr = self.toBool(cond_expr)
            self.Builder.cbranch(cond_expr, stmt_block, end_block)

        with self.Builder.goto_block(stmt_block):
            # expr
            self.visit(ctx.stmt())
            expr = ctx.expr(1)
            self.visit(expr)
            self.Builder.branch(cond_block)
            if self.signal_stack[-1] == 0:
                loop_blocks = self.loop_stack[-1]
                self.Builder.position_at_start(loop_blocks['buf'])
                self.Builder.branch(end_block)

        self.Builder.position_at_start(end_block)

        self.loop_stack.pop()
        self.signal_stack.pop()

        self.var_stack.pop()
        self.var_stack.pop()
        self.var_stack.pop()
        self.var_stack.pop()

    def visitWhile_stmt(self, ctx: SmallCParser.While_stmtContext):
        func = self.function_dict[self.function]

        end_block = func.append_basic_block()
        self.var_stack.append({})
        cond_block = func.append_basic_block()
        self.var_stack.append({})
        stmt_block = func.append_basic_block()
        self.var_stack.append({})
        loop_block = func.append_basic_block()
        # 1 -> continue, -1 -> break
        self.signal_stack.append(0)

        self.loop_stack.append({
            'continue': cond_block,
            'break': end_block,
            'buf': loop_block
        })

        self.Builder.branch(cond_block)

        with self.Builder.goto_block(cond_block):
            expr = self.getVal_of_expr(ctx.expr())
            cond_expr = self.toBool(expr)
            self.Builder.cbranch(cond_expr, stmt_block, end_block)

        with self.Builder.goto_block(stmt_block):
            self.visit(ctx.stmt())
            self.Builder.branch(cond_block)
            if self.signal_stack[-1] == 0:
                loop_blocks = self.loop_stack[-1]
                self.Builder.position_at_start(loop_blocks['buf'])
                self.Builder.branch(end_block)

        self.Builder.position_at_start(end_block)

        self.loop_stack.pop()
        self.signal_stack.pop()

        self.var_stack.pop()
        self.var_stack.pop()
        self.var_stack.pop()

    def visitCond_stmt(self, ctx: SmallCParser.Cond_stmtContext):

        expr = self.getVal_of_expr(ctx.expr())

        cond_expr = self.toBool(expr)
        else_expr = ctx.ELSE()

        if else_expr:
            with self.Builder.if_else(cond_expr) as (then, otherwise):
                with then:
                    self.var_stack.append({})
                    true_stmt = ctx.stmt(0)
                    self.visit(true_stmt)
                    self.var_stack.pop()
                with otherwise:
                    self.var_stack.append({})
                    else_stmt = ctx.stmt(1)
                    self.visit(else_stmt)
                    self.var_stack.pop()
        else:
            with self.Builder.if_then(cond_expr):
                self.var_stack.append({})
                true_stmt = ctx.stmt(0)
                self.visit(true_stmt)
                self.var_stack.pop()
        return None

    def visitVar_decl(self, ctx: SmallCParser.Var_declContext):
        self.cur_decl_type = self.getType(ctx.type_specifier().getText())
        return self.visitChildren(ctx)

    def visitVar_decl_list(self, ctx: SmallCParser.Var_decl_listContext):
        ans = []
        decls = ctx.variable_id()
        for decl in decls:
            ans.append(self.visit(decl))
        return ans

    def visitVariable_id(self, ctx: SmallCParser.Variable_idContext):
        identifier = ctx.identifier()
        type = self.cur_decl_type

        if not self.function:
            if identifier.array_indexing():
                length = self.getVal_of_expr(
                    identifier.array_indexing().expr())
                type = ArrayType(type, length.constant)
                g_var = GlobalVariable(self.Module, type,
                                       identifier.IDENTIFIER().getText())
            else:
                g_var = GlobalVariable(self.Module, type,
                                       identifier.IDENTIFIER().getText())

            g_var.initializer = Constant(type, None)
            atomic = {
                "id": identifier.IDENTIFIER().getText(),
                "type": type,
                "value": None,
                "ptr": g_var
            }
            if not len(self.var_stack):
                self.var_stack.append({})
            self.var_stack[0][identifier.IDENTIFIER().getText()] = atomic
            return g_var

        var_map = self.var_stack[-1]

        if identifier.array_indexing():
            length = self.getVal_of_expr(identifier.array_indexing().expr())
            type = ArrayType(type, length.constant)
            ptr = self.Builder.alloca(typ=type,
                                      name=identifier.IDENTIFIER().getText())
        else:
            ptr = self.Builder.alloca(typ=type,
                                      name=identifier.IDENTIFIER().getText())

        expr = ctx.expr()
        if expr:
            value = self.getVal_of_expr(expr)
        else:
            value = Constant(type, None)
        if isinstance(value.type, PointerType):
            value = self.Builder.load(value)
        self.Builder.store(value, ptr)
        var_map[identifier.IDENTIFIER().getText()] = {
            "id": identifier.IDENTIFIER().getText(),
            "type": type,
            "value": value,
            "ptr": ptr
        }
        return ptr

    def visitPrimary(self, ctx: SmallCParser.PrimaryContext):
        if ctx.BOOLEAN():
            return Constant(IntType(1), bool(ctx.getText()))
        elif ctx.INTEGER():
            return Constant(IntType(32), int(ctx.getText()))
        elif ctx.REAL():
            return Constant(FloatType, float(ctx.getText()))
        elif ctx.CHARCONST():
            tempStr = ctx.getText()[1:-1]
            tempStr = tempStr.replace('\\n', '\n')
            tempStr = tempStr.replace('\\0', '\0')
            if ctx.getText()[0] == '"':
                tempStr += '\0'
                temp = GlobalVariable(self.Module,
                                      ArrayType(IntType(8), len(tempStr)),
                                      name="str_" + tempStr[:-1] +
                                      str(self.counter))
                self.counter += 1
                temp.initializer = Constant(
                    ArrayType(IntType(8), len(tempStr)),
                    bytearray(tempStr, encoding='utf-8'))
                temp.global_constant = True
                return self.Builder.gep(
                    temp, [Constant(IntType(32), 0),
                           Constant(IntType(32), 0)],
                    inbounds=True)
            return Constant(IntType(8), ord(tempStr[0]))
        elif ctx.identifier():
            return self.visit(ctx.identifier())
        elif ctx.functioncall():
            return self.visit(ctx.functioncall())
        elif ctx.expr():
            return self.visit(ctx.expr())
        else:
            return self.error("type error in <visitPrimary>")

    def visitFactor(self, ctx: SmallCParser.FactorContext):
        if (ctx.MINUS()):
            factor = self.getVal_of_expr(ctx.factor())
            if isinstance(factor.type, PointerType):
                factor = self.Builder.load(factor)
            factor = self.Builder.neg(factor)
            return factor
        return self.visitChildren(ctx)

    def visitTerm(self, ctx: SmallCParser.TermContext):
        if (ctx.ASTERIKS()):
            term = self.getVal_of_expr(ctx.term())
            if isinstance(term.type, PointerType):
                term = self.Builder.load(term)
            factor = self.getVal_of_expr(ctx.factor())
            if isinstance(factor.type, PointerType):
                factor = self.Builder.load(factor)
            return self.Builder.mul(term, factor)
        if (ctx.SLASH()):
            term = self.getVal_of_expr(ctx.term())
            if isinstance(term.type, PointerType):
                term = self.Builder.load(term)
            factor = self.getVal_of_expr(ctx.factor())
            if isinstance(factor.type, PointerType):
                factor = self.Builder.load(factor)
            return self.Builder.sdiv(term, factor)
        return self.visitChildren(ctx)

    def visitEquation(self, ctx: SmallCParser.EquationContext):
        if (ctx.PLUS()):
            equation = self.getVal_of_expr(ctx.equation())
            if isinstance(equation.type, PointerType):
                equation = self.Builder.load(equation)
            if str(equation.type) != 'i32':
                equation = self.Builder.zext(equation, IntType(32))
            term = self.getVal_of_expr(ctx.term())
            if isinstance(term.type, PointerType):
                term = self.Builder.load(term)
            if str(term.type) != 'i32':
                term = self.Builder.zext(term, IntType(32))
            return self.Builder.add(equation, term)
        if (ctx.MINUS()):
            equation = self.getVal_of_expr(ctx.equation())
            if isinstance(equation.type, PointerType):
                equation = self.Builder.load(equation)
            if str(equation.type) != 'i32':
                equation = self.Builder.zext(equation, IntType(32))
            term = self.getVal_of_expr(ctx.term())
            if isinstance(term.type, PointerType):
                term = self.Builder.load(term)
            if str(term.type) != 'i32':
                term = self.Builder.zext(term, IntType(32))
            return self.Builder.sub(equation, term)
        return self.visitChildren(ctx)

    def visitIdentifier(self, ctx: SmallCParser.IdentifierContext):
        if (ctx.AMPERSAND() and ctx.array_indexing()):
            return self.Builder.gep(self.getVal_of_expr(ctx.IDENTIFIER()),
                                    self.getVal_of_expr(ctx.array_indexing()))
        if (ctx.ASTERIKS() and ctx.array_indexing()):
            return self.Builder.load(
                self.Builder.gep(self.getVal_of_expr(ctx.IDENTIFIER()),
                                 self.getVal_of_expr(ctx.array_indexing())))
        if (ctx.AMPERSAND()):
            return self.getVal_local(str(ctx.IDENTIFIER()))['ptr']
        if (ctx.ASTERIKS()):
            return self.getVal_local(str(ctx.IDENTIFIER()))['ptr']
        temp = self.getVal_local(ctx.IDENTIFIER().getText())
        temp_ptr = temp['ptr']
        # if isinstance(temp_val['type'],ArrayType):
        #     if temp.array_indexing():
        #         index = self.getVal_of_expr(temp.array_indexing().expr())
        #         temp_ptr = self.Builder.gep(temp_ptr, [Constant(IntType(32), 0), index], inbounds=True)
        #     elif temp.AMPERSAND():
        #         Constant(PointerType(IntType(8)), temp_ptr.getText())
        #     elif temp.ASTERIKS():
        #         pass
        #     else: #返回数组地址
        #         temp_ptr = self.Builder.gep(temp_ptr, [Constant(IntType(32), 0), Constant(IntType(32), 0)], inbounds=True)
        #         return temp_ptr
        if isinstance(temp['type'], ArrayType):
            if ctx.array_indexing():
                index = self.getVal_of_expr(ctx.array_indexing().expr())
                if isinstance(index.type, PointerType):
                    index = self.Builder.load(index)
                temp_ptr = self.Builder.gep(temp_ptr,
                                            [Constant(IntType(32), 0), index],
                                            inbounds=True)
                value = self.Builder.load(temp_ptr)
                self.var_stack[-1][temp_ptr.name] = {
                    'id': temp_ptr.name,
                    'type': temp_ptr.type,
                    'value': value,
                    'ptr': temp_ptr
                }
                return temp_ptr
            else:
                temp_ptr = self.Builder.gep(
                    temp_ptr,
                    [Constant(IntType(32), 0),
                     Constant(IntType(32), 0)],
                    inbounds=True)
                value = self.Builder.load(temp_ptr)
                self.var_stack[-1][temp_ptr.name] = {
                    'id': temp_ptr.name,
                    'type': temp_ptr.type,
                    'value': value,
                    'ptr': temp_ptr
                }
                return temp_ptr
        temp_val = temp['ptr']
        return temp_val

    def visitArray_indexing(self, ctx: SmallCParser.Array_indexingContext):
        return self.visit(ctx.expr())
Exemple #28
0
 def codegen(self, builder: ir.IRBuilder, ctx: CodegenContext) -> None:
     value = self.value.codegen(builder, ctx)
     builder.store(value, ctx.vars[self.var.symbol])
Exemple #29
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 = { }

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

    # ----------------------------------------------------------------------
    # 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)
        
    # Load/store instructions for variables.  
    def emit_LOAD(self, name):
        self.push(self.builder.load(self.locals[name], name))

    def emit_STORE(self, name):
        self.builder.store(self.pop(), self.locals[name])

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

    # 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])
Exemple #30
0
# Part (f) - Global variables

from llvmlite.ir import GlobalVariable, VoidType
x_var = GlobalVariable(mod, ty_double, 'x')
x_var.initializer = Constant(ty_double, 0.0)

incr_func = Function(mod, 
                     FunctionType(VoidType(), []), 
                     name='incr')

block = incr_func.append_basic_block("entry")
builder = IRBuilder(block)
tmp1 = builder.load(x_var)
tmp2 = builder.fadd(tmp1, Constant(ty_double, 1.0))
builder.store(tmp2, x_var)
builder.ret_void()

# Part (g) - JIT

import llvmlite.binding as llvm

llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()

target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
compiled_mod = llvm.parse_assembly(str(mod))
engine = llvm.create_mcjit_compiler(compiled_mod, target_machine)
Exemple #31
0
class CodeGenerator(NodeVisitor):
    def __init__(self, symbol_table) -> None:
        # Module is an LLVM construct that contains functions and global variables.
        # In many ways, it is the top-level structure that the LLVM IR uses to contain
        # code. It will own the memory for all of the IR that we generate, which is why
        # the codegen() method returns a raw Value*, rather than a unique_ptr<Value>.
        self.module = Module()

        # The Builder object is a helper object that makes it easy to generate LLVM
        # instructions. Instances of the IRBuilder class template keep track of the
        # current place to insert instructions and has methods to create new instructions.
        self.builder = None
        self.symbol_table = symbol_table
        self.expr_counter = 0
        self.str_counter = 0
        self.bool_counter = 0
        self.printf_counter = 0
        self.func_name = ""
        self.GLOBAL_MEMORY = {}

    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)

    def visit_Program(self, node: Program) -> None:
        """Visit the Block node in AST and call it.

        Args:
            node (Program): Program node (root)
        """

        self.visit(node.block)

    def visit_Block(self, node: Block) -> None:
        """Initializes the method calls according to the nodes represented by the
        variables and compound declarations.

        Args:
            node (Block): the block containing the VAR and BEGIN sections
        """

        for declaration in node.declarations:
            self.visit(declaration)
        self.visit(node.compound_statement)

    def visit_Compound(self, node: Compound) -> None:
        """Central component that coordinates the compound statements (assigments and
        writeln statement) and calls the methods according to their type.

        Args:
            node (Compound): node containing all of the compound statements (assigments
            and writeln statement)
        """

        for child in node.children:
            self.visit(child)

    def visit_Assign(self, node: Assign) -> None:
        """Creates the LLVM IR instructions for the expressions, strings or Booleans
        that are assigned to a variable and adds these to a **auxiliary** GLOBAL MEMORY.

        Args:
            node (Assign): node containing the assignment content
        """

        node_type = type(node.right).__name__
        if isinstance(node.right, String):
            self._create_instruct(node_type)
            self.visit(node.left)
            instruct = self.visit(node.right)
            c_str = self.builder.alloca(instruct.type)
            self.builder.store(instruct, c_str)
            self.builder.ret_void()
        else:
            self._create_instruct(node_type)
            self.visit(node.left)
            instruct = self.visit(node.right)
            self.builder.ret(instruct)

        self.GLOBAL_MEMORY[node.left.value] = instruct

    def visit_Var(self, node: Var) -> VarSymbol:
        """Search for the variable in the Symbol Table and define the Double Type.

        Args:
            node (Var): variable token

        Returns:
            VarSymbol: a variable symbol with updated type
        """

        var_name = node.value
        var_symbol = self.symbol_table.get_token(var_name)
        var_symbol.type = DoubleType()

        return var_symbol

    def visit_Num(self, node: Num) -> Constant:
        """Set the Double Type to a specific number.

        Args:
            node (Num): a token represeting a number (constant)

        Returns:
            Constant: a LLVM IR Constant representing the number.
        """
        return Constant(DoubleType(), float(node.value))

    def visit_BinaryOperator(self, node: BinaryOperator) -> Instruction:
        """Performs the Binary arithmetic operations and returns a LLVM IR Instruction
        according to the operation.

        Args:
            node (BinaryOperator): node containing the variables (or numbers) and the
            arithmetic operators

        Returns:
            Instruction: LLVM arithmetic instruction
        """

        left = self.visit(node.left)
        right = self.visit(node.right)

        if isinstance(left, VarSymbol):
            left_symbol = self.GLOBAL_MEMORY[left.name]
        else:
            left_symbol = left

        if isinstance(right, VarSymbol):
            right_symbol = self.GLOBAL_MEMORY[right.name]
        else:
            right_symbol = right

        if node.operator.type == TokenType.PLUS:
            return self.builder.fadd(left_symbol, right_symbol, "addtmp")
        elif node.operator.type == TokenType.MINUS:
            return self.builder.fsub(left_symbol, right_symbol, "subtmp")
        elif node.operator.type == TokenType.MUL:
            return self.builder.fmul(left_symbol, right_symbol, "multmp")
        elif node.operator.type == TokenType.INTEGER_DIV:
            return self.builder.fdiv(left_symbol, right_symbol, "udivtmp")
        elif node.operator.type == TokenType.FLOAT_DIV:
            return self.builder.fdiv(left_symbol, right_symbol, "fdivtmp")

    def visit_UnaryOperator(self, node: UnaryOperator) -> Constant:
        """Performs Unary Operations according to the arithmetic operator (PLUS and MINUS)
        transforming them into a LLVM IR Constant.

        Args:
            node (UnaryOperator): node containing the variables (or numbers) and the
            arithmetic operators (PLUS and MINUS)

        Returns:
            Constant: a LLVM IR Constant representing the number or variable.
        """

        operator = node.operator.type
        if operator == TokenType.PLUS:
            expression = self.visit(node.expression)
            return Constant(DoubleType(), float(+expression.constant))
        elif operator == TokenType.MINUS:
            expression = self.visit(node.expression)
            return Constant(DoubleType(), float(-expression.constant))

    def visit_String(self, node: String) -> Constant:
        """Converts the literal string to an array of characters.

        Args:
            node (String): a token represeting a string (literal)

        Returns:
            Constant: a constant containing a array of characters
        """

        content = node.value
        return Constant(ArrayType(IntType(8), len(content)),
                        bytearray(content.encode("utf8")))

    def visit_Boolean(self, node: Boolean) -> Constant:
        """Converts the boolean type to an integer (i1) constant.

        Args:
            nodo (Boolean): a token represeting a literal boolean type

        Returns:
            Constant: a constant of type IntType (1) representing the Boolean type:
                1 = True and 0 = False
        """

        if node.token.type == TokenType.FALSE:
            return Constant(IntType(1), 0)
        else:
            return Constant(IntType(1), 1)

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

    def visit_VarDeclaration(self, node: VarDeclaration) -> None:
        pass

    def visit_Type(self, node: Type) -> None:
        pass

    def visit_Empty(self, node: Empty) -> None:
        pass