def codegen(self, builder: ir.IRBuilder, ctx: CodegenContext) -> None: if self.value is not None: value = self.value.codegen(builder, ctx) ctx.vars[self.symbol] = builder.alloca( self.symbol.get_type().llvm_type(), name=self.symbol.unique_name()) if self.value is not None: builder.store(value, ctx.vars[self.symbol])
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 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 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 codegen(self, builder: IRBuilder, ctx: Context): ptr = builder.alloca(ir.ArrayType(ir.IntType(8), len(self.value) + 1)) mapped = list( map(lambda x: ir.Constant(ir.IntType(8), ord(x)), list(self.value))) # La trasformo in un array di char mapped.append(ir.Constant(ir.IntType(8), 0)) # Aggiungo il terminatore value = ir.Constant.literal_array(mapped) builder.store(value, ptr) return builder.bitcast(ptr, ir.PointerType(ir.IntType(8)))
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 opt_store(builder: ir.IRBuilder, value, ptr): assert isinstance(ptr.type, ir.PointerType) if isinstance(value, ir.Constant) and type(value.type) != type(ptr.type.pointee): value = ptr.type(get_type_rank(ptr.type.pointee)[1](value.constant)) else: proper_type = get_proper_type(value.type, ptr.type.pointee) if type(value.type) != type(proper_type[0]): value = convert(builder, value, proper_type[0]) builder.store(value, ptr)
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 impl_construct_dtype_on_stack(context: BaseContext, builder: ir.IRBuilder, sig, args): ty = sig.args[0].dtype_as_type() containing_size = find_size_for_dtype(sig.args[0].dtype) ptr = builder.alloca(ir.IntType(8), containing_size) for i, (name, mem_ty) in enumerate(ty.members): llvm_mem_ty = context.get_value_type(mem_ty) offset = ty.offset(name) v = builder.extract_value(args[1], i) v = context.cast(builder, v, sig.args[1][i], mem_ty) v_ptr_byte = builder.gep(ptr, (ir.Constant(ir.IntType(32), offset),), True) v_ptr = builder.bitcast(v_ptr_byte, llvm_mem_ty.as_pointer()) builder.store(v, v_ptr) return ptr
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 _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 code_gen(self, builder: ir.IRBuilder) -> None: """Wrapper of the code_gen function, Rcursively generates it's dependences and call itselves _code_gen core Note that only Lvalue node need generate llvm ir """ if self.gened: return for dep in self.dependence: dep.code_gen(builder) self.gened = True if isinstance(self.ind, (list, tuple, np.ndarray)): for i in range(len(self.ind)): index = ir.Constant(int_type, self.ind[i]) builder.store( index, builder.gep(self.src_inds, [ir.Constant(int_type, i)])) if self.vtype == LRValue.LEFT: self._code_gen(builder)
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 _store_to_alloc(self, index: Union[ir.Constant, ir.Instruction], src_nums: List[ir.LoadInstr], builder: ir.IRBuilder) -> None: if not self.dtype in (DType.Complx, DType.DComplx): dest_ptr = builder.gep(self.alloc, [index]) builder.store(src_nums[0], dest_ptr) else: cmplx_dest_index = builder.mul(index, ir.Constant(int_type, 2)) dest_ptr_r = builder.gep(self.alloc, [cmplx_dest_index]) builder.store(src_nums[0], dest_ptr_r) cmplx_dest_index = builder.add(cmplx_dest_index, ir.Constant(int_type, 1)) dest_ptr_i = builder.gep(self.alloc, [cmplx_dest_index]) builder.store(src_nums[1], dest_ptr_i)
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)
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)
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 GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') self.function = Function(self.module, FunctionType(void_type, []), name='main') self.block = self.function.append_basic_block('entry') self.builder = IRBuilder(self.block) # Dictionary that holds all of the global variable/function declarations. # Any declaration in the Gone source code is going to get an entry here self.vars = {} # Dictionary that holds all of the temporary registers created in # the intermediate code. self.temps = {} # Initialize the runtime library functions (see below) self.declare_runtime_library() def declare_runtime_library(self): # Certain functions such as I/O and string handling are often easier # to implement in an external C library. This method should make # the LLVM declarations for any runtime functions to be used # during code generation. Please note that runtime function # functions are implemented in C in a separate file gonert.c self.runtime = {} # Declare printing functions self.runtime['_print_int'] = Function(self.module, FunctionType( void_type, [int_type]), name="_print_int") self.runtime['_print_float'] = Function(self.module, FunctionType( void_type, [float_type]), name="_print_float") self.runtime['_print_byte'] = Function(self.module, FunctionType( void_type, [byte_type]), name="_print_byte") def generate_code(self, ircode): # Given a sequence of SSA intermediate code tuples, generate LLVM # instructions using the current builder (self.builder). Each # opcode tuple (opcode, args) is dispatched to a method of the # form self.emit_opcode(args) for opcode, *args in ircode: if hasattr(self, 'emit_' + opcode): getattr(self, 'emit_' + opcode)(*args) else: print('Warning: No emit_' + opcode + '() method') # Add a return statement. Note, at this point, we don't really have # user-defined functions so this is a bit of hack--it may be removed later. self.builder.ret_void() # ---------------------------------------------------------------------- # Opcode implementation. You must implement the opcodes. A few # sample opcodes have been given to get you started. # ---------------------------------------------------------------------- # Creation of literal values. Simply define as LLVM constants. def emit_MOVI(self, value, target): self.temps[target] = Constant(int_type, value) def emit_MOVF(self, value, target): self.temps[target] = Constant(float_type, value) pass # You must implement def emit_MOVB(self, value, target): self.temps[target] = Constant(byte_type, value) # Allocation of variables. Declare as global variables and set to # a sensible initial value. def emit_VARI(self, name): var = GlobalVariable(self.module, int_type, name=name) var.initializer = Constant(int_type, 0) self.vars[name] = var def emit_VARF(self, name): var = GlobalVariable(self.module, float_type, name=name) var.initializer = Constant(float_type, 0.0) self.vars[name] = var pass # You must implement def emit_VARB(self, name): var = GlobalVariable(self.module, byte_type, name=name) var.initializer = Constant(byte_type, 0) self.vars[name] = var # Load/store instructions for variables. Load needs to pull a # value from a global variable and store in a temporary. Store # goes in the opposite direction. def emit_LOADI(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) def emit_LOADF(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) pass # You must implement def emit_LOADB(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) pass # You must implement def emit_STOREI(self, source, target): self.builder.store(self.temps[source], self.vars[target]) def emit_STOREF(self, source, target): self.builder.store(self.temps[source], self.vars[target]) pass # You must implement def emit_STOREB(self, source, target): self.builder.store(self.temps[source], self.vars[target]) pass # You must implement # Binary + operator def emit_ADDI(self, left, right, target): self.temps[target] = self.builder.add(self.temps[left], self.temps[right], target) def emit_ADDF(self, left, right, target): self.temps[target] = self.builder.fadd(self.temps[left], self.temps[right], target) pass # You must implement # Binary - operator def emit_SUBI(self, left, right, target): self.temps[target] = self.builder.sub(self.temps[left], self.temps[right], target) pass # You must implement def emit_SUBF(self, left, right, target): self.temps[target] = self.builder.fsub(self.temps[left], self.temps[right], target) pass # You must implement # Binary * operator def emit_MULI(self, left, right, target): self.temps[target] = self.builder.mul(self.temps[left], self.temps[right], target) pass # You must implement def emit_MULF(self, left, right, target): self.temps[target] = self.builder.fmul(self.temps[left], self.temps[right], target) pass # You must implement # Binary / operator def emit_DIVI(self, left, right, target): self.temps[target] = self.builder.sdiv(self.temps[left], self.temps[right], target) pass # You must implement def emit_DIVF(self, left, right, target): self.temps[target] = self.builder.fdiv(self.temps[left], self.temps[right], target) pass # You must implement # Print statements def emit_PRINTI(self, source): self.builder.call(self.runtime['_print_int'], [self.temps[source]]) def emit_PRINTF(self, source): self.builder.call(self.runtime['_print_float'], [self.temps[source]]) pass # You must implement def emit_PRINTB(self, source): self.builder.call(self.runtime['_print_byte'], [self.temps[source]]) pass # You must implement
class LLVMGen: builder: Optional[IRBuilder] current_func: Optional[RIALFunction] conditional_block: Optional[LLVMBlock] end_block: Optional[LLVMBlock] current_block: Optional[LLVMBlock] current_struct: Optional[RIALIdentifiedStructType] currently_unsafe: bool def __init__(self): self.current_block = None self.conditional_block = None self.end_block = None self.current_func = None self.builder = None self.current_struct = None self.currently_unsafe = False def _get_by_identifier(self, identifier: str, variable: Optional = None) -> Optional: if isinstance(variable, RIALVariable): variable = variable.backing_value if not variable is None and hasattr(variable, 'type') and isinstance(variable.type, PointerType): if isinstance(variable.type.pointee, RIALIdentifiedStructType): struct = ParserState.find_struct(variable.type.pointee.name) if struct is None: return None if not self.check_struct_access_allowed(struct): raise PermissionError(f"Tried accesssing struct {struct.name}") prop = struct.definition.properties[identifier] if prop is None: return None # Check property access if not self.check_property_access_allowed(struct, prop[1]): raise PermissionError(f"Tried to access property {prop[1].name} but it was not allowed!") variable = self.builder.gep(variable, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), prop[0])]) else: # Search local variables variable = self.current_block.get_named_value(identifier) # Search for a global variable if variable is None: glob = ParserState.find_global(identifier) # Check if in same module if glob is not None: if glob.backing_value.parent.name != ParserState.module().name: glob_current_module = ParserState.module().get_global_safe(glob.name) if glob_current_module is not None: variable = glob_current_module else: # TODO: Check if global access is allowed variable = self.gen_global(glob.name, None, glob.backing_value.type.pointee, glob.access_modifier, "external", glob.backing_value.global_constant) else: variable = glob.backing_value # If variable is none, just do a full function search if variable is None: variable = ParserState.find_function(identifier) if variable is None: variable = ParserState.find_function(mangle_function_name(identifier, [])) # Check if in same module if variable is not None: if variable.module.name != ParserState.module().name: variable_current_module = ParserState.module().get_global_safe(variable.name) if variable_current_module is not None: variable = variable_current_module else: variable = self.create_function_with_type(variable.name, variable.name, variable.function_type, variable.linkage, variable.calling_convention, variable.definition) return variable def get_exact_definition(self, identifier: str): identifiers = identifier.split('.') variable = None for ident in identifiers: variable = self._get_by_identifier(ident, variable) # Search for the full name if variable is None: variable = self._get_by_identifier(identifier, variable) if variable is None: return None if isinstance(variable, RIALFunction): return variable if isinstance(variable, RIALVariable): return variable return RIALVariable(identifier, map_llvm_to_type(variable.type.pointee), variable) def get_definition(self, identifier: str): variable = self.get_exact_definition(identifier) # Search with module specifier if variable is None: if ':' in identifier: parts = identifier.split(':') module_name = ':'.join(parts[0:-1]) if CompilationManager.check_module_already_compiled(module_name): return None if ParserState.add_dependency_and_wait(module_name): return self.get_definition(identifier) return None return variable def gen_integer(self, number: int, length: int, unsigned: bool = False): return ir.Constant((unsigned and LLVMUIntType(length) or ir.IntType(length)), number) def gen_half(self, number: float): return ir.Constant(ir.HalfType(), number) def gen_float(self, number: float): return ir.Constant(ir.FloatType(), number) def gen_double(self, number: float): return ir.Constant(ir.DoubleType(), number) def gen_global(self, name: str, value: Optional[ir.Constant], ty: Type, access_modifier: RIALAccessModifier, linkage: str, constant: bool): rial_variable = ParserState.module().get_rial_variable(name) if rial_variable is not None: return rial_variable.backing_value glob = ir.GlobalVariable(ParserState.module(), ty, name=name) glob.linkage = linkage glob.global_constant = constant if value is not None: glob.initializer = value rial_variable = RIALVariable(name, map_llvm_to_type(ty), glob, access_modifier) ParserState.module().global_variables.append(rial_variable) return glob def gen_string_lit(self, name: str, value: str): value = eval("'{}'".format(value)) # TODO: Remove the always-added \0 once we don't need that anymore arr = bytearray(value.encode("utf-8") + b"\x00") const_char_arr = ir.Constant(ir.ArrayType(ir.IntType(8), len(arr)), arr) glob = self.gen_global(name, const_char_arr, const_char_arr.type, RIALAccessModifier.PRIVATE, "private", True) return glob def gen_load_if_necessary(self, value): if isinstance(value, AllocaInstr) or isinstance(value, Argument) or isinstance(value, FormattedConstant) or ( hasattr(value, 'type') and isinstance(value.type, PointerType)): return self.builder.load(value) return value def gen_var_if_necessary(self, value): # Check if variable is not: # - Pointer # - Alloca (Variable), inherently pointer # - Argument (always pointer) # - Type is pointer (e.g. getelementptr instruction) if not (isinstance(value, PointerType) or isinstance(value, AllocaInstr) or isinstance(value, Argument) or isinstance(value.type, PointerType)): allocad = self.builder.alloca(value.type) self.builder.store(value, allocad) return allocad return value def gen_addition(self, left, right): left = self.gen_load_if_necessary(left) right = self.gen_load_if_necessary(right) if isinstance(left.type, ir.IntType): return self.builder.add(left, right) if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType): return self.builder.fadd(left, right) return None def gen_subtraction(self, left, right): left = self.gen_load_if_necessary(left) right = self.gen_load_if_necessary(right) if isinstance(left.type, ir.IntType): return self.builder.sub(left, right) if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType): return self.builder.fsub(left, right) return None def gen_multiplication(self, left, right): left = self.gen_load_if_necessary(left) right = self.gen_load_if_necessary(right) if isinstance(left.type, ir.IntType): return self.builder.mul(left, right) if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType): return self.builder.fmul(left, right) return None def gen_division(self, left, right): left = self.gen_load_if_necessary(left) right = self.gen_load_if_necessary(right) if isinstance(left.type, LLVMUIntType): return self.builder.udiv(left, right) if isinstance(left.type, ir.IntType): return self.builder.sdiv(left, right) if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType): return self.builder.fdiv(left, right) return None def gen_comparison(self, comparison: str, left, right): left = left right = right if isinstance(left.type, LLVMUIntType) or isinstance(left.type, ir.PointerType): return self.builder.icmp_unsigned(comparison, left, right) if isinstance(left.type, ir.IntType): return self.builder.icmp_signed(comparison, left, right) if isinstance(left.type, ir.FloatType) or isinstance(left.type, ir.DoubleType): return self.builder.fcmp_ordered(comparison, left, right) return None def gen_shorthand(self, variable, value, operation): loaded = self.builder.load(variable) value = self.gen_load_if_necessary(value) mathed = None if operation == "+": mathed = self.gen_addition(loaded, value) elif operation == "-": mathed = self.gen_subtraction(loaded, value) elif operation == "*": mathed = self.gen_multiplication(loaded, value) elif operation == "/": mathed = self.gen_division(loaded, value) self.builder.store(mathed, variable) return mathed def gen_function_call(self, possible_function_names: List[str], llvm_args: List) -> Optional[CallInstr]: func = None # Check if it's actually a local variable for function_name in possible_function_names: var = self.get_definition(function_name) if var is not None: func = var # Try to find by function name if func is None: for function_name in possible_function_names: func = ParserState.find_function(function_name) if func is not None: break # Try to find by function name but enable canonical name if func is None: rial_arg_types = [map_llvm_to_type(arg.type) for arg in llvm_args] for function_name in possible_function_names: func = ParserState.find_function(function_name, rial_arg_types) if func is not None: break if func is None: return None if isinstance(func, RIALVariable): func = func.backing_value if isinstance(func, ir.PointerType) and isinstance(func.pointee, RIALFunction): func = func.pointee elif isinstance(func, GlobalVariable) or isinstance(func, AllocaInstr): loaded_func = self.builder.load(func) call = self.builder.call(loaded_func, llvm_args) return call # Check if call is allowed if not self.check_function_call_allowed(func): raise PermissionError(f"Tried calling function {func.name} from {self.current_func.name}") # Check if function is declared in current module if ParserState.module().get_global_safe(func.name) is None: func = self.create_function_with_type(func.name, func.canonical_name, func.function_type, func.linkage, func.calling_convention, func.definition) args = list() # Gen a load if necessary for i, arg in enumerate(llvm_args): llvm_arg = func.definition.rial_args[i].llvm_type if llvm_arg == arg.type: args.append(arg) continue args.append(self.builder.load(arg)) # Check type matching for i, arg in enumerate(args): if len(func.args) > i and arg.type != func.args[i].type: # Check for base types ty = isinstance(arg.type, PointerType) and arg.type.pointee or arg.type func_arg_type = isinstance(func.args[i].type, PointerType) and func.args[i].type.pointee or func.args[ i].type if isinstance(ty, RIALIdentifiedStructType): struct = ParserState.find_struct(ty.name) if struct is not None: found = False # Check if a base struct matches the type expected # TODO: Recursive check for base_struct in struct.definition.base_structs: if base_struct == func_arg_type.name: args.remove(arg) args.insert(i, self.builder.bitcast(arg, ir.PointerType(base_struct))) found = True break if found: continue # TODO: SLOC information raise TypeError( f"Function {func.name} expects a {func.args[i].type} but got a {arg.type}") # Gen call return self.builder.call(func, args) def gen_no_op(self): self.gen_function_call(["rial:builtin:settings:nop_function"], []) def check_function_call_allowed(self, func: RIALFunction): # Unsafe in safe context is big no-no if func.definition.unsafe and not self.currently_unsafe: return False # Public is always okay if func.definition.access_modifier == RIALAccessModifier.PUBLIC: return True # Internal only when it's the same TLM if func.definition.access_modifier == RIALAccessModifier.INTERNAL: return func.module.name.split(':')[0] == ParserState.module().name.split(':')[0] # Private is harder if func.definition.access_modifier == RIALAccessModifier.PRIVATE: # Allowed if not in struct and in current module if func.definition.struct == "" and func.module.name == ParserState.module().name: return True # Allowed if in same struct irregardless of module if self.current_struct is not None and self.current_struct.name == func.definition.struct: return True return False def check_property_access_allowed(self, struct: RIALIdentifiedStructType, prop: RIALVariable): # Same struct, anything goes if self.current_struct is not None and self.current_struct.name == struct.name: return True # Unless it's private it's okay if prop.access_modifier == RIALAccessModifier.PRIVATE: return False return True def check_struct_access_allowed(self, struct: RIALIdentifiedStructType): # Public is always okay if struct.definition.access_modifier == RIALAccessModifier.PUBLIC: return True # Private and same module if struct.definition.access_modifier == RIALAccessModifier.PRIVATE: return struct.module_name == ParserState.module().name # Internal and same TLM if struct.definition.access_modifier == RIALAccessModifier.INTERNAL: return struct.module_name.split(':')[0] == ParserState.module().name.split(':')[0] return False def declare_nameless_variable_from_rial_type(self, rial_type: str, value): returned_type = ParserState.map_type_to_llvm_no_pointer(rial_type) if isinstance(returned_type, ir.VoidType): return value returned_value = self.builder.alloca(returned_type) if isinstance(ParserState.map_type_to_llvm(rial_type), PointerType): self.builder.store(self.builder.load(value), returned_value) else: self.builder.store(value, returned_value) return returned_value def assign_non_constant_global_variable(self, glob: GlobalVariable, value): if self.current_func.name != "global_ctor": raise PermissionError() if isinstance(value, AllocaInstr): value = self.builder.load(value) elif isinstance(value, PointerType): value = self.builder.load(value.pointee) elif isinstance(value, FormattedConstant): value = self.builder.load(value) self.builder.store(value, glob) def declare_non_constant_global_variable(self, identifier: str, value, access_modifier: RIALAccessModifier, linkage: str): """ Needs to be called with create_in_global_ctor or otherwise the store/load operations are going to fail. :param identifier: :param value: :param access_modifier: :param linkage: :return: """ if ParserState.module().get_global_safe(identifier) is not None: return None if self.current_func.name != "global_ctor": return None if isinstance(value, AllocaInstr): variable = self.gen_global(identifier, null(value.type.pointee), value.type.pointee, access_modifier, linkage, False) elif isinstance(value, PointerType): variable = self.gen_global(identifier, null(value.pointee.type), value.pointee.type, access_modifier, linkage, False) elif isinstance(value, FormattedConstant) or isinstance(value, AllocaInstr): variable = self.gen_global(identifier, null(value.type.pointee), value.type.pointee, access_modifier, linkage, False) elif isinstance(value, Constant): variable = self.gen_global(identifier, null(value.type), value.type, access_modifier, linkage, False) else: variable = self.gen_global(identifier, null(value.type), value.type, access_modifier, linkage, False) self.assign_non_constant_global_variable(variable, value) return variable def declare_variable(self, identifier: str, value) -> Optional[AllocaInstr]: variable = self.current_block.get_named_value(identifier) if variable is not None: return None if isinstance(value, AllocaInstr) or isinstance(value, PointerType): variable = value variable.name = identifier elif isinstance(value, CastInstr) and value.opname == "inttoptr": variable = self.builder.alloca(value.type.pointee) variable.name = identifier rial_type = f"{map_llvm_to_type(value.type)}" variable.set_metadata('type', ParserState.module().add_metadata((rial_type,))) self.builder.store(self.builder.load(value), variable) elif isinstance(value, FormattedConstant): variable = self.builder.alloca(value.type.pointee) variable.name = identifier rial_type = f"{map_llvm_to_type(value.type)}" variable.set_metadata('type', ParserState.module().add_metadata((rial_type,))) self.builder.store(self.builder.load(value), variable) elif isinstance(value, Constant): variable = self.builder.alloca(value.type) variable.name = identifier rial_type = f"{map_llvm_to_type(value.type)}" variable.set_metadata('type', ParserState.module().add_metadata((rial_type,))) self.builder.store(value, variable) else: variable = self.builder.alloca(value.type) variable.name = identifier rial_type = f"{map_llvm_to_type(value.type)}" variable.set_metadata('type', ParserState.module().add_metadata((rial_type,))) self.builder.store(value, variable) self.current_block.add_named_value(identifier, variable) return variable def assign_to_variable(self, identifier: Union[str, Any], value): if isinstance(identifier, str): variable = self.get_definition(identifier) else: variable = identifier if variable is None: return None if isinstance(value, AllocaInstr): value = self.builder.load(value) elif isinstance(value, PointerType) and not isinstance(variable, PointerType): value = self.builder.load(value.pointee) self.builder.store(value, variable) return variable def create_block(self, block_name: str, parent: Optional[LLVMBlock] = None, sibling: Optional[LLVMBlock] = None) -> \ Optional[LLVMBlock]: if parent is None and sibling is None and block_name != "entry" and len(self.current_func.basic_blocks) > 0: return None block = self.builder.append_basic_block(block_name) llvmblock = create_llvm_block(block, parent, sibling) return llvmblock def create_loop(self, base_block_name: str, parent: LLVMBlock): (conditional_llvm_block, body_llvm_block, end_llvm_block) = self.create_conditional_block(base_block_name, parent) self.conditional_block = conditional_llvm_block self.end_block = end_llvm_block return conditional_llvm_block, body_llvm_block, end_llvm_block, def create_switch_blocks(self, base_block_name: str, parent: LLVMBlock, count_of_cases: int, default_case: bool) -> \ List[LLVMBlock]: blocks = list() for i in range(0, count_of_cases): blocks.append(self.create_block(f"{base_block_name}.case.{i}", parent, None)) if default_case: blocks.append(self.create_block(f"{base_block_name}.default", parent)) return blocks def create_conditional_block(self, base_block_name: str, parent: LLVMBlock) -> Tuple[ LLVMBlock, LLVMBlock, LLVMBlock]: # Create three blocks, one condition, one body, and one after the loop conditional_llvm_block = self.create_block(f"{base_block_name}.condition", parent, None) body_llvm_block = self.create_block(f"{base_block_name}.body", conditional_llvm_block, None) end_llvm_block = self.create_block(f"{base_block_name}.end", None, parent) return conditional_llvm_block, body_llvm_block, end_llvm_block, def create_conditional_block_with_else(self, base_block_name: str, parent: LLVMBlock): (conditional_block, body_block, else_block) = self.create_conditional_block(base_block_name, parent) # Transform end block into else block else_block.block.name = f"{base_block_name}.else" else_block.sibling = None else_block.parent = conditional_block # Create new end block end_block = self.create_block(f"{base_block_name}.if_else.end", None, conditional_block.parent) return conditional_block, body_block, else_block, end_block def create_jump_if_not_exists(self, target_block: LLVMBlock) -> Optional[Branch]: if self.current_block.block.terminator is None: return self.builder.branch(target_block.block) return None def create_conditional_jump(self, condition, true_block: LLVMBlock, false_block: LLVMBlock, true_branch_weight: int = 50, false_branch_weight: int = 50) -> ConditionalBranch: # Check if condition is a variable, we need to load that for LLVM condition = self.gen_load_if_necessary(condition) cbranch = self.builder.cbranch(condition, true_block.block, false_block.block) cbranch.set_weights([true_branch_weight, false_branch_weight]) return cbranch def create_jump(self, target_block: LLVMBlock): return self.builder.branch(target_block.block) def enter_block(self, llvmblock: LLVMBlock): self.current_block = llvmblock self.builder.position_at_start(self.current_block.block) def enter_block_end(self, llvmblock: LLVMBlock): self.current_block = llvmblock self.builder.position_at_end(self.current_block.block) def create_identified_struct(self, name: str, linkage: str, rial_access_modifier: RIALAccessModifier, base_llvm_structs: List[RIALIdentifiedStructType], body: List[RIALVariable]) -> RIALIdentifiedStructType: # Build normal struct and switch out with RIALStruct struct = ParserState.module().context.get_identified_type(name) rial_struct = RIALIdentifiedStructType(struct.context, struct.name, struct.packed) ParserState.module().context.identified_types[struct.name] = rial_struct struct = rial_struct ParserState.module().structs.append(struct) # Create metadata definition struct_def = StructDefinition(rial_access_modifier) # Build body and body definition props_def = dict() props = list() prop_offset = 0 for deriv in base_llvm_structs: for prop in deriv.definition.properties.values(): props.append(ParserState.map_type_to_llvm(prop[1].rial_type)) props_def[prop[1].name] = (prop_offset, prop[1]) prop_offset += 1 struct_def.base_structs.append(deriv.name) for bod in body: props.append(ParserState.map_type_to_llvm(bod.rial_type)) props_def[bod.name] = (prop_offset, bod) prop_offset += 1 struct.set_body(*tuple(props)) struct.module_name = ParserState.module().name self.current_struct = struct struct_def.properties = props_def # Store def in metadata struct.definition = struct_def return struct def finish_struct(self): self.current_struct = None self.current_func = None self.current_block = None def create_function_with_type(self, name: str, canonical_name: str, ty: FunctionType, linkage: str, calling_convention: str, function_def: FunctionDefinition) -> RIALFunction: """ Creates an IR Function with the specified arguments. NOTHING MORE. :param canonical_name: :param canonical_name: :param function_def: :param name: :param ty: :param linkage: :param calling_convention: :return: """ # Create function with specified linkage (internal -> module only) func = RIALFunction(ParserState.module(), ty, name=name, canonical_name=canonical_name) func.linkage = linkage func.calling_convention = calling_convention func.definition = function_def # Set argument names for i, arg in enumerate(func.args): arg.name = function_def.rial_args[i].name ParserState.module().rial_functions.append(func) return func def create_function_body(self, func: RIALFunction, rial_arg_types: List[str]): self.current_func = func # Create entry block bb = func.append_basic_block("entry") llvm_bb = create_llvm_block(bb) self.current_block = llvm_bb if self.builder is None: self.builder = IRBuilder(bb) self.builder.position_at_start(bb) # Allocate new variables for the passed arguments for i, arg in enumerate(func.args): # Don't copy variables that are a pointer if isinstance(arg.type, PointerType): variable = RIALVariable(arg.name, rial_arg_types[i], arg) else: allocated_arg = self.builder.alloca(arg.type) self.builder.store(arg, allocated_arg) variable = RIALVariable(arg.name, rial_arg_types[i], allocated_arg) self.current_block.add_named_value(arg.name, variable) def finish_current_block(self): if self.current_block.block.terminator is None: self.builder.position_at_end(self.current_block.block) self.builder.ret_void() self.current_block = self.current_block.parent def finish_current_func(self): # If we're in release mode # Reorder all possible allocas to the start of the function if CompilationManager.config.raw_opts.release: entry: Block = self.current_func.entry_basic_block pos = entry.instructions.index( next((instr for instr in reversed(entry.instructions) if isinstance(instr, AllocaInstr)), entry.terminator)) allocas: List[Tuple[AllocaInstr, Block]] = list() for block in self.current_func.blocks: block: Block # Skip first block if block == entry: continue for instr in block.instructions: if isinstance(instr, AllocaInstr): allocas.append((instr, block)) for instr_block in allocas: if instr_block is not None: instr_block[1].instructions.remove(instr_block[0]) entry.instructions.insert(pos, instr_block[0]) pos += 1 # Insert nop if block is empty if len(instr_block[1].instructions) == 0: self.builder.position_before(instr_block[1].terminator) self.gen_no_op() self.current_func = None def create_return_statement(self, statement=VoidType()): if isinstance(statement, VoidType): return self.builder.ret_void() return self.builder.ret(statement) def finish_loop(self): self.conditional_block = None self.end_block = None def create_function_type(self, llvm_return_type: Type, llvm_arg_types: List[Type], var_args: bool): return ir.FunctionType(llvm_return_type, tuple(llvm_arg_types), var_arg=var_args) @contextmanager def create_in_global_ctor(self): current_block = self.current_block current_func = self.current_func current_struct = self.current_struct conditional_block = self.conditional_block end_block = self.end_block pos = self.builder is not None and self.builder._anchor or 0 func = ParserState.module().get_global_safe('global_ctor') if func is None: func_type = self.create_function_type(ir.VoidType(), [], False) func = self.create_function_with_type('global_ctor', 'global_ctor', func_type, "internal", "ccc", FunctionDefinition('void')) self.create_function_body(func, []) struct_type = ir.LiteralStructType([ir.IntType(32), func_type.as_pointer(), ir.IntType(8).as_pointer()]) glob_value = ir.Constant(ir.ArrayType(struct_type, 1), [ir.Constant.literal_struct( [ir.Constant(ir.IntType(32), 65535), func, NULL])]) glob_type = ir.ArrayType(struct_type, 1) self.gen_global("llvm.global_ctors", glob_value, glob_type, RIALAccessModifier.PRIVATE, "appending", False) else: self.builder.position_before(func.entry_basic_block.terminator) self.current_func = func self.current_block = create_llvm_block(func.entry_basic_block) self.current_struct = None self.conditional_block = None self.end_block = None yield self.finish_current_block() self.finish_current_func() self.current_block = current_block self.current_func = current_func self.current_struct = current_struct self.conditional_block = conditional_block self.end_block = end_block self.builder._anchor = pos self.builder._block = self.current_block is not None and self.current_block.block or None return
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)
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))
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)
# Perform the comparison testvar = builder.icmp_signed('<', a_var, b_var) # Make three blocks then_block = f_func.append_basic_block('then') else_block = f_func.append_basic_block('else') merge_block = f_func.append_basic_block('merge') # Emit the branch instruction builder.cbranch(testvar, then_block, else_block) # Generate code in the then-branch builder.position_at_end(then_block) result = builder.add(a_var, b_var) builder.store(result, c_var) builder.branch(merge_block) # Generate code in the else-branch builder.position_at_end(else_block) result = builder.sub(a_var, b_var) builder.store(result, c_var) builder.branch(merge_block) # Emit code after the if-else builder.position_at_end(merge_block) builder.ret(builder.load(c_var)) print(mod) print(":::: RUNNING ::::")
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())
def codegen(self, builder: ir.IRBuilder, ctx: CodegenContext) -> None: value = self.value.codegen(builder, ctx) builder.store(value, ctx.vars[self.var.symbol])
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])
# Part (f) - Global variables from llvmlite.ir import GlobalVariable, VoidType x_var = GlobalVariable(mod, ty_double, 'x') x_var.initializer = Constant(ty_double, 0.0) incr_func = Function(mod, FunctionType(VoidType(), []), name='incr') block = incr_func.append_basic_block("entry") builder = IRBuilder(block) tmp1 = builder.load(x_var) tmp2 = builder.fadd(tmp1, Constant(ty_double, 1.0)) builder.store(tmp2, x_var) builder.ret_void() # Part (g) - JIT import llvmlite.binding as llvm llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() target = llvm.Target.from_default_triple() target_machine = target.create_target_machine() compiled_mod = llvm.parse_assembly(str(mod)) engine = llvm.create_mcjit_compiler(compiled_mod, target_machine)
class CodeGenerator(NodeVisitor): def __init__(self, symbol_table) -> None: # Module is an LLVM construct that contains functions and global variables. # In many ways, it is the top-level structure that the LLVM IR uses to contain # code. It will own the memory for all of the IR that we generate, which is why # the codegen() method returns a raw Value*, rather than a unique_ptr<Value>. self.module = Module() # The Builder object is a helper object that makes it easy to generate LLVM # instructions. Instances of the IRBuilder class template keep track of the # current place to insert instructions and has methods to create new instructions. self.builder = None self.symbol_table = symbol_table self.expr_counter = 0 self.str_counter = 0 self.bool_counter = 0 self.printf_counter = 0 self.func_name = "" self.GLOBAL_MEMORY = {} def _create_instruct(self, typ: str, is_printf: bool = False) -> None: """Create a new Function instruction and attach it to a new Basic Block Entry. Args: typ (str): node type. is_printf (bool, optional): Defaults to False. """ if is_printf or typ in ["String", "ArrayType"]: self.str_counter += 1 self.func_name = f"_{typ}.{self.str_counter}" func_type = FunctionType(VoidType(), []) elif typ == "Boolean": self.bool_counter += 1 self.func_name = f"_{typ}.{self.bool_counter}" func_type = FunctionType(IntType(1), []) else: self.expr_counter += 1 self.func_name = f"_{typ}_Expr.{self.expr_counter}" func_type = FunctionType(DoubleType(), []) main_func = Function(self.module, func_type, self.func_name) bb_entry = main_func.append_basic_block("entry") self.builder = IRBuilder(bb_entry) def visit_Program(self, node: Program) -> None: """Visit the Block node in AST and call it. Args: node (Program): Program node (root) """ self.visit(node.block) def visit_Block(self, node: Block) -> None: """Initializes the method calls according to the nodes represented by the variables and compound declarations. Args: node (Block): the block containing the VAR and BEGIN sections """ for declaration in node.declarations: self.visit(declaration) self.visit(node.compound_statement) def visit_Compound(self, node: Compound) -> None: """Central component that coordinates the compound statements (assigments and writeln statement) and calls the methods according to their type. Args: node (Compound): node containing all of the compound statements (assigments and writeln statement) """ for child in node.children: self.visit(child) def visit_Assign(self, node: Assign) -> None: """Creates the LLVM IR instructions for the expressions, strings or Booleans that are assigned to a variable and adds these to a **auxiliary** GLOBAL MEMORY. Args: node (Assign): node containing the assignment content """ node_type = type(node.right).__name__ if isinstance(node.right, String): self._create_instruct(node_type) self.visit(node.left) instruct = self.visit(node.right) c_str = self.builder.alloca(instruct.type) self.builder.store(instruct, c_str) self.builder.ret_void() else: self._create_instruct(node_type) self.visit(node.left) instruct = self.visit(node.right) self.builder.ret(instruct) self.GLOBAL_MEMORY[node.left.value] = instruct def visit_Var(self, node: Var) -> VarSymbol: """Search for the variable in the Symbol Table and define the Double Type. Args: node (Var): variable token Returns: VarSymbol: a variable symbol with updated type """ var_name = node.value var_symbol = self.symbol_table.get_token(var_name) var_symbol.type = DoubleType() return var_symbol def visit_Num(self, node: Num) -> Constant: """Set the Double Type to a specific number. Args: node (Num): a token represeting a number (constant) Returns: Constant: a LLVM IR Constant representing the number. """ return Constant(DoubleType(), float(node.value)) def visit_BinaryOperator(self, node: BinaryOperator) -> Instruction: """Performs the Binary arithmetic operations and returns a LLVM IR Instruction according to the operation. Args: node (BinaryOperator): node containing the variables (or numbers) and the arithmetic operators Returns: Instruction: LLVM arithmetic instruction """ left = self.visit(node.left) right = self.visit(node.right) if isinstance(left, VarSymbol): left_symbol = self.GLOBAL_MEMORY[left.name] else: left_symbol = left if isinstance(right, VarSymbol): right_symbol = self.GLOBAL_MEMORY[right.name] else: right_symbol = right if node.operator.type == TokenType.PLUS: return self.builder.fadd(left_symbol, right_symbol, "addtmp") elif node.operator.type == TokenType.MINUS: return self.builder.fsub(left_symbol, right_symbol, "subtmp") elif node.operator.type == TokenType.MUL: return self.builder.fmul(left_symbol, right_symbol, "multmp") elif node.operator.type == TokenType.INTEGER_DIV: return self.builder.fdiv(left_symbol, right_symbol, "udivtmp") elif node.operator.type == TokenType.FLOAT_DIV: return self.builder.fdiv(left_symbol, right_symbol, "fdivtmp") def visit_UnaryOperator(self, node: UnaryOperator) -> Constant: """Performs Unary Operations according to the arithmetic operator (PLUS and MINUS) transforming them into a LLVM IR Constant. Args: node (UnaryOperator): node containing the variables (or numbers) and the arithmetic operators (PLUS and MINUS) Returns: Constant: a LLVM IR Constant representing the number or variable. """ operator = node.operator.type if operator == TokenType.PLUS: expression = self.visit(node.expression) return Constant(DoubleType(), float(+expression.constant)) elif operator == TokenType.MINUS: expression = self.visit(node.expression) return Constant(DoubleType(), float(-expression.constant)) def visit_String(self, node: String) -> Constant: """Converts the literal string to an array of characters. Args: node (String): a token represeting a string (literal) Returns: Constant: a constant containing a array of characters """ content = node.value return Constant(ArrayType(IntType(8), len(content)), bytearray(content.encode("utf8"))) def visit_Boolean(self, node: Boolean) -> Constant: """Converts the boolean type to an integer (i1) constant. Args: nodo (Boolean): a token represeting a literal boolean type Returns: Constant: a constant of type IntType (1) representing the Boolean type: 1 = True and 0 = False """ if node.token.type == TokenType.FALSE: return Constant(IntType(1), 0) else: return Constant(IntType(1), 1) def visit_Writeln(self, node: Writeln) -> None: """Converts the contents of the command writeln to LLVM ir code and adds the print call to the operating system. Args: node (Writeln): content passed in the command writeln """ self.printf_counter += 1 output_operation_type = "%s" if isinstance(node.content[0], BinaryOperator): self._create_instruct("BinaryOperator", is_printf=True) writeln_content = self.visit(node.content[0]) if isinstance(writeln_content, VarSymbol): content = self.GLOBAL_MEMORY[writeln_content.name] else: content = writeln_content content_type = type(content.type).__name__ if self.builder.block.is_terminated: self._create_instruct(typ=content_type, is_printf=True) if isinstance(content.type, DoubleType): output_operation_type = "%f" output_format = f"{output_operation_type}\n\0" printf_format = Constant( ArrayType(IntType(8), len(output_format)), bytearray(output_format.encode("utf8")), ) fstr = GlobalVariable(self.module, printf_format.type, name=f"fstr_{self.printf_counter}") fstr.linkage = "internal" fstr.global_constant = True fstr.initializer = printf_format writeln_type = FunctionType(IntType(32), [], var_arg=True) writeln = Function( self.module, writeln_type, name=f"printf_{content_type}_{self.printf_counter}", ) body = self.builder.alloca(content.type) temp_loaded = self.builder.load(body) self.builder.store(temp_loaded, body) void_pointer_type = IntType(8).as_pointer() casted_arg = self.builder.bitcast(fstr, void_pointer_type) self.builder.call(writeln, [casted_arg, body]) self.builder.ret_void() def visit_VarDeclaration(self, node: VarDeclaration) -> None: pass def visit_Type(self, node: Type) -> None: pass def visit_Empty(self, node: Empty) -> None: pass