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 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 __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_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 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 visit_Writeln(self, node: Writeln) -> None: """Converts the contents of the command writeln to LLVM ir code and adds the print call to the operating system. Args: node (Writeln): content passed in the command writeln """ self.printf_counter += 1 output_operation_type = "%s" if isinstance(node.content[0], BinaryOperator): self._create_instruct("BinaryOperator", is_printf=True) writeln_content = self.visit(node.content[0]) if isinstance(writeln_content, VarSymbol): content = self.GLOBAL_MEMORY[writeln_content.name] else: content = writeln_content content_type = type(content.type).__name__ if self.builder.block.is_terminated: self._create_instruct(typ=content_type, is_printf=True) if isinstance(content.type, DoubleType): output_operation_type = "%f" output_format = f"{output_operation_type}\n\0" printf_format = Constant( ArrayType(IntType(8), len(output_format)), bytearray(output_format.encode("utf8")), ) fstr = GlobalVariable(self.module, printf_format.type, name=f"fstr_{self.printf_counter}") fstr.linkage = "internal" fstr.global_constant = True fstr.initializer = printf_format writeln_type = FunctionType(IntType(32), [], var_arg=True) writeln = Function( self.module, writeln_type, name=f"printf_{content_type}_{self.printf_counter}", ) body = self.builder.alloca(content.type) temp_loaded = self.builder.load(body) self.builder.store(temp_loaded, body) void_pointer_type = IntType(8).as_pointer() casted_arg = self.builder.bitcast(fstr, void_pointer_type) self.builder.call(writeln, [casted_arg, body]) self.builder.ret_void()
def 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 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 _create_instruct(self, typ: str, is_printf: bool = False) -> None: """Create a new Function instruction and attach it to a new Basic Block Entry. Args: typ (str): node type. is_printf (bool, optional): Defaults to False. """ if is_printf or typ in ["String", "ArrayType"]: self.str_counter += 1 self.func_name = f"_{typ}.{self.str_counter}" func_type = FunctionType(VoidType(), []) elif typ == "Boolean": self.bool_counter += 1 self.func_name = f"_{typ}.{self.bool_counter}" func_type = FunctionType(IntType(1), []) else: self.expr_counter += 1 self.func_name = f"_{typ}_Expr.{self.expr_counter}" func_type = FunctionType(DoubleType(), []) main_func = Function(self.module, func_type, self.func_name) bb_entry = main_func.append_basic_block("entry") self.builder = IRBuilder(bb_entry)
def 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 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 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()
# hellollvm.py from llvmlite.ir import ( Module, Function, FunctionType, IntType, Constant, IRBuilder ) mod = Module('hello') hello_func = Function(mod, FunctionType(IntType(32), []), name='hello') block = hello_func.append_basic_block('entry') builder = IRBuilder(block) builder.ret(Constant(IntType(32), 37)) # A user-defined function from llvmlite.ir import DoubleType ty_double = DoubleType() dsquared_func = Function(mod, FunctionType(ty_double, [ty_double, ty_double]), name='dsquared') block = dsquared_func.append_basic_block('entry') builder = IRBuilder(block) # Get the function args x, y = dsquared_func.args # Compute temporary values for x*x and y*y xsquared = builder.fmul(x, x) ysquared = builder.fmul(y, y)
# hello_llvm.py from llvmlite.ir import (Module, Function, FunctionType, IntType, IRBuilder, Constant) mod = Module('hello') int_type = IntType(32) hello_func = Function(mod, FunctionType(int_type, []), name='hello') block = hello_func.append_basic_block('entry') builder = IRBuilder(block) builder.ret(Constant(IntType(32), 37)) print(mod)
# A sample of executing an integer < in LLVM from llvmlite.ir import (Module, Function, FunctionType, IntType, IRBuilder) mod = Module('example') # Define a function: # # func lessthan(x int, y int) bool { # return x < y; # } # # Note: The result of boolean operations is a 1-bit integer func = Function(mod, FunctionType(IntType(1), [IntType(32), IntType(32)]), name='lessthan') block = func.append_basic_block("entry") builder = IRBuilder(block) # The icmp_signed() instruction is used for all comparisons on # integers. For floating point, use fcmp_ordered(). The compare operation # takes an operator expressed as a string such as '<', '>', '==', # etc. result = builder.icmp_signed('<', func.args[0], func.args[1]) builder.ret(result) # ---- Test Code
# hellollvm.py from llvmlite.ir import (Module, Function, FunctionType, IntType, Constant, IRBuilder) from llvmlite.ir import DoubleType mod = Module('hello') hello_func = Function(mod, FunctionType(IntType(32), []), name='hello') block = hello_func.append_basic_block('entry') builder = IRBuilder(block) builder.ret(Constant(IntType(32), 37)) ty_double = DoubleType() dsquared_func = Function(mod, FunctionType(ty_double, [ty_double, ty_double]), name='dsquared') block = dsquared_func.append_basic_block('entry') builder = IRBuilder(block) # Get the function args x, y = dsquared_func.args # Compute temporary values for x*x and y*y xsquared = builder.fmul(x, x) ysquared = builder.fmul(y, y) # Sum the values and return the result d2 = builder.fadd(xsquared, ysquared) builder.ret(d2)
def external_function_decl(self, tree: Tree): nodes = tree.children modifier: DeclarationModifier = nodes[0] if isinstance(modifier, MetadataToken): return tree if modifier.static: raise PermissionError("External functions cannot be static") access_modifier: AccessModifier = modifier.access_modifier unsafe: bool = modifier.unsafe name = nodes[2].value llvm_return_type = self.module.get_definition(nodes[1]) if isinstance(llvm_return_type, RIALIdentifiedStructType): return_type = llvm_return_type.name else: return_type = map_shortcut_to_type('.'.join(nodes[1])) # Builtins can be passed as raw values if is_builtin_type(return_type): llvm_return_type = llvm_return_type # Pointer to first element for still normal arrays elif re.match(r".+\[[0-9]+\]$", return_type) is not None: llvm_return_type = llvm_return_type.as_pointer() elif isinstance(llvm_return_type, RIALIdentifiedStructType): llvm_return_type = llvm_return_type.as_pointer() else: llvm_return_type = llvm_return_type # External functions cannot be declared in a struct if self.module.current_struct is not None: log_fail( f"External function {name} cannot be declared inside a class!") raise Discard() args: List[RIALVariable] = self.visit(nodes[3]) llvm_args = list() # Map RIAL args to llvm arg types for arg in args: if arg.name.endswith("..."): continue # Pointer for array and struct types if isinstance(arg.llvm_type, RIALIdentifiedStructType) or isinstance( arg.llvm_type, ArrayType): llvm_type = arg.llvm_type.as_pointer() else: llvm_type = arg.llvm_type llvm_args.append(llvm_type) var_args = len(args) > 0 and args[-1].name.endswith("...") # Hasn't been declared previously, redeclare the function type here func_type = FunctionType(llvm_return_type, llvm_args, var_args) # Create function definition func = RIALFunction(self.module, func_type, name, name) func.linkage = "external" func.calling_convention = "ccc" func.definition = FunctionDefinition(return_type, access_modifier, args, unsafe=True) func.attributes = self.attributes # Update args for i, arg in enumerate(func.args): arg.name = args[i].name args[i].value = arg raise Discard()
def extension_function_decl(self, tree: Tree): nodes = tree.children modifier: DeclarationModifier = nodes[0] if isinstance(modifier, MetadataToken): return tree if modifier.static: raise PermissionError("Extension functions cannot be static") access_modifier: AccessModifier = modifier.access_modifier unsafe: bool = modifier.unsafe linkage = access_modifier.get_linkage() name = nodes[2].value llvm_return_type = self.module.get_definition(nodes[1]) if isinstance(llvm_return_type, RIALIdentifiedStructType): return_type = llvm_return_type.name else: return_type = map_shortcut_to_type('.'.join(nodes[1])) # Builtins can be passed as raw values if is_builtin_type(return_type): llvm_return_type = llvm_return_type # Pointer to first element for still normal arrays elif re.match(r".+\[[0-9]+\]$", return_type) is not None: llvm_return_type = llvm_return_type.as_pointer() elif isinstance(llvm_return_type, RIALIdentifiedStructType): llvm_return_type = llvm_return_type.as_pointer() else: llvm_return_type = llvm_return_type # Extension functions cannot be declared inside other classes. if self.module.current_struct is not None: log_fail( f"Extension function {name} cannot be declared inside another class!" ) raise Discard() args: List[RIALVariable] = list() this_type = self.module.get_definition(nodes[3]) if isinstance(this_type, RIALIdentifiedStructType): rial_type = this_type.name else: rial_type = map_shortcut_to_type('.'.join(nodes[3])) args.append(RIALVariable(nodes[4].value, rial_type, this_type, None)) if isinstance(nodes[5], Tree) and nodes[5].data == "function_decl_args": args.extend(self.visit(nodes[5])) body_start = 6 else: body_start = 5 # If the function has the NoMangleAttribute we need to use the normal name if 'noduplicate' in self.attributes: full_function_name = name else: full_function_name = self.module.get_unique_function_name( name, [arg.rial_type for arg in args]) llvm_args = list() # Map RIAL args to llvm arg types for arg in args: if arg.name.endswith("..."): continue # Builtins can be passed as raw values if is_builtin_type(arg.rial_type): llvm_args.append(arg.llvm_type) # Pointer to first element for still normal arrays elif re.match(r".+\[[0-9]+\]$", arg.rial_type) is not None: llvm_args.append(arg.llvm_type.as_pointer()) elif isinstance(arg.llvm_type, RIALIdentifiedStructType): llvm_args.append(arg.llvm_type.as_pointer()) else: llvm_args.append(arg.llvm_type) # Hasn't been declared previously, redeclare the function type here func_type = FunctionType(llvm_return_type, llvm_args) # Create the actual function in IR func = RIALFunction(self.module, func_type, full_function_name, name) func.linkage = linkage func.calling_convention = self.default_cc func.definition = FunctionDefinition(return_type, access_modifier, args, args[0].rial_type, unsafe) # Update args for i, arg in enumerate(func.args): arg.name = args[i].name args[i].value = arg # If it has no body we can discard it now. if len(nodes) <= body_start: with self.module.create_or_enter_function_body(func): self.module.builder.ret_void() raise Discard() token = nodes[2] metadata_token = MetadataToken(token.type, token.value) metadata_token.metadata["func"] = func metadata_token.metadata["body_start"] = body_start nodes.remove(token) nodes.insert(0, metadata_token) return Tree('function_decl', nodes)
def function_decl(self, tree: Tree): nodes = tree.children modifier: DeclarationModifier = nodes[0] if isinstance(modifier, MetadataToken): return tree access_modifier: AccessModifier = modifier.access_modifier unsafe: bool = modifier.unsafe linkage = access_modifier.get_linkage() name = nodes[2].value llvm_return_type = self.module.get_definition(nodes[1]) if isinstance(llvm_return_type, RIALIdentifiedStructType): return_type = llvm_return_type.name else: return_type = map_shortcut_to_type('.'.join(nodes[1])) # Builtins can be passed as raw values if is_builtin_type(return_type): llvm_return_type = llvm_return_type # Pointer to first element for still normal arrays elif re.match(r".+\[[0-9]+\]$", return_type) is not None: llvm_return_type = llvm_return_type.as_pointer() elif isinstance(llvm_return_type, RIALIdentifiedStructType): llvm_return_type = llvm_return_type.as_pointer() else: llvm_return_type = llvm_return_type args: List[RIALVariable] = list() # Add class as implicit parameter if self.module.current_struct is not None and not modifier.static: args.append( RIALVariable("this", self.module.current_struct.name, self.module.current_struct, None)) args.extend(self.visit(nodes[3])) llvm_args = list() # Map RIAL args to llvm arg types for arg in args: if arg.name.endswith("..."): continue # Pointer for array and struct types if isinstance(arg.llvm_type, RIALIdentifiedStructType) or isinstance( arg.llvm_type, ArrayType): llvm_type = arg.llvm_type.as_pointer() else: llvm_type = arg.llvm_type llvm_args.append(llvm_type) # If the function has the NoMangleAttribute we need to use the normal name if 'noduplicate' in self.attributes: if modifier.static: raise PermissionError( "NoMangle for static function doesn't work") full_function_name = name else: if modifier.static: if self.module.current_struct is None: raise PermissionError( "Static functions can only be declared in types") full_function_name = self.module.get_unique_function_name( f"{self.module.current_struct.name}::{name}", [arg.rial_type for arg in args]) else: full_function_name = self.module.get_unique_function_name( name, [arg.rial_type for arg in args]) # Check if main method if name.endswith("main") and self.module.name.endswith("main"): self.attributes.add('alwaysinline') # Check that main method returns either Int32 or void if return_type != "Int32" and return_type != "Void": log_fail( f"Main method must return an integer status code or void, {return_type} given!" ) # Hasn't been declared previously, redeclare the function type here func_type = FunctionType(llvm_return_type, llvm_args, False) # Create the actual function in IR func = RIALFunction(self.module, func_type, full_function_name, name) func.linkage = linkage func.calling_convention = self.default_cc func.definition = FunctionDefinition( return_type, access_modifier, args, self.module.current_struct is not None and self.module.current_struct or "", unsafe) func.attributes = self.attributes # Update args for i, arg in enumerate(func.args): arg.name = args[i].name args[i].value = arg # If it has no body we do not need to go through it later as it's already declared with this method. if not len(nodes) > 4: with self.module.create_or_enter_function_body(func): self.module.builder.ret_void() raise Discard() token = nodes[2] metadata_token = MetadataToken(token.type, token.value) metadata_token.metadata["func"] = func metadata_token.metadata["body_start"] = 4 nodes.remove(token) nodes.insert(0, metadata_token) return Tree('function_decl', nodes)