class GenerateLLVM(object): def __init__(self, name='module'): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module(name) # self.function = Function(self.module, # FunctionType(void_type, []), # name='main') # self.block = self.function.append_basic_block('entry') self.block = None # self.builder = IRBuilder(self.block) self.builder = None # Dictionary that holds all of the global variable/function # declarations. # Any declaration in the Gone source code is going to get an entry here # self.vars = {} self.globals = {} self.locals = {} # Dictionary that holds all of the temporary variables created in # the intermediate code. For example, if you had an expression # like this: # # a = b + c*d # # The corresponding intermediate code might look like this: # # ('load_int', 'b', 'int_1') # ('load_int', 'c', 'int_2') # ('load_int', 'd', 'int_3') # ('mul_int', 'int_2','int_3','int_4') # ('add_int', 'int_1','int_4','int_5') # ('store_int', 'int_5', 'a') # # The self.temp dictionary below is used to map names such as 'int_1', # 'int_2' to their corresponding LLVM values. Essentially, every time # you make anything in LLVM, it gets stored here. self.temps = {} # Initialize the runtime library functions (see below) self.declare_runtime_library() self.last_branch = None def start_function(self, name, rettypename, parmtypenames): rettype = typemap[rettypename] parmtypes = [typemap[pname] for pname in parmtypenames] # Type.function(rettype, parmtypes, False) func_type = FunctionType(rettype, parmtypes) # Create the function for which we're generating code # Function.new(self.module, func_type, name) self.function = Function(self.module, func_type, name=name) # Make the builder and entry block self.block = self.function.append_basic_block("entry") self.builder = IRBuilder(self.block) # Make the exit block self.exit_block = self.function.append_basic_block("exit") # Clear the local vars and temps self.locals = {} self.temps = {} # Make the return variable if rettype is not void_type: self.locals['return'] = self.builder.alloca(rettype, name="return") # Put an entry in the globals self.globals[name] = self.function def new_basic_block(self, name=''): self.builder = IRBuilder(self.block.instructions) return self.function.append_basic_block(name) def declare_runtime_library(self): # Certain functions such as I/O and string handling are often easier # to implement in an external C library. This method should make # the LLVM declarations for any runtime functions to be used # during code generation. Please note that runtime function # functions are implemented in C in a separate file gonert.c self.runtime = {} # Declare printing functions self.runtime['_print_int'] = Function(self.module, FunctionType( void_type, [int_type]), name="_print_int") self.runtime['_print_float'] = Function(self.module, FunctionType( void_type, [float_type]), name="_print_float") self.runtime['_print_bool'] = Function(self.module, FunctionType( void_type, [int_type]), name="_print_bool") def generate_code(self, ircode): # Given a sequence of SSA intermediate code tuples, generate LLVM # instructions using the current builder (self.builder). Each # opcode tuple (opcode, args) is dispatched to a method of the # form self.emit_opcode(args) for opcode, *args in ircode: if hasattr(self, 'emit_' + opcode): getattr(self, 'emit_' + opcode)(*args) else: print('Warning: No emit_' + opcode + '() method') # Add a return statement. Note, at this point, we don't really have # user-defined functions so this is a bit of hack--it may be removed # later. # self.builder.ret_void() def terminate(self): # Add a return statement. This connects the last block to the exit # block. # The return statement is then emitted if self.last_branch != self.block: self.builder.branch(self.exit_block) self.builder.position_at_end(self.exit_block) if 'return' in self.locals: self.builder.ret(self.builder.load(self.locals['return'])) else: self.builder.ret_void() def add_block(self, name): # Add a new block to the existing function return self.function.append_basic_block(name) def set_block(self, block): # Sets the current block for adding more code self.block = block self.builder.position_at_end(block) def cbranch(self, testvar, true_block, false_block): self.builder.cbranch(self.temps[testvar], true_block, false_block) def branch(self, next_block): if self.last_branch != self.block: self.builder.branch(next_block) self.last_branch = self.block # ---------------------------------------------------------------------- # Opcode implementation. You must implement the opcodes. A few # sample opcodes have been given to get you started. # ---------------------------------------------------------------------- # Creation of literal values. Simply define as LLVM constants. def emit_literal_int(self, value, target): self.temps[target] = Constant(int_type, value) def emit_literal_float(self, value, target): self.temps[target] = Constant(float_type, value) def emit_literal_bool(self, value, target): self.temps[target] = Constant(bool_type, value) # def emit_literal_string(self, value, target): # self.temps[target] = Constant(string_type, value) # Allocation of variables. Declare as global variables and set to # a sensible initial value. def emit_alloc_int(self, name): var = self.builder.alloca(int_type, name=name) var.initializer = Constant(int_type, 0) self.locals[name] = var def emit_alloc_float(self, name): var = self.builder.alloca(float_type, name=name) var.initializer = Constant(float_type, 0) self.locals[name] = var def emit_alloc_bool(self, name): var = self.builder.alloca(bool_type, name=name) var.initializer = Constant(bool_type, 0) self.locals[name] = var def emit_global_int(self, name): var = GlobalVariable(self.module, int_type, name=name) var.initializer = Constant(int_type, 0) self.globals[name] = var def emit_global_float(self, name): var = GlobalVariable(self.module, float_type, name=name) var.initializer = Constant(float_type, 0) self.globals[name] = var def emit_global_bool(self, name): var = GlobalVariable(self.module, bool_type, name=name) var.initializer = Constant(bool_type, 0) self.globals[name] = var # def emit_alloc_string(self, name): # var = GlobalVariable(self.module, string_type, name=name) # var.initializer = Constant(string_type, "") # self.vars[name] = var # Load/store instructions for variables. Load needs to pull a # value from a global variable and store in a temporary. Store # goes in the opposite direction. def lookup_var(self, name): if name in self.locals: return self.locals[name] else: return self.globals[name] def emit_load_int(self, name, target): # print('LOADINT %s, %s' % (name, target)) # print('GLOBALS %s' % self.globals) # print('LOCALS %s' % self.locals) self.temps[target] = self.builder.load(self.lookup_var(name), target) def emit_load_float(self, name, target): self.temps[target] = self.builder.load(self.lookup_var(name), target) def emit_load_bool(self, name, target): self.temps[target] = self.builder.load(self.lookup_var(name), target) def emit_store_int(self, source, target): self.builder.store(self.temps[source], self.lookup_var(target)) def emit_store_float(self, source, target): self.builder.store(self.temps[source], self.lookup_var(target)) def emit_store_bool(self, source, target): self.builder.store(self.temps[source], self.lookup_var(target)) # Binary + operator def emit_add_int(self, left, right, target): self.temps[target] = self.builder.add( self.temps[left], self.temps[right], target) def emit_add_float(self, left, right, target): self.temps[target] = self.builder.fadd( self.temps[left], self.temps[right], target) # Binary - operator def emit_sub_int(self, left, right, target): self.temps[target] = self.builder.sub( self.temps[left], self.temps[right], target) def emit_sub_float(self, left, right, target): self.temps[target] = self.builder.fsub( self.temps[left], self.temps[right], target) # Binary * operator def emit_mul_int(self, left, right, target): self.temps[target] = self.builder.mul( self.temps[left], self.temps[right], target) def emit_mul_float(self, left, right, target): self.temps[target] = self.builder.fmul( self.temps[left], self.temps[right], target) # Binary / operator def emit_div_int(self, left, right, target): self.temps[target] = self.builder.sdiv( self.temps[left], self.temps[right], target) def emit_div_float(self, left, right, target): self.temps[target] = self.builder.fdiv( self.temps[left], self.temps[right], target) # Unary + operator def emit_uadd_int(self, source, target): self.temps[target] = self.builder.add( Constant(int_type, 0), self.temps[source], target) def emit_uadd_float(self, source, target): self.temps[target] = self.builder.fadd( Constant(float_type, 0.0), self.temps[source], target) # Unary - operator def emit_usub_int(self, source, target): self.temps[target] = self.builder.sub( Constant(int_type, 0), self.temps[source], target) def emit_usub_float(self, source, target): self.temps[target] = self.builder.fsub( Constant(float_type, 0.0), self.temps[source], target) # Binary < operator def emit_lt_int(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '<', self.temps[left], self.temps[right], target) def emit_lt_float(self, left, right, target): self.temps[target] = self.builder.fcmp_ordered( '<', self.temps[left], self.temps[right], target) # Binary <= operator def emit_le_int(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '<=', self.temps[left], self.temps[right], target) def emit_le_float(self, left, right, target): self.temps[target] = self.builder.fcmp_ordered( '<=', self.temps[left], self.temps[right], target) # Binary > operator def emit_gt_int(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '>', self.temps[left], self.temps[right], target) def emit_gt_float(self, left, right, target): self.temps[target] = self.builder.fcmp_ordered( '>', self.temps[left], self.temps[right], target) # Binary >= operator def emit_ge_int(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '>=', self.temps[left], self.temps[right], target) def emit_ge_float(self, left, right, target): self.temps[target] = self.builder.fcmp_ordered( '>=', self.temps[left], self.temps[right], target) # Binary == operator def emit_eq_int(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '==', self.temps[left], self.temps[right], target) def emit_eq_bool(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '==', self.temps[left], self.temps[right], target) def emit_eq_float(self, left, right, target): self.temps[target] = self.builder.fcmp_ordered( '==', self.temps[left], self.temps[right], target) # Binary != operator def emit_ne_int(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '!=', self.temps[left], self.temps[right], target) def emit_ne_bool(self, left, right, target): self.temps[target] = self.builder.icmp_signed( '!=', self.temps[left], self.temps[right], target) def emit_ne_float(self, left, right, target): self.temps[target] = self.builder.fcmp_ordered( '!=', self.temps[left], self.temps[right], target) # Binary && operator def emit_and_bool(self, left, right, target): self.temps[target] = self.builder.and_( self.temps[left], self.temps[right], target) # Binary || operator def emit_or_bool(self, left, right, target): self.temps[target] = self.builder.or_( self.temps[left], self.temps[right], target) # Unary ! operator def emit_not_bool(self, source, target): self.temps[target] = self.builder.icmp_signed( '==', self.temps[source], Constant(bool_type, 0), target) # Print statements def emit_print_int(self, source): self.builder.call(self.runtime['_print_int'], [self.temps[source]]) def emit_print_float(self, source): self.builder.call(self.runtime['_print_float'], [self.temps[source]]) def emit_print_bool(self, source): self.builder.call(self.runtime['_print_bool'], [ self.builder.zext(self.temps[source], int_type)]) # Extern function declaration. def emit_extern_func(self, name, rettypename, *parmtypenames): rettype = typemap[rettypename] parmtypes = [typemap[pname] for pname in parmtypenames] func_type = FunctionType(rettype, parmtypes) self.globals[name] = Function(self.module, func_type, name=name) # Call an external function. def emit_call_func(self, funcname, *args): target = args[-1] func = self.globals[funcname] argvals = [self.temps[name] for name in args[:-1]] self.temps[target] = self.builder.call(func, argvals) # Function parameter declarations. Must create as local variables def emit_parm_int(self, name, num): var = self.builder.alloca(int_type, name=name) self.builder.store(self.function.args[num], var) self.locals[name] = var def emit_parm_float(self, name, num): var = self.builder.alloca(float_type, name=name) self.builder.store(self.function.args[num], var) self.locals[name] = var def emit_parm_bool(self, name, num): var = self.builder.alloca(bool_type, name=name) self.builder.store(self.function.args[num], var) self.locals[name] = var # Return statements def emit_return_int(self, source): self.builder.store(self.temps[source], self.locals['return']) self.branch(self.exit_block) def emit_return_float(self, source): self.builder.store(self.temps[source], self.locals['return']) self.branch(self.exit_block) def emit_return_bool(self, source): self.builder.store(self.temps[source], self.locals['return']) self.branch(self.exit_block) def emit_return_void(self): self.branch(self.exit_block)
class GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') self.globals = {} # Initialize the runtime library functions (see below) self.declare_runtime_library() def generate_function(self, name, return_type, arg_types, arg_names, ircode): self.function = Function(self.module, FunctionType(return_type, arg_types), name=name) self.globals[name] = self.function self.block = self.function.append_basic_block('entry') self.builder = IRBuilder(self.block) self.locals = {} self.vars = ChainMap(self.locals, self.globals) # Have to declare local variables for holding function arguments for n, (name, ty) in enumerate(zip(arg_names, arg_types)): self.locals[name] = self.builder.alloca(ty, name=name) self.builder.store(self.function.args[n], self.locals[name]) # Dictionary that holds all of the temporary registers created in # the intermediate code. self.temps = {} # Make the exit block for function return self.return_block = self.function.append_basic_block('return') if return_type != void_type: self.return_var = self.builder.alloca(return_type, name='return') self.generate_code(ircode) if not self.block.is_terminated: self.builder.branch(self.return_block) self.builder.position_at_end(self.return_block) if return_type != void_type: self.builder.ret(self.builder.load(self.return_var)) else: self.builder.ret_void() def declare_runtime_library(self): # Certain functions such as I/O and string handling are often easier # to implement in an external C library. This method should make # the LLVM declarations for any runtime functions to be used # during code generation. Please note that runtime function # functions are implemented in C in a separate file gonert.c self.runtime = {} # Declare printing functions self.runtime['_print_int'] = Function(self.module, FunctionType( void_type, [int_type]), name="_print_int") self.runtime['_print_float'] = Function(self.module, FunctionType( void_type, [float_type]), name="_print_float") self.runtime['_print_byte'] = Function(self.module, FunctionType( void_type, [byte_type]), name="_print_byte") def generate_code(self, ircode): # Given a sequence of SSA intermediate code tuples, generate LLVM # instructions using the current builder (self.builder). Each # opcode tuple (opcode, args) is dispatched to a method of the # form self.emit_opcode(args) # Collect and create all basic blocks labels = [instr[1] for instr in ircode if instr[0] == 'LABEL'] self.basicblocks = { name: self.function.append_basic_block(name) for name in labels } for opcode, *args in ircode: if hasattr(self, 'emit_' + opcode): getattr(self, 'emit_' + opcode)(*args) else: print('Warning: No emit_' + opcode + '() method') # ---------------------------------------------------------------------- # Opcode implementation. You must implement the opcodes. A few # sample opcodes have been given to get you started. # ---------------------------------------------------------------------- # Creation of literal values. Simply define as LLVM constants. def emit_MOVI(self, value, target): self.temps[target] = Constant(int_type, value) def emit_MOVF(self, value, target): self.temps[target] = Constant(float_type, value) pass # You must implement def emit_MOVB(self, value, target): self.temps[target] = Constant(byte_type, value) # Allocation of variables. Declare as global variables and set to # a sensible initial value. def emit_VARI(self, name): var = GlobalVariable(self.module, int_type, name=name) var.initializer = Constant(int_type, 0) self.globals[name] = var def emit_VARF(self, name): var = GlobalVariable(self.module, float_type, name=name) var.initializer = Constant(float_type, 0.0) self.globals[name] = var def emit_VARB(self, name): var = GlobalVariable(self.module, byte_type, name=name) var.initializer = Constant(byte_type, 0) self.globals[name] = var def emit_ALLOCI(self, name): self.locals[name] = self.builder.alloca(int_type, name=name) def emit_ALLOCF(self, name): self.locals[name] = self.builder.alloca(float_type, name=name) def emit_ALLOCB(self, name): self.locals[name] = self.builder.alloca(byte_type, name=name) # Load/store instructions for variables. Load needs to pull a # value from a global variable and store in a temporary. Store # goes in the opposite direction. def emit_LOADI(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) def emit_LOADF(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) def emit_LOADB(self, name, target): self.temps[target] = self.builder.load(self.vars[name], target) pass # You must implement def emit_STOREI(self, source, target): self.builder.store(self.temps[source], self.vars[target]) def emit_STOREF(self, source, target): self.builder.store(self.temps[source], self.vars[target]) def emit_STOREB(self, source, target): self.builder.store(self.temps[source], self.vars[target]) # Binary + operator def emit_ADDI(self, left, right, target): self.temps[target] = self.builder.add(self.temps[left], self.temps[right], target) def emit_ADDF(self, left, right, target): self.temps[target] = self.builder.fadd(self.temps[left], self.temps[right], target) pass # You must implement # Binary - operator def emit_SUBI(self, left, right, target): self.temps[target] = self.builder.sub(self.temps[left], self.temps[right], target) pass # You must implement def emit_SUBF(self, left, right, target): self.temps[target] = self.builder.fsub(self.temps[left], self.temps[right], target) pass # You must implement # Binary * operator def emit_MULI(self, left, right, target): self.temps[target] = self.builder.mul(self.temps[left], self.temps[right], target) pass # You must implement def emit_MULF(self, left, right, target): self.temps[target] = self.builder.fmul(self.temps[left], self.temps[right], target) pass # You must implement # Binary / operator def emit_DIVI(self, left, right, target): self.temps[target] = self.builder.sdiv(self.temps[left], self.temps[right], target) pass # You must implement def emit_DIVF(self, left, right, target): self.temps[target] = self.builder.fdiv(self.temps[left], self.temps[right], target) pass # You must implement def emit_CMPI(self, op, left, right, target): result = self.builder.icmp_signed(op, self.temps[left], self.temps[right], '_temp') self.temps[target] = self.builder.zext(result, int_type, target) def emit_CMPF(self, op, left, right, target): result = self.builder.fcmp_ordered(op, self.temps[left], self.temps[right], '_temp') self.temps[target] = self.builder.zext(result, int_type, target) def emit_CMPB(self, op, left, right, target): result = self.builder.icmp_signed(op, self.temps[left], self.temps[right], '_temp') self.temps[target] = self.builder.zext(result, int_type, target) def emit_AND(self, left, right, target): self.temps[target] = self.builder.and_(self.temps[left], self.temps[right], target) def emit_OR(self, left, right, target): self.temps[target] = self.builder.or_(self.temps[left], self.temps[right], target) def emit_XOR(self, left, right, target): self.temps[target] = self.builder.xor(self.temps[left], self.temps[right], target) # Print statements def emit_PRINTI(self, source): self.builder.call(self.runtime['_print_int'], [self.temps[source]]) def emit_PRINTF(self, source): self.builder.call(self.runtime['_print_float'], [self.temps[source]]) pass # You must implement def emit_PRINTB(self, source): self.builder.call(self.runtime['_print_byte'], [self.temps[source]]) pass # You must implement def emit_LABEL(self, name): self.block = self.basicblocks[name] self.builder.position_at_end(self.block) def emit_CBRANCH(self, test, true_label, false_label): true_block = self.basicblocks[true_label] false_block = self.basicblocks[false_label] self.builder.cbranch(self.builder.trunc(self.temps[test], IntType(1)), true_block, false_block) def emit_BRANCH(self, next_label): target = self.basicblocks[next_label] if not self.block.is_terminated: self.builder.branch(target) def emit_CALL(self, name, *extra): *args, target = extra args = [self.temps[a] for a in args] func = self.vars[name] self.temps[target] = self.builder.call(func, args) def emit_RET(self, source): self.builder.store(self.temps[source], self.return_var) self.builder.branch(self.return_block)
class GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') # All globals variables and function definitions go here self.globals = {} self.blocks = {} # Initialize the runtime library functions (see below) self.declare_runtime_library() def declare_runtime_library(self): # Certain functions such as I/O and string handling are often easier # to implement in an external C library. This method should make # the LLVM declarations for any runtime functions to be used # during code generation. Please note that runtime function # functions are implemented in C in a separate file gonert.c self.runtime = {} # Declare printing functions self.runtime['_print_int'] = Function(self.module, FunctionType( void_type, [int_type]), name="_print_int") self.runtime['_print_float'] = Function(self.module, FunctionType( void_type, [float_type]), name="_print_float") self.runtime['_print_byte'] = Function(self.module, FunctionType( void_type, [byte_type]), name="_print_byte") def generate_code(self, ir_function): # Given a sequence of SSA intermediate code tuples, generate LLVM # instructions using the current builder (self.builder). Each # opcode tuple (opcode, args) is dispatched to a method of the # form self.emit_opcode(args) # print(ir_function) self.function = Function( self.module, FunctionType(LLVM_TYPE_MAPPING[ir_function.return_type], [ LLVM_TYPE_MAPPING[ptype] for _, ptype in ir_function.parameters ]), name=ir_function.name) self.block = self.function.append_basic_block('entry') self.builder = IRBuilder(self.block) # Save the function as a global to be referenced later on another # function self.globals[ir_function.name] = self.function # All local variables are stored here self.locals = {} self.vars = ChainMap(self.locals, self.globals) # Dictionary that holds all of the temporary registers created in # the intermediate code. self.temps = {} # Setup the function parameters for n, (pname, ptype) in enumerate(ir_function.parameters): self.vars[pname] = self.builder.alloca(LLVM_TYPE_MAPPING[ptype], name=pname) self.builder.store(self.function.args[n], self.vars[pname]) # Allocate the return value and return_block if ir_function.return_type: self.vars['return'] = self.builder.alloca( LLVM_TYPE_MAPPING[ir_function.return_type], name='return') self.return_block = self.function.append_basic_block('return') for opcode, *args in ir_function.code: if hasattr(self, 'emit_' + opcode): getattr(self, 'emit_' + opcode)(*args) else: print('Warning: No emit_' + opcode + '() method') if not self.block.is_terminated: self.builder.branch(self.return_block) self.builder.position_at_end(self.return_block) self.builder.ret(self.builder.load(self.vars['return'], 'return')) def get_block(self, block_name): block = self.blocks.get(block_name) if block is None: block = self.function.append_basic_block(block_name) self.blocks[block_name] = block return block # ---------------------------------------------------------------------- # Opcode implementation. You must implement the opcodes. A few # sample opcodes have been given to get you started. # ---------------------------------------------------------------------- # Creation of literal values. Simply define as LLVM constants. def emit_MOV(self, value, target, val_type): self.temps[target] = Constant(val_type, value) emit_MOVI = partialmethod(emit_MOV, val_type=int_type) emit_MOVF = partialmethod(emit_MOV, val_type=float_type) emit_MOVB = partialmethod(emit_MOV, val_type=byte_type) # Allocation of GLOBAL variables. Declare as global variables and set to # a sensible initial value. def emit_VAR(self, name, var_type): var = GlobalVariable(self.module, var_type, name=name) var.initializer = Constant(var_type, 0) self.globals[name] = var emit_VARI = partialmethod(emit_VAR, var_type=int_type) emit_VARF = partialmethod(emit_VAR, var_type=float_type) emit_VARB = partialmethod(emit_VAR, var_type=byte_type) # Allocation of LOCAL variables. Declare as local variables and set to # a sensible initial value. def emit_ALLOC(self, name, var_type): self.locals[name] = self.builder.alloca(var_type, name=name) emit_ALLOCI = partialmethod(emit_ALLOC, var_type=int_type) emit_ALLOCF = partialmethod(emit_ALLOC, var_type=float_type) emit_ALLOCB = partialmethod(emit_ALLOC, var_type=byte_type) # Load/store instructions for variables. Load needs to pull a # value from a global variable and store in a temporary. Store # goes in the opposite direction. def emit_LOADI(self, name, target): self.temps[target] = self.builder.load(self.vars[name], name=target) emit_LOADF = emit_LOADI emit_LOADB = emit_LOADI def emit_STOREI(self, source, target): self.builder.store(self.temps[source], self.vars[target]) emit_STOREF = emit_STOREI emit_STOREB = emit_STOREI # Binary + operator def emit_ADDI(self, left, right, target): self.temps[target] = self.builder.add(self.temps[left], self.temps[right], name=target) def emit_ADDF(self, left, right, target): self.temps[target] = self.builder.fadd(self.temps[left], self.temps[right], name=target) # Binary - operator def emit_SUBI(self, left, right, target): self.temps[target] = self.builder.sub(self.temps[left], self.temps[right], name=target) def emit_SUBF(self, left, right, target): self.temps[target] = self.builder.fsub(self.temps[left], self.temps[right], name=target) # Binary * operator def emit_MULI(self, left, right, target): self.temps[target] = self.builder.mul(self.temps[left], self.temps[right], name=target) def emit_MULF(self, left, right, target): self.temps[target] = self.builder.fmul(self.temps[left], self.temps[right], name=target) # Binary / operator def emit_DIVI(self, left, right, target): self.temps[target] = self.builder.sdiv(self.temps[left], self.temps[right], name=target) def emit_DIVF(self, left, right, target): self.temps[target] = self.builder.fdiv(self.temps[left], self.temps[right], name=target) # Print statements def emit_PRINT(self, source, runtime_name): self.builder.call(self.runtime[runtime_name], [self.temps[source]]) emit_PRINTI = partialmethod(emit_PRINT, runtime_name="_print_int") emit_PRINTF = partialmethod(emit_PRINT, runtime_name="_print_float") emit_PRINTB = partialmethod(emit_PRINT, runtime_name="_print_byte") def emit_CMPI(self, operator, left, right, target): tmp = self.builder.icmp_signed(operator, self.temps[left], self.temps[right], 'tmp') # LLVM compares produce a 1-bit integer as a result. Since our IRcode using integers # for bools, need to sign-extend the result up to the normal int_type to continue # with further processing (otherwise you'll get a LLVM type error). self.temps[target] = self.builder.zext(tmp, int_type, target) def emit_CMPF(self, operator, left, right, target): tmp = self.builder.fcmp_ordered(operator, self.temps[left], self.temps[right], 'tmp') self.temps[target] = self.builder.zext(tmp, int_type, target) emit_CMPB = emit_CMPI # Logical ops def emit_AND(self, left, right, target): self.temps[target] = self.builder.and_(self.temps[left], self.temps[right], target) def emit_OR(self, left, right, target): self.temps[target] = self.builder.or_(self.temps[left], self.temps[right], target) def emit_XOR(self, left, right, target): self.temps[target] = self.builder.xor(self.temps[left], self.temps[right], target) def emit_LABEL(self, lbl_name): self.block = self.get_block(lbl_name) self.builder.position_at_end(self.block) def emit_BRANCH(self, dst_label): if not self.block.is_terminated: self.builder.branch(self.get_block(dst_label)) def emit_CBRANCH(self, test_target, true_label, false_label): true_block = self.get_block(true_label) false_block = self.get_block(false_label) testvar = self.temps[test_target] self.builder.cbranch(self.builder.trunc(testvar, IntType(1)), true_block, false_block) def emit_RET(self, register): self.builder.store(self.temps[register], self.vars['return']) self.builder.branch(self.return_block) def emit_CALL(self, func_name, *registers): # print(self.globals) args = [self.temps[r] for r in registers[:-1]] target = registers[-1] self.temps[target] = self.builder.call(self.globals[func_name], args)
class GenerateLLVM(object): def __init__(self): # Perform the basic LLVM initialization. You need the following parts: # # 1. A top-level Module object # 2. A Function instance in which to insert code # 3. A Builder instance to generate instructions # # Note: For project 5, we don't have any user-defined # functions so we're just going to emit all LLVM code into a top # level function void main() { ... }. This will get changed later. self.module = Module('module') # 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]])
def _build_opr(self, loop_inc: Union[ir.instructions.Instruction, ir.Constant], builder: ir.IRBuilder) -> List[ir.instructions.Instruction]: """Build llvm ir for binary operator If the type of any operand is different from the output type, a type cast from type_cast_llvm dict should be performed. Special calculations on Complx and DComplx output """ assert (loop_inc.type == int_type) if not self.dtype in (DType.Complx, DType.DComplx): left_nums = self.LHS.get_ele(loop_inc, builder) right_nums = self.RHS.get_ele(loop_inc, builder) opr_instr = getattr(builder, biopr_map[self.opr][self.dtype]) if self.opr != BinaryOpr.IDIV: # for IDIV operator, the precision may vary. if self.LHS.dtype != self.dtype: left_nums = build_type_cast(builder, left_nums, self.LHS.dtype, self.dtype) if self.RHS.dtype != self.dtype: right_nums = build_type_cast(builder, right_nums, self.RHS.dtype, self.dtype) elif self.LHS.dtype != self.RHS.dtype: left_nums = build_type_cast(builder, left_nums, self.LHS.dtype, DType.Double) right_nums = build_type_cast(builder, right_nums, self.RHS.dtype, DType.Double) product = [opr_instr(left_nums[0], right_nums[0])] if self.opr == BinaryOpr.IDIV: if self.LHS.dtype != self.RHS.dtype: product_type = DType.Double else: product_type = self.LHS.dtype product = build_type_cast(builder, product, product_type, DType.Int) return product else: if self.LHS.dtype in (DType.Complx, DType.DComplx): left_num_real, left_num_imag = self.LHS.get_ele( loop_inc, builder) else: left_num_real = self.LHS.get_ele(loop_inc, builder)[0] left_num_imag = ir.Constant(type_map_llvm[self.LHS.dtype], 0) if self.LHS.dtype != self.dtype: left_nums = build_type_cast(builder, [left_num_real, left_num_imag], self.LHS.dtype, self.dtype) if self.RHS.dtype in (DType.Complx, DType.DComplx): right_num_real, right_num_imag = self.RHS.get_ele( loop_inc, builder) else: right_num_real = self.RHS.get_ele(loop_inc, builder)[0] right_num_imag = ir.Constant(type_map_llvm[self.RHS.dtype], 0) if self.RHS.dtype != self.dtype: right_nums = build_type_cast(builder, [right_num_real, right_num_imag], self.RHS.dtype, self.dtype) if self.opr == BinaryOpr.ADD or self.opr == BinaryOpr.SUB: opr_instr = getattr(builder, biopr_map[self.opr][self.dtype]) product_real = opr_instr(left_num_real, right_num_real) product_imag = opr_instr(left_num_imag, right_num_imag) elif self.opr == BinaryOpr.MUL: rxr = builder.fmul(left_num_real, right_num_real) rxi = builder.fmul(left_num_real, right_num_imag) ixr = builder.fmul(left_num_imag, right_num_real) ixi = builder.fmul(left_num_imag, right_num_imag) product_real = builder.fsub(rxr, ixi) product_imag = builder.fadd(rxi, ixr) elif self.opr == BinaryOpr.FDIV: denom1 = builder.fmul(right_num_real, right_num_real) denom2 = builder.fmul(right_num_imag, right_num_imag) denom = builder.fadd(denom1, denom2) rxr = builder.fmul(left_num_real, right_num_real) rxi = builder.fmul(left_num_imag, right_num_imag) ixr = builder.fmul(left_num_imag, right_num_real) ixi = builder.fmul(left_num_imag, right_num_imag) product_real = builder.fadd(rxr, ixi) product_imag = builder.fsub(ixr, rxi) product_real = builder.fdiv(product_real, denom) product_imag = builder.fdiv(product_imag, denom) return [product_real, product_imag]
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): 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 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 CodeGenerator(NodeVisitor): def __init__(self, symbol_table) -> None: # Module is an LLVM construct that contains functions and global variables. # In many ways, it is the top-level structure that the LLVM IR uses to contain # code. It will own the memory for all of the IR that we generate, which is why # the codegen() method returns a raw Value*, rather than a unique_ptr<Value>. self.module = Module() # The Builder object is a helper object that makes it easy to generate LLVM # instructions. Instances of the IRBuilder class template keep track of the # current place to insert instructions and has methods to create new instructions. self.builder = None self.symbol_table = symbol_table self.expr_counter = 0 self.str_counter = 0 self.bool_counter = 0 self.printf_counter = 0 self.func_name = "" self.GLOBAL_MEMORY = {} def _create_instruct(self, typ: str, is_printf: bool = False) -> None: """Create a new Function instruction and attach it to a new Basic Block Entry. Args: typ (str): node type. is_printf (bool, optional): Defaults to False. """ if is_printf or typ in ["String", "ArrayType"]: self.str_counter += 1 self.func_name = f"_{typ}.{self.str_counter}" func_type = FunctionType(VoidType(), []) elif typ == "Boolean": self.bool_counter += 1 self.func_name = f"_{typ}.{self.bool_counter}" func_type = FunctionType(IntType(1), []) else: self.expr_counter += 1 self.func_name = f"_{typ}_Expr.{self.expr_counter}" func_type = FunctionType(DoubleType(), []) main_func = Function(self.module, func_type, self.func_name) bb_entry = main_func.append_basic_block("entry") self.builder = IRBuilder(bb_entry) def visit_Program(self, node: Program) -> None: """Visit the Block node in AST and call it. Args: node (Program): Program node (root) """ self.visit(node.block) def visit_Block(self, node: Block) -> None: """Initializes the method calls according to the nodes represented by the variables and compound declarations. Args: node (Block): the block containing the VAR and BEGIN sections """ for declaration in node.declarations: self.visit(declaration) self.visit(node.compound_statement) def visit_Compound(self, node: Compound) -> None: """Central component that coordinates the compound statements (assigments and writeln statement) and calls the methods according to their type. Args: node (Compound): node containing all of the compound statements (assigments and writeln statement) """ for child in node.children: self.visit(child) def visit_Assign(self, node: Assign) -> None: """Creates the LLVM IR instructions for the expressions, strings or Booleans that are assigned to a variable and adds these to a **auxiliary** GLOBAL MEMORY. Args: node (Assign): node containing the assignment content """ node_type = type(node.right).__name__ if isinstance(node.right, String): self._create_instruct(node_type) self.visit(node.left) instruct = self.visit(node.right) c_str = self.builder.alloca(instruct.type) self.builder.store(instruct, c_str) self.builder.ret_void() else: self._create_instruct(node_type) self.visit(node.left) instruct = self.visit(node.right) self.builder.ret(instruct) self.GLOBAL_MEMORY[node.left.value] = instruct def visit_Var(self, node: Var) -> VarSymbol: """Search for the variable in the Symbol Table and define the Double Type. Args: node (Var): variable token Returns: VarSymbol: a variable symbol with updated type """ var_name = node.value var_symbol = self.symbol_table.get_token(var_name) var_symbol.type = DoubleType() return var_symbol def visit_Num(self, node: Num) -> Constant: """Set the Double Type to a specific number. Args: node (Num): a token represeting a number (constant) Returns: Constant: a LLVM IR Constant representing the number. """ return Constant(DoubleType(), float(node.value)) def visit_BinaryOperator(self, node: BinaryOperator) -> Instruction: """Performs the Binary arithmetic operations and returns a LLVM IR Instruction according to the operation. Args: node (BinaryOperator): node containing the variables (or numbers) and the arithmetic operators Returns: Instruction: LLVM arithmetic instruction """ left = self.visit(node.left) right = self.visit(node.right) if isinstance(left, VarSymbol): left_symbol = self.GLOBAL_MEMORY[left.name] else: left_symbol = left if isinstance(right, VarSymbol): right_symbol = self.GLOBAL_MEMORY[right.name] else: right_symbol = right if node.operator.type == TokenType.PLUS: return self.builder.fadd(left_symbol, right_symbol, "addtmp") elif node.operator.type == TokenType.MINUS: return self.builder.fsub(left_symbol, right_symbol, "subtmp") elif node.operator.type == TokenType.MUL: return self.builder.fmul(left_symbol, right_symbol, "multmp") elif node.operator.type == TokenType.INTEGER_DIV: return self.builder.fdiv(left_symbol, right_symbol, "udivtmp") elif node.operator.type == TokenType.FLOAT_DIV: return self.builder.fdiv(left_symbol, right_symbol, "fdivtmp") def visit_UnaryOperator(self, node: UnaryOperator) -> Constant: """Performs Unary Operations according to the arithmetic operator (PLUS and MINUS) transforming them into a LLVM IR Constant. Args: node (UnaryOperator): node containing the variables (or numbers) and the arithmetic operators (PLUS and MINUS) Returns: Constant: a LLVM IR Constant representing the number or variable. """ operator = node.operator.type if operator == TokenType.PLUS: expression = self.visit(node.expression) return Constant(DoubleType(), float(+expression.constant)) elif operator == TokenType.MINUS: expression = self.visit(node.expression) return Constant(DoubleType(), float(-expression.constant)) def visit_String(self, node: String) -> Constant: """Converts the literal string to an array of characters. Args: node (String): a token represeting a string (literal) Returns: Constant: a constant containing a array of characters """ content = node.value return Constant(ArrayType(IntType(8), len(content)), bytearray(content.encode("utf8"))) def visit_Boolean(self, node: Boolean) -> Constant: """Converts the boolean type to an integer (i1) constant. Args: nodo (Boolean): a token represeting a literal boolean type Returns: Constant: a constant of type IntType (1) representing the Boolean type: 1 = True and 0 = False """ if node.token.type == TokenType.FALSE: return Constant(IntType(1), 0) else: return Constant(IntType(1), 1) def visit_Writeln(self, node: Writeln) -> None: """Converts the contents of the command writeln to LLVM ir code and adds the print call to the operating system. Args: node (Writeln): content passed in the command writeln """ self.printf_counter += 1 output_operation_type = "%s" if isinstance(node.content[0], BinaryOperator): self._create_instruct("BinaryOperator", is_printf=True) writeln_content = self.visit(node.content[0]) if isinstance(writeln_content, VarSymbol): content = self.GLOBAL_MEMORY[writeln_content.name] else: content = writeln_content content_type = type(content.type).__name__ if self.builder.block.is_terminated: self._create_instruct(typ=content_type, is_printf=True) if isinstance(content.type, DoubleType): output_operation_type = "%f" output_format = f"{output_operation_type}\n\0" printf_format = Constant( ArrayType(IntType(8), len(output_format)), bytearray(output_format.encode("utf8")), ) fstr = GlobalVariable(self.module, printf_format.type, name=f"fstr_{self.printf_counter}") fstr.linkage = "internal" fstr.global_constant = True fstr.initializer = printf_format writeln_type = FunctionType(IntType(32), [], var_arg=True) writeln = Function( self.module, writeln_type, name=f"printf_{content_type}_{self.printf_counter}", ) body = self.builder.alloca(content.type) temp_loaded = self.builder.load(body) self.builder.store(temp_loaded, body) void_pointer_type = IntType(8).as_pointer() casted_arg = self.builder.bitcast(fstr, void_pointer_type) self.builder.call(writeln, [casted_arg, body]) self.builder.ret_void() def visit_VarDeclaration(self, node: VarDeclaration) -> None: pass def visit_Type(self, node: Type) -> None: pass def visit_Empty(self, node: Empty) -> None: pass
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])