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 builder_add( builder: ir.IRBuilder, left: ir.Value, right: ir.Value ) -> R[Pair[BlockBuilder, Value]]: builder_ = BlockBuilder.box(builder) return ( builder_.add(Value.box(left), Value.box(right)), lambda: Pair[BlockBuilder, Value].create( builder_, Value.box(builder.add(left, right)) ), )
def string_to_volpe(b: ir.IRBuilder, string: ir.Value): with options(b, int64) as (ret, phi): ret(int64(0)) character = b.load(b.gep(string, [phi])) with b.if_then(b.icmp_unsigned("!=", character, char(0))): ret(b.add(phi, int64(1))) new_string = string_type.unwrap()(ir.Undefined) new_string = b.insert_value(new_string, string, 0) new_string = b.insert_value(new_string, phi, 1) return new_string
def _load_from_src(self, ind: Union[ir.Constant, ir.Instruction], builder: ir.IRBuilder) -> List[ir.Instruction]: zero = ir.Constant(type_map_llvm[self.dtype], 0) if not self.dtype in (DType.Complx, DType.DComplx): if self.dtype == DType.Int: return [builder.add(zero, self.const)] else: return [builder.fadd(zero, self.const)] else: return [ builder.fadd(zero, self.const_real), builder.fadd(zero, self.const_imag) ]
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 _load_from_alloc(self, index: Union[ir.Constant, ir.Instruction], builder: ir.IRBuilder) -> List[ir.LoadInstr]: if not self.dtype in (DType.Complx, DType.DComplx): self_ptr = builder.gep(self.alloc, [index]) products = [builder.load(self_ptr)] else: cmplx_index = builder.mul(index, ir.Constant(int_type, 2)) self_ptr_real = builder.gep(self.alloc, [cmplx_index]) product_real = builder.load(self_ptr_real) cmplx_index = builder.add(cmplx_index, ir.Constant(int_type, 1)) self_ptr_imag = builder.gep(self.alloc, [cmplx_index]) product_imag = builder.load(self_ptr_imag) products = [product_real, product_imag] return products
def _load_from_src(self, ind: Union[ir.Constant, ir.Instruction], builder: ir.IRBuilder) -> List[ir.Instruction]: if isinstance(self.ind, (ir.Constant, ir.Instruction)): src_index = self.ind if isinstance(self.ind, slice): start, _, step = self.ind.indices(self.src.size) muled = builder.mul(ind, ir.Constant(int_type, step)) src_index = builder.add(muled, ir.Constant(int_type, start)) elif isinstance(self.ind, Node): src_index = self.ind.get_ele(ind, builder)[0] else: src_index_ptr = builder.gep(self.src_inds, [ind]) src_index = builder.load(src_index_ptr) return self.src.get_ele(src_index, builder)
def _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 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()])
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())
loop_test_block = f_func.append_basic_block('looptest') builder.branch(loop_test_block) builder.position_at_end(loop_test_block) # Perform the comparison testvar = builder.icmp_signed('>', builder.load(n_var), Constant(IntType(32), 0)) # Make three blocks loop_body_block = f_func.append_basic_block('loopbody') loop_exit_block = f_func.append_basic_block('loopexit') # Branch based on the test var builder.cbranch(testvar, loop_body_block, loop_exit_block) builder.position_at_end(loop_body_block) tmp = builder.add(builder.load(total_var), builder.load(n_var)) builder.store(tmp, total_var) tmp2 = builder.sub(builder.load(n_var), Constant(IntType(32), 1)) builder.store(tmp2, n_var) builder.branch(loop_test_block) # Emit code in the loop-exit builder.position_at_end(loop_exit_block) builder.ret(builder.load(total_var)) print(mod) print(':::: RUNNING ::::') import llvmlite.binding as llvm
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)
hello_func = Function(mod, FunctionType(int_type, []), name='hello') block = hello_func.append_basic_block('entry') builder = IRBuilder(block) x = builder.alloca(int_type, name='x') y = builder.alloca(int_type, name='y') builder.store(Constant(int_type, 4), x) builder.store(Constant(int_type, 5), y) t1 = builder.load(x) t2 = builder.load(x) t3 = builder.mul(t1, t2) t4 = builder.load(y) t5 = builder.load(y) t6 = builder.mul(t4, t5) t7 = builder.add(t3, t6) d = builder.alloca(int_type, name='d') builder.store(t7, d) builder.call(_print_int, [builder.load(d)]) builder.ret(Constant(int_type, 37)) print(mod) def run_jit(module): 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()
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 dictionary of global declarations # 3. Initialization of runtime functions (for printing) # self.module = Module('module') # Dictionary that holds all of the global variable/function declarations. # Any declaration in the Wabbit source code is going to get an entry here self.globals = {} # Initialize the runtime library functions (see below) self.declare_runtime_library() def declare_runtime_library(self): # Certain functions such as I/O and string handling are often easier # to implement in an external C library. This method should make # the LLVM declarations for any runtime functions to be used # during code generation. Please note that runtime function # functions are implemented in C in a separate file wabbitrt.c self.runtime = {} # Declare runtime functions functions = [ ('_print_int', void_type, [int_type]), ('_print_float', void_type, [float_type]), ('_print_byte', void_type, [int_type]), ('_grow', int_type, [int_type]), ('_peeki', int_type, [int_type]), ('_peekf', float_type, [int_type]), ('_peekb', int_type, [int_type]), ('_pokei', void_type, [int_type, int_type]), ('_pokef', void_type, [int_type, float_type]), ('_pokeb', void_type, [int_type, int_type]), ] for name, rettype, args in functions: self.runtime[name] = Function(self.module, FunctionType(rettype, args), name=name) def declare_function(self, funcname, argtypes, rettype): self.function = Function(self.module, FunctionType(rettype, argtypes), name=funcname) # Insert a reference in global namespace self.globals[funcname] = self.function def generate_function(self, funcname, argnames, ircode): # Generate code for a single Wabbit function. Each opcode # tuple (opcode, args) is dispatched to a method of the form # self.emit_opcode(args). Function should already be declared # using declare_function. self.function = self.globals[funcname] self.block = self.function.append_basic_block('entry') self.builder = IRBuilder(self.block) # Stack of LLVM temporaries self.stack = [] # Dictionary of local variables self.locals = { } for opcode, *opargs in ircode: if hasattr(self, 'emit_'+opcode): getattr(self, 'emit_'+opcode)(*opargs) else: print('Warning: No emit_'+opcode+'() method') # Add a return statement to void functions. if self.function.function_type.return_type == void_type: self.builder.ret_void() # Helper methods for LLVM temporary stack manipulation def push(self, value): self.stack.append(value) def pop(self): return self.stack.pop() # ---------------------------------------------------------------------- # Opcode implementation. You must implement the opcodes. A few # sample opcodes have been given to get you started. # ---------------------------------------------------------------------- # Creation of literal values. Simply define as LLVM constants. def emit_CONSTI(self, value): self.push(Constant(int_type, value)) def emit_CONSTF(self, value): self.push(Constant(float_type, value)) # Allocation of variables. Declare as global variables and set to # a sensible initial value. def emit_VARI(self, name): self.locals[name] = self.builder.alloca(int_type, name=name) def emit_VARF(self, name): self.locals[name] = self.builder.alloca(float_type, name=name) # Load/store instructions for variables. def emit_LOAD(self, name): self.push(self.builder.load(self.locals[name], name)) def emit_STORE(self, name): self.builder.store(self.pop(), self.locals[name]) # Binary + operator def emit_ADDI(self): self.push(self.builder.add(self.pop(), self.pop())) def emit_ADDF(self): self.push(self.builder.fadd(self.pop(), self.pop())) # Binary - operator def emit_SUBI(self): right = self.pop() left = self.pop() self.push(self.builder.sub(left, right)) def emit_SUBF(self): right = self.pop() left = self.pop() self.push(self.builder.fsub(left, right)) # Binary * operator def emit_MULI(self): self.push(self.builder.mul(self.pop(), self.pop())) def emit_MULF(self): self.push(self.builder.fmul(self.pop(), self.pop())) # Binary / operator def emit_DIVI(self): right = self.pop() left = self.pop() self.push(self.builder.sdiv(left, right)) def emit_DIVF(self): right = self.pop() left = self.pop() self.push(self.builder.fdiv(left, right)) # Conversion def emit_ITOF(self): self.push(self.builder.sitofp(self.pop(), float_type)) def emit_FTOI(self): self.push(self.builder.fptosi(self.pop(), int_type)) # Print statements def emit_PRINTI(self): self.builder.call(self.runtime['_print_int'], [self.pop()]) def emit_PRINTF(self): self.builder.call(self.runtime['_print_float'], [self.pop()]) def emit_PRINTB(self): self.builder.call(self.runtime['_print_byte'], [self.pop()]) # Memory statements def emit_GROW(self): self.push(self.builder.call(self.runtime['_grow'], [self.pop()])) def emit_PEEKI(self): self.push(self.builder.call(self.runtime['_peeki'], [self.pop()])) def emit_PEEKF(self): self.push(self.builder.call(self.runtime['_peekf'], [self.pop()])) def emit_PEEKB(self): self.push(self.builder.call(self.runtime['_peekb'], [self.pop()])) def emit_POKEI(self): value = self.pop() addr = self.pop() self.builder.call(self.runtime['_pokei'], [addr, value]) def emit_POKEF(self): value = self.pop() addr = self.pop() self.builder.call(self.runtime['_pokef'], [addr, value]) def emit_POKEB(self): value = self.pop() addr = self.pop() self.builder.call(self.runtime['_pokeb'], [addr, value])
class GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') 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 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)
def compile(self, module: ir.Module, builder: ir.IRBuilder, symbols: SymbolTable) -> ir.Value: bop = self.bop exprL = self.exprL.compile(module, builder, symbols) exprL_type = self.exprL.type_of() exprL_int = exprL_type in IntTypes exprR = self.exprR.compile(module, builder, symbols) exprR_type = self.exprR.type_of() exprR_int = exprR_type in IntTypes if bop == '+': if exprL_int and exprR_int: return builder.add(exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprR_type.ir_type) if exprR_int: exprR = builder.sitofp(exprR, exprL_type.ir_type) return builder.fadd(exprL, exprR) if bop == '-': if exprL_int and exprR_int: return builder.sub(exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.fsub(exprL, exprR) if bop == '*': if exprL_int and exprR_int: return builder.mul(exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.fmul(exprL, exprR) if bop == '/': if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.fdiv(exprL, exprR) if bop == '//': return builder.sdiv(exprL, exprR) if bop == '%': if exprL_int and exprR_int: return builder.srem(exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.frem(exprL, exprR) if bop in ['&', '&&']: return builder.and_(exprL, exprR) if bop in ['|', '||']: return builder.or_(exprL, exprR) if bop == '^': return builder.xor(exprL, exprR) if bop in ['<', '<=', '>=', '>']: if exprL_int and exprR_int: return builder.icmp_signed(bop, exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.fcmp_ordered(bop, exprL, exprR) if bop == '==': if exprL_int and exprR_int: return builder.icmp_signed(bop, exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.fcmp_ordered(bop, exprL, exprR) if bop == '!=': if exprL_int and exprR_int: return builder.icmp_signed(bop, exprL, exprR) if exprL_int: exprL = builder.sitofp(exprL, exprL_type.ir_type) if exprR_int: exprR = builder.sitofp(exprL, exprR_type.ir_type) return builder.fcmp_unordered(bop, exprL, exprR) # if bop == '**': # if bop in ['**', '*', '%', '-', '<<', '>>', '>>>', '<=', '>=', '<', # '>', '&', '^', '|', '==', '!=', '&&', '||', '=', '+=', # '-=', '*=', '&=', '|=', '^=', '<<=', '>>=', '>>>=', '%=']: # data = (exprL, bop, exprR) # if minify: # return "({}{}{})".format(*data) # return "({} {} {})".format(*data) # converter = { # '<=>': '<>=', # '===': '==', # '!==': '!=', # '~=': '~==', # '//=': '/=' # } # if bop in converter.keys(): # data = (exprL, converter[bop], exprR) # if minify: # return "({}{}{})".format(*data) # return "({} {} {})".format(*data) # if bop == '+': # if self.exprL.type_of() == StringType: # return "{}..{}".format(exprL, exprR) # return "{}+{}".format(exprL, exprR) # if bop == '/': # return "({}/double({}))".format(exprL, exprR) # if bop == '//': # return "floor({}/{})".format(exprL, exprR) # if bop == 'is': # return "{} is '{}'".format(exprL, exprR) # if bop == '|>': # return "({}({}))".format(exprR, exprL) # if bop == '**=': # data = (exprL, exprR) # if minify: # return "({0}={0}**{1})".format(*data) # return "({0} = {0} ** {1})".format(*data) # if bop == '/=': # data = (exprL, exprR) # if minify: # return "({0}={0}/double({1}))".format(*data) # return "({0} = {0} / double({1}))".format(*data) WappaException( "FATAL", "Unhandled Binary Operator {}".format(bop), self.tok) return bop
class GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') self.function = Function(self.module, FunctionType(void_type, []), name='main') self.block = self.function.append_basic_block('entry') self.builder = IRBuilder(self.block) # Dictionary that holds all of the global variable/function declarations. # Any declaration in the Gone source code is going to get an entry here self.vars = ChainMap() # Dictionary that holds all of the temporary registers created in # the intermediate code. self.temps = {} self.blocks = { } self.funcs = { } self.current_func = None # Initialize the runtime library functions (see below) self.declare_runtime_library() def declare_runtime_library(self): # Certain functions such as I/O and string handling are often easier # to implement in an external C library. This method should make # the LLVM declarations for any runtime functions to be used # during code generation. Please note that runtime function # functions are implemented in C in a separate file gonert.c self.runtime = {} # Declare printing functions self.runtime['_print_int'] = Function(self.module, FunctionType(void_type, [int_type]), name="_print_int") self.runtime['_print_float'] = Function(self.module, FunctionType(void_type, [float_type]), name="_print_float") self.runtime['_print_byte'] = Function(self.module, FunctionType(void_type, [byte_type]), name="_print_byte") def generate_code(self, ircode): # Given a sequence of SSA intermediate code tuples, generate LLVM # instructions using the current builder (self.builder). Each # opcode tuple (opcode, args) is dispatched to a method of the # form self.emit_opcode(args) # first collect all the blocks for instr in ircode: if instr[0] == 'BLK': self.blocks[instr[1]] = self.function.append_basic_block(instr[1]) for opcode, *args in ircode: if hasattr(self, 'emit_'+opcode): getattr(self, 'emit_'+opcode)(*args) else: print('Warning: No emit_'+opcode+'() method') # Add a return statement. Note, at this point, we don't really have # user-defined functions so this is a bit of hack--it may be removed later. self.builder.ret_void() # ---------------------------------------------------------------------- # Opcode implementation. You must implement the opcodes. A few # sample opcodes have been given to get you started. # ---------------------------------------------------------------------- def emit_FUNCTION_END(self, f_name): if not self.block.is_terminated: self.builder.branch(self.return_block) self.builder.position_at_end(self.return_block) self.builder.ret(self.builder.load(self.return_var)) def emit_FUNCTION(self, f_name, arguments, return_type): arguments = eval(arguments) argtypes = list(map(lambda z: z[0], arguments)) f_func = Function(self.module, FunctionType( type_lookup[return_type], argtypes ), name=f_name) self.current_func = f_func self.block = self.current_func.append_basic_block('entry') self.return_block = self.current_func.append_basic_block('return') self.return_var = self.builder.alloca(type_lookup[return_type], name='return') self.funcs[f_name] = self.current_func def emit_CALL(self, f_name, sources, target): #import pdb #pdb.set_trace() f = self.funcs[f_name] self.temps[target] = self.builder.call(f, [self.temps[r] for r in eval(sources)] ) def emit_RETURN(self, var): self.builder.ret(self.vars[var]) # Creation of literal values. Simply define as LLVM constants. def emit_MOVI(self, value, target): self.temps[target] = Constant(int_type, value) def emit_MOVF(self, value, target): self.temps[target] = Constant(float_type, value) def emit_MOVB(self, value, target): self.temps[target] = Constant(byte_type, value) # Allocation of variables. Declare as global variables and set to # a sensible initial value. def emit_VARI(self, name): var = GlobalVariable(self.module, int_type, name=name) var.initializer = Constant(int_type, 0) self.vars[name] = var def emit_VARF(self, name): var = GlobalVariable(self.module, float_type, name=name) var.initializer = Constant(float_type, 0) self.vars[name] = var def emit_VARB(self, name): var = GlobalVariable(self.module, byte_type, name=name) var.initializer = Constant(byte_type, 0) self.vars[name] = var def emit_ALLOCI(self, name): var = self.builder.alloca(IntType(32), name=name) self.builder.store(Constant(int_type, 0), var) self.vars[name] = var # Load/store instructions for variables. Load needs to pull a # value from a global variable and store in a temporary. Store # goes in the opposite direction. def emit_LOADI(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) emit_LOADB = emit_LOADI emit_LOADF = emit_LOADI def emit_STOREI(self, source, target): self.builder.store(self.temps[source], self.vars[target]) emit_STOREF = emit_STOREI emit_STOREB = emit_STOREI # Binary + operator def emit_ADDI(self, left, right, target): self.temps[target] = self.builder.add(self.temps[left], self.temps[right], target) # Binary + operator def emit_ADDF(self, left, right, target): self.temps[target] = self.builder.fadd(self.temps[left], self.temps[right], target) # Binary - operator def emit_SUBI(self, left, right, target): self.temps[target] = self.builder.sub(self.temps[left], self.temps[right], target) def emit_SUBF(self, left, right, target): self.temps[target] = self.builder.fsub(self.temps[left], self.temps[right], target) # Binary * operator def emit_MULI(self, left, right, target): self.temps[target] = self.builder.mul(self.temps[left], self.temps[right], target) def emit_MULF(self, left, right, target): self.temps[target] = self.builder.fmul(self.temps[left], self.temps[right], target) # Binary / operator def emit_DIVI(self, left, right, target): self.temps[target] = self.builder.sdiv(self.temps[left], self.temps[right], target) def emit_DIVF(self, left, right, target): self.temps[target] = self.builder.fdiv(self.temps[left], self.temps[right], target) def emit_AND(self, left, right, target): self.temps[target] = self.builder.and_(self.temps[left], self.temps[right], target) def emit_OR(self, left, right, target): self.temps[target] = self.builder.or_(self.temps[left], self.temps[right], target) def emit_CMPF(self, op, left, right, target): temp = self.builder.fcmp_ordered(op, self.temps[left], self.temps[right], 'temp') self.temps[target] = self.builder.zext(temp, int_type) def emit_CMPI(self, op, left, right, target): temp = self.builder.icmp_signed(op, self.temps[left], self.temps[right], 'temp') self.temps[target] = self.builder.zext(temp, int_type) def cast_to_int(self, value): return self.builder.zext(value, int_type) def cast_to_byte(self, value): return self.builder.trunc(value, IntType(1)) # Print statements def emit_PRINTI(self, source): self.builder.call(self.runtime['_print_int'], [self.cast_to_int(self.temps[source])]) def emit_PRINTF(self, source): self.builder.call(self.runtime['_print_float'], [self.temps[source]]) def emit_PRINTB(self, source): self.builder.call(self.runtime['_print_byte'], [self.temps[source]]) # conditionals and branches def emit_CJMP(self, target, true_branch, false_branch): self.builder.cbranch(self.cast_to_byte(self.temps[target]), self.blocks[true_branch], self.blocks[false_branch]) def emit_JMP(self, target): self.builder.branch(self.blocks[target]) def emit_BLK(self, target): self.builder.position_at_end(self.blocks[target])
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): self.module = Module('module') self.globals = {} self.blocks = {} self.declare_runtime_library() def declare_runtime_library(self): self.runtime = {} self.runtime['_print_int'] = Function(self.module, FunctionType( void_type, [int_type]), name="_print_int") self.runtime['_print_float'] = Function(self.module, FunctionType( void_type, [float_type]), name="_print_float") self.runtime['_print_byte'] = Function(self.module, FunctionType( void_type, [byte_type]), name="_print_byte") def generate_code(self, ir_function): self.function = Function( self.module, FunctionType(LLVM_TYPE_MAPPING[ir_function.return_type], [ LLVM_TYPE_MAPPING[ptype] for _, ptype in ir_function.parameters ]), name=ir_function.name) self.block = self.function.append_basic_block('entry') self.builder = IRBuilder(self.block) self.globals[ir_function.name] = self.function self.locals = {} self.vars = ChainMap(self.locals, self.globals) self.temps = {} for n, (pname, ptype) in enumerate(ir_function.parameters): self.vars[pname] = self.builder.alloca(LLVM_TYPE_MAPPING[ptype], name=pname) self.builder.store(self.function.args[n], self.vars[pname]) if ir_function.return_type: self.vars['return'] = self.builder.alloca( LLVM_TYPE_MAPPING[ir_function.return_type], name='return') self.return_block = self.function.append_basic_block('return') for opcode, *args in ir_function.code: if hasattr(self, 'emit_' + opcode): getattr(self, 'emit_' + opcode)(*args) else: print('Warning: No emit_' + opcode + '() method') if not self.block.is_terminated: self.builder.branch(self.return_block) self.builder.position_at_end(self.return_block) self.builder.ret(self.builder.load(self.vars['return'], 'return')) def get_block(self, block_name): block = self.blocks.get(block_name) if block is None: block = self.function.append_basic_block(block_name) self.blocks[block_name] = block return block def emit_MOV(self, value, target, val_type): self.temps[target] = Constant(val_type, value) emit_MOVI = partialmethod(emit_MOV, val_type=int_type) emit_MOVF = partialmethod(emit_MOV, val_type=float_type) emit_MOVB = partialmethod(emit_MOV, val_type=byte_type) def emit_VAR(self, name, var_type): var = GlobalVariable(self.module, var_type, name=name) var.initializer = Constant(var_type, 0) self.globals[name] = var emit_VARI = partialmethod(emit_VAR, var_type=int_type) emit_VARF = partialmethod(emit_VAR, var_type=float_type) emit_VARB = partialmethod(emit_VAR, var_type=byte_type) def emit_ALLOC(self, name, var_type): self.locals[name] = self.builder.alloca(var_type, name=name) emit_ALLOCI = partialmethod(emit_ALLOC, var_type=int_type) emit_ALLOCF = partialmethod(emit_ALLOC, var_type=float_type) emit_ALLOCB = partialmethod(emit_ALLOC, var_type=byte_type) def emit_LOADI(self, name, target): self.temps[target] = self.builder.load(self.vars[name], name=target) emit_LOADF = emit_LOADI emit_LOADB = emit_LOADI def emit_STOREI(self, source, target): self.builder.store(self.temps[source], self.vars[target]) emit_STOREF = emit_STOREI emit_STOREB = emit_STOREI def emit_ADDI(self, left, right, target): self.temps[target] = self.builder.add(self.temps[left], self.temps[right], name=target) def emit_ADDF(self, left, right, target): self.temps[target] = self.builder.fadd(self.temps[left], self.temps[right], name=target) def emit_SUBI(self, left, right, target): self.temps[target] = self.builder.sub(self.temps[left], self.temps[right], name=target) def emit_SUBF(self, left, right, target): self.temps[target] = self.builder.fsub(self.temps[left], self.temps[right], name=target) def emit_MULI(self, left, right, target): self.temps[target] = self.builder.mul(self.temps[left], self.temps[right], name=target) def emit_MULF(self, left, right, target): self.temps[target] = self.builder.fmul(self.temps[left], self.temps[right], name=target) def emit_DIVI(self, left, right, target): self.temps[target] = self.builder.sdiv(self.temps[left], self.temps[right], name=target) def emit_DIVF(self, left, right, target): self.temps[target] = self.builder.fdiv(self.temps[left], self.temps[right], name=target) def emit_PRINT(self, source, runtime_name): self.builder.call(self.runtime[runtime_name], [self.temps[source]]) emit_PRINTI = partialmethod(emit_PRINT, runtime_name="_print_int") emit_PRINTF = partialmethod(emit_PRINT, runtime_name="_print_float") emit_PRINTB = partialmethod(emit_PRINT, runtime_name="_print_byte") def emit_CMPI(self, operator, left, right, target): if operator == "=": operator = "==" tmp = self.builder.icmp_signed(operator, self.temps[left], self.temps[right], 'tmp') self.temps[target] = self.builder.zext(tmp, int_type, target) def emit_CMPF(self, operator, left, right, target): if operator == "=": operator = "==" tmp = self.builder.fcmp_ordered(operator, self.temps[left], self.temps[right], 'tmp') self.temps[target] = self.builder.zext(tmp, int_type, target) emit_CMPB = emit_CMPI def emit_AND(self, left, right, target): self.temps[target] = self.builder.and_(self.temps[left], self.temps[right], target) def emit_OR(self, left, right, target): self.temps[target] = self.builder.or_(self.temps[left], self.temps[right], target) def emit_XOR(self, left, right, target): self.temps[target] = self.builder.xor(self.temps[left], self.temps[right], target) def emit_LABEL(self, lbl_name): self.block = self.get_block(lbl_name) self.builder.position_at_end(self.block) def emit_BRANCH(self, dst_label): if not self.block.is_terminated: self.builder.branch(self.get_block(dst_label)) def emit_CBRANCH(self, test_target, true_label, false_label): true_block = self.get_block(true_label) false_block = self.get_block(false_label) testvar = self.temps[test_target] self.builder.cbranch(self.builder.trunc(testvar, IntType(1)), true_block, false_block) def emit_RET(self, register): self.builder.store(self.temps[register], self.vars['return']) self.builder.branch(self.return_block) def emit_CALL(self, func_name, *registers): args = [self.temps[r] for r in registers[:-1]] target = registers[-1] self.temps[target] = self.builder.call(self.globals[func_name], args)
class GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') # 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]])
c_var = builder.alloca(IntType(32), name='c') # 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)
def builder_add(builder: ir.IRBuilder, left: ir.Value, right: ir.Value): builder_ = box_block_ref(builder) return ( builder_.add(box_value(left), box_value(right)), lambda: box_value(builder.add(left, right)), )
def ir_add_int(builder: ir.IRBuilder, args): return builder.add(args[0], args[1])