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)
def _load_from_alloc(self, index: Union[ir.Constant, ir.Instruction], builder: ir.IRBuilder) -> List[ir.LoadInstr]: if not self.dtype in (DType.Complx, DType.DComplx): self_ptr = builder.gep(self.alloc, [index]) products = [builder.load(self_ptr)] else: cmplx_index = builder.mul(index, ir.Constant(int_type, 2)) self_ptr_real = builder.gep(self.alloc, [cmplx_index]) product_real = builder.load(self_ptr_real) cmplx_index = builder.add(cmplx_index, ir.Constant(int_type, 1)) self_ptr_imag = builder.gep(self.alloc, [cmplx_index]) product_imag = builder.load(self_ptr_imag) products = [product_real, product_imag] return products
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
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)
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)
def get_value(scope_stack, builder: ir.IRBuilder, item): if isinstance(item, tuple) and item[0] == 'id': identifier = get_identifier(scope_stack, item[1]) item = identifier['value'] if item.opname == 'alloca' and identifier['type'] != 'array': item = builder.load(item) elif item.opname == 'alloca' and identifier['type'] == 'array': item = builder.gep(item, [int_type(0), int_type(0)]) elif isinstance(item, tuple) and item[0] == 'array_index': item = builder.load(item[1]) elif isinstance(item, tuple) and item[0] == 'struct': item = get_struct_pointer(scope_stack, builder, item[1], item[2]) item = builder.load(item) elif isinstance(item, tuple) and item[0] == 'struct_ptr': item = get_struct_ptr_pointer(scope_stack, builder, item[1], item[2]) item = builder.load(item) return item
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)
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)
def get_struct_ptr_pointer(scope_stack, builder: ir.IRBuilder, struct, item, index=int_type(0)): identifier = get_identifier(scope_stack, struct) assert identifier['type'] == 'struct_ptr' item = struct_map[identifier['val_type']][item] value = builder.load(identifier['value']) return builder.gep(value, [index, item['index']], True)
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)
def string_to_volpe(b: ir.IRBuilder, string: ir.Value): with options(b, int64) as (ret, phi): ret(int64(0)) character = b.load(b.gep(string, [phi])) with b.if_then(b.icmp_unsigned("!=", character, char(0))): ret(b.add(phi, int64(1))) new_string = string_type.unwrap()(ir.Undefined) new_string = b.insert_value(new_string, string, 0) new_string = b.insert_value(new_string, phi, 1) return new_string
def get_array_pointer(scope_stack, builder: ir.IRBuilder, array, index): if isinstance(array, tuple) and array[0] == 'struct': item = get_struct_pointer(scope_stack, builder, array[1], array[2]) elif isinstance(array, tuple) and array[0] == 'struct_ptr': item = get_struct_ptr_pointer(scope_stack, builder, array[1], array[2]) else: identifier = None if isinstance(array, tuple): identifier = get_identifier(scope_stack, array[1]) item = get_pointer(scope_stack, builder, array) if identifier and identifier['type'] == 'val_ptr': item = builder.load(item) return builder.gep(item, [index], True) return builder.gep(item, [int_type(0), index], True)
def _load_from_src(self, ind: Union[ir.Constant, ir.Instruction], builder: ir.IRBuilder) -> List[ir.Instruction]: if isinstance(self.ind, (ir.Constant, ir.Instruction)): src_index = self.ind if isinstance(self.ind, slice): start, _, step = self.ind.indices(self.src.size) muled = builder.mul(ind, ir.Constant(int_type, step)) src_index = builder.add(muled, ir.Constant(int_type, start)) elif isinstance(self.ind, Node): src_index = self.ind.get_ele(ind, builder)[0] else: src_index_ptr = builder.gep(self.src_inds, [ind]) src_index = builder.load(src_index_ptr) return self.src.get_ele(src_index, builder)
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)
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()])
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
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])
def compile(self, module: ir.Module, builder: ir.IRBuilder, symbols: SymbolTable) -> ir.Value: if self.parent is None: return symbols.get_symbol(self.ID) else: ret = self.parent.compile(module, builder, symbols) rtype = ret.type if isinstance(rtype, ir.PointerType): rtype = rtype.pointee if isinstance(rtype, ir.IdentifiedStructType): index = symbols.get_elements(rtype.name).index(self.ID) ret = builder.gep( ret, [make_constant(IntType, i) for i in (0, index)]) rtype = ret.type if isinstance(rtype, ir.PointerType) and ( rtype.pointee in [i.ir_type for i in PrimitiveTypes]): ret = builder.load(ret) return ret
def _code_gen(self, builder: ir.IRBuilder) -> None: with LoopCtx(self.name, builder, self.size) as loop: loop_inc = builder.load(loop.inc) products = self._build_opr(loop_inc, builder) self._store_to_alloc(loop_inc, products, builder)
def _code_gen(self, builder: ir.IRBuilder) -> None: # instr = getattr(builder, biopr_map[self.opr][self.dtype]) with LoopCtx(self.name, builder, self.size) as loop: loop_inc = builder.load(loop.inc) products = self._build_opr(loop_inc, builder) self._store_to_alloc(loop_inc, products, builder)
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)
# 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 ::::") 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))
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])
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]])
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)
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)
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())
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)
def codegen(self, builder: ir.IRBuilder, ctx: CodegenContext) -> ir.Value: ptr = self.ptr.codegen(builder, ctx) return builder.load(ptr)
d = builder.call(sqrt_func, [d2]) builder.ret(d) # 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))
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))