def find_struct(struct_name: str) -> Optional[RIALIdentifiedStructType]: # Search with name struct = ParserState.search_structs(struct_name) # Search with current module specifier if struct is None: struct = ParserState.search_structs( f"{ParserState.module().name}:{struct_name}") # Iterate through usings if struct is None: structs_found: List[Tuple] = list() for using in ParserState.module().dependencies: s = ParserState.search_structs(f"{using}:{struct_name}") if s is not None: structs_found.append((using, s)) if len(structs_found) == 0: return None if len(structs_found) > 1: log_fail(f"Multiple declarations found for {struct_name}") log_fail( f"Specify one of them by using {structs_found[0][0]}:{struct_name} for example" ) return None struct = structs_found[0][1] return struct
def external_function_decl(self, tree: Tree): nodes = tree.children access_modifier: RIALAccessModifier = nodes[0].access_modifier unsafe: bool = nodes[0].unsafe linkage = "external" calling_convention = "ccc" return_type = nodes[1].value name = nodes[2].value # External functions cannot be declared in a struct if self.llvmgen.current_struct is not None: log_fail(f"External function {name} cannot be declared inside a class!") raise Discard() args: List[RIALVariable] = list() var_args = False i = 3 while i < len(nodes): if var_args is True: log_fail("PARAMS must be last in arguments") break if nodes[i].type == "PARAMS": var_args = True i += 1 if nodes[i].type == "IDENTIFIER": arg_type = nodes[i].value i += 1 arg_name = nodes[i].value if var_args: arg_name += "..." args.append(RIALVariable(arg_name, arg_type, None)) i += 1 if not unsafe and not self.llvmgen.currently_unsafe: raise PermissionError("Can only declare external functions in unsafe blocks or as unsafe functions.") # Map RIAL args to llvm arg types llvm_args = [arg.llvm_type for arg in args if not arg.name.endswith("...")] # Hasn't been declared previously, redeclare the function type here llvm_return_type = ParserState.map_type_to_llvm(return_type) func_type = self.llvmgen.create_function_type(llvm_return_type, llvm_args, var_args) # Create the actual function in IR func = self.llvmgen.create_function_with_type(name, name, func_type, linkage, calling_convention, FunctionDefinition(return_type, access_modifier, args, "", unsafe)) # Update backing values for i, arg in enumerate(func.args): args[i].backing_value = arg raise Discard()
def compile_ir(self, module: RIALModule) -> ModuleRef: with self.lock: llvm_ir = str(module) try: mod = self.binding.parse_assembly(llvm_ir) mod.verify() except Exception as e: log_fail(llvm_ir) raise e self._optimize_module(mod) return mod
def switch_block(self, tree: Tree): nodes = tree.children parent = self.llvmgen.current_block variable: RIALVariable = self.transform_helper(nodes[0]) end_block = self.llvmgen.create_block( f"{self.llvmgen.current_block.block.name}.end_switch", sibling=self.llvmgen.current_block) self.llvmgen.end_block = end_block switch = self.llvmgen.builder.switch(variable.raw_backing_value, None) collected_empties = list() i = 1 while i < len(nodes): var, block = self.transform_helper(nodes[i]) # Check if just case, collect for future use if block is None: collected_empties.append(var) else: self.llvmgen.enter_block_end(block) # Check if default case and set as default if var is None: if switch.default is not None: log_fail( f"{ParserState.module().name} Duplicate default case in switch statement" ) else: switch.default = block.block else: switch.add_case(var, block.block) # Add all collected cases for collected_empty in collected_empties: switch.add_case(collected_empty, block.block) collected_empties.clear() self.llvmgen.enter_block_end(parent) i += 1 if switch.default is None: switch.default = end_block.block if len(collected_empties) > 0: log_fail("Empty cases in switch statement!") self.llvmgen.end_block = None self.llvmgen.enter_block(end_block)
def map_type_to_llvm_no_pointer(name: str): llvm_type = map_type_to_llvm(name) # Check if builtin type if llvm_type is None: llvm_type = ParserState.find_struct(name) if llvm_type is None: # Arrays match = re.match(r"^([^\[]+)\[([0-9]+)?\]$", name) if match is not None: ty = match.group(1) count = match.group(2) if count is not None: return ir.ArrayType(ParserState.map_type_to_llvm(ty), int(count)) else: return ParserState.map_type_to_llvm(ty).as_pointer() # Function type match = re.match(r"^([^(]+)\(([^,)]+\s*,?\s*)*\)$", name) if match is not None: return_type = "" arg_types = list() var_args = False for i, group in enumerate(match.groups()): if i == 0: return_type = group.strip() elif group == "...": var_args = True elif group is None: break else: arg_types.append(group.strip()) return ir.FunctionType( ParserState.map_type_to_llvm(return_type), [ ParserState.map_type_to_llvm(arg) for arg in arg_types ], var_args) log_fail(f"Referenced unknown type {name}") return None return llvm_type
def constructor_call(self, tree: Tree): nodes = tree.children name = nodes[0] struct = ParserState.find_struct(name) if struct is None: log_fail(f"Instantiation of unknown type {name}") arguments = list() instantiated = self.llvmgen.builder.alloca(struct) instantiated = RIALVariable(name, name, instantiated) arguments.append(instantiated) arguments.extend(self.transform_helper(nodes[1])) llvm_args = [arg.backing_value for arg in arguments] constructor_name = mangle_function_name( "constructor", [arg.llvm_type for arg in arguments], name) try: call_instr = self.llvmgen.gen_function_call([constructor_name], llvm_args) if call_instr is None: log_fail( f"Failed to generate call to function {constructor_name}") return NULL return instantiated except IndexError: log_fail("Missing argument in function call") return NULL
def return_rule(self, tree: Tree): nodes = tree.children if self.llvmgen.current_block.block.terminator is not None: log_fail(f"'return' after return found!") return None variable: RIALVariable = self.transform_helper(nodes[0]) if variable.rial_type != self.llvmgen.current_func.definition.rial_return_type: raise TypeError( variable, self.llvmgen.current_func.definition.rial_return_type) if isinstance(variable.raw_llvm_type, ir.VoidType): self.llvmgen.builder.ret_void() elif variable.is_primitive: self.llvmgen.builder.ret(variable.raw_backing_value) else: self.llvmgen.builder.ret(variable.backing_value) return variable
def function_call(self, tree: Tree): nodes = tree.children full_function_name: str function_name: str # Function call specialisation if isinstance(nodes[0], Tree): return self.visit(nodes[0]) function_name = nodes[0].value arguments: List[RIALVariable] = self.transform_helper(nodes[1]) arg_types = [arg.llvm_type for arg in arguments] try: call_instr = self.llvmgen.gen_function_call([ mangle_function_name(function_name, arg_types), function_name ], [arg.backing_value for arg in arguments]) if call_instr is None: log_fail( f"Failed to generate call to function {function_name}") return NULL rial_func = call_instr.callee if isinstance(rial_func, RIALFunction): return RIALVariable(f"{rial_func.name}_call", rial_func.definition.rial_return_type, call_instr) return RIALVariable(f"{rial_func.name}_call", "Unknown", call_instr) except IndexError: log_fail("Missing argument in function call") return NULL
def nested_function_call(self, tree: Tree): nodes = tree.children i = 0 full_name = "" arguments = list() while i < len(nodes): if not isinstance(nodes[i], Token): break full_name += f".{nodes[i].value}" i += 1 implicit_parameter_name = '.'.join(full_name.split('.')[0:-1]) function_name = full_name.split('.')[-1] implicit_parameter: RIALVariable = self.llvmgen.get_definition( implicit_parameter_name) if implicit_parameter is None: log_fail( f"Could not find implicit parameter {implicit_parameter_name} in function call {full_name}" ) return NULL arguments.append(implicit_parameter) arguments.extend(self.transform_helper(nodes[i])) arg_types = [arg.llvm_type for arg in arguments] mangled_names = list() # Generate mangled names for implicit parameter and derived if implicit_parameter is not None: ty = implicit_parameter.rial_type # Check if it's a builtin type if ty in ParserState.builtin_types: mangled_names.append( mangle_function_name(function_name, arg_types, ty)) else: mangled_names.append( mangle_function_name(function_name, arg_types, ty)) struct = ParserState.find_struct(ty) # Also mangle base structs to see if it's a derived function for base_struct in struct.definition.base_structs: arg_tys = arg_types arg_tys.pop(0) arg_tys.insert(0, base_struct) mangled_names.append( mangle_function_name(function_name, arg_types, base_struct)) else: mangled_names.append(mangle_function_name(function_name, arg_types)) try: call_instr = self.llvmgen.gen_function_call( [*mangled_names, function_name], [arg.backing_value for arg in arguments]) if call_instr is None: log_fail( f"Failed to generate call to function {function_name}") return NULL rial_func = call_instr.callee if isinstance(rial_func, RIALFunction): return RIALVariable(f"{rial_func.name}_call", rial_func.definition.rial_return_type, call_instr) return RIALVariable(f"{rial_func.name}_call", "Unknown", call_instr) except IndexError: log_fail( f"Missing argument in function call to function {function_name}" ) return NULL
def extension_function_decl(self, tree: Tree): nodes = tree.children access_modifier: RIALAccessModifier = nodes[0].access_modifier unsafe: bool = nodes[0].unsafe linkage = access_modifier.get_linkage() calling_convention = self.default_cc return_type = nodes[1].value name = nodes[2].value # Extension functions cannot be declared inside other classes. if self.llvmgen.current_struct is not None: log_fail(f"Extension function {name} cannot be declared inside another class!") raise Discard() if not self.mangling: log_fail(f"Extension function {name} does not qualify for no mangling.") raise Discard() args: List[RIALVariable] = list() this_arg = map_shortcut_to_type(nodes[3].value) has_body = False args.append(RIALVariable(nodes[4].value, nodes[3].value, None)) i = 5 while i < len(nodes): if not isinstance(nodes[i], Token): has_body = True break if nodes[i].type == "IDENTIFIER": arg_type = nodes[i].value i += 1 arg_name = nodes[i].value args.append(RIALVariable(arg_name, arg_type, None)) i += 1 else: break # Map RIAL args to llvm arg types llvm_args = [arg.llvm_type for arg in args if not arg.name.endswith("...")] full_function_name = mangle_function_name(name, llvm_args, this_arg) full_function_name = f"{ParserState.module().name}:{full_function_name}" # Hasn't been declared previously, redeclare the function type here llvm_return_type = ParserState.map_type_to_llvm(return_type) func_type = self.llvmgen.create_function_type(llvm_return_type, llvm_args, False) # Create the actual function in IR func = self.llvmgen.create_function_with_type(full_function_name, name, func_type, linkage, calling_convention, FunctionDefinition(return_type, access_modifier, args, self.llvmgen.current_struct is not None and self.llvmgen.current_struct.name or "", unsafe)) # Update backing values for i, arg in enumerate(func.args): args[i].backing_value = arg if is_builtin_type(this_arg): if this_arg not in ParserState.builtin_types: ParserState.builtin_types[this_arg] = dict() ParserState.builtin_types[this_arg][func.name] = func if not this_arg in ParserState.module().builtin_type_methods: ParserState.module().builtin_type_methods[this_arg] = list() ParserState.module().builtin_type_methods[this_arg].append(func.name) else: struct = ParserState.find_struct(this_arg) if struct is None: log_fail(f"Extension function for non-existing type {this_arg}") ParserState.module().functions.remove(func) raise Discard() struct.definition.functions.append(func.name) if not has_body: raise Discard() token = nodes[2] metadata_token = MetadataToken(token.type, token.value) metadata_token.metadata["func"] = func metadata_token.metadata["body_start"] = i nodes.remove(token) nodes.insert(0, metadata_token) return Tree('function_decl', nodes)
def compiler(): global exceptions exceptions = False if not CompilationManager.config.raw_opts.disable_cache: Cache.load_cache() if CompilationManager.config.raw_opts.file is not None: path = Path(CompilationManager.config.raw_opts.file) else: path = CompilationManager.config.rial_path.joinpath( "builtin").joinpath("start.rial") if not path.exists(): raise FileNotFoundError(str(path)) # Clear global data that can sometimes remain (especially during test execution). context.global_context.scope._useset.clear() context.global_context.identified_types.clear() # Collect all always imported paths builtin_path = str( CompilationManager.config.rial_path.joinpath("builtin").joinpath( "always_imported")) for file in [ join(builtin_path, f) for f in listdir(builtin_path) if isfile(join(builtin_path, f)) ]: CompilationManager.request_file(file) module_name = CompilationManager.mod_name_from_path( CompilationManager.filename_from_path(str(file))) CompilationManager.always_imported.append(module_name) # Request main file CompilationManager.request_file(str(path)) if CompilationManager.config.raw_opts.profile_gil: import gil_load gil_load.init() gil_load.start() futures = list() with concurrent.futures.ThreadPoolExecutor() as executor: while True: for future in futures: if future.done(): futures.remove(future) try: # This timeout is so small, it shouldn't matter. # However it serves a crucial role in regards to Python's GIL. # Setting this on a higher timeout obviously means that there may be some excessive time spent waiting # for nothing to appear. # BUT, setting it to non-blocking means that the GIL never gives any of the compilation threads priority # as well as holding up the internal threading.Lock(s). # So setting this timeout so small means not much time is spent waiting for nothing, but it gives other # things the opportunity to get control of the GIL and execute. path = CompilationManager.files_to_compile.get(timeout=0.01) future = executor.submit(compile_file, path) futures.append(future) except Empty: pass if len(futures) == 0 and CompilationManager.files_to_compile.empty( ): break executor.shutdown(True) CompilationManager.files_to_compile.join() if CompilationManager.config.raw_opts.profile_gil: import gil_load gil_load.stop() print(f"Main Thread ID: {threading.current_thread().ident}") print(gil_load.format(gil_load.get())) if exceptions: return if not CompilationManager.config.raw_opts.disable_cache: Cache.save_cache() execute_stage(Stage.POST_IR_GEN) modules: Dict[str, ModuleRef] = dict() for key, mod in CompilationManager.modules.items(): if not CompilationManager.config.raw_opts.disable_cache: cache_path = str( CompilationManager.get_cache_path_str(key)).replace( ".rial", ".cache") if not Path(cache_path).exists(): CompilationManager.codegen.save_module(mod, cache_path) with run_with_profiling(CompilationManager.filename_from_path(key), ExecutionStep.COMPILE_MOD): try: modules[key] = CompilationManager.codegen.compile_ir(mod) except Exception as e: log_fail(f"Exception when compiling module {mod.name}") log_fail(e) log_fail(traceback.format_exc()) return object_files: List[str] = list() CompilationManager.codegen.generate_final_modules(list(modules.values())) for path in list(modules.keys()): mod = modules[path] if CompilationManager.config.raw_opts.print_ir: ir_file = str( CompilationManager.get_output_path_str(path)).replace( ".rial", ".ll") if check_needs_output(mod.name, ir_file): CompilationManager.codegen.save_ir(ir_file, mod) if CompilationManager.config.raw_opts.print_asm: asm_file = str( CompilationManager.get_cache_path_str(path)).replace( ".rial", ".asm") if check_needs_output(mod.name, asm_file): CompilationManager.codegen.save_assembly(asm_file, mod) if not CompilationManager.config.raw_opts.use_object_files: llvm_bitcode_file = str( CompilationManager.get_output_path_str(path)).replace( ".rial", ".o") if check_needs_output(mod.name, llvm_bitcode_file): CompilationManager.codegen.save_llvm_bitcode( llvm_bitcode_file, mod) object_files.append(llvm_bitcode_file) else: object_file = str( CompilationManager.get_output_path_str(path)).replace( ".rial", ".o") if check_needs_output(mod.name, object_file): CompilationManager.codegen.save_object(object_file, mod) object_files.append(object_file) with run_with_profiling(CompilationManager.config.project_name, ExecutionStep.LINK_EXE): exe_path = str( CompilationManager.config.bin_path.joinpath( f"{CompilationManager.config.project_name}{Platform.get_exe_file_extension()}" )) Linker.link_files( object_files, exe_path, CompilationManager.config.raw_opts.print_link_command, CompilationManager.config.raw_opts.strip)
def compile_file(path: str): global exceptions try: last_modified = os.path.getmtime(path) filename = CompilationManager.filename_from_path(path) if check_cache(path): CompilationManager.finish_file(path) CompilationManager.files_to_compile.task_done() return module_name = CompilationManager.mod_name_from_path(filename) module = CompilationManager.codegen.get_module( module_name, filename, str(CompilationManager.config.source_path)) ParserState.set_module(module) ParserState.set_llvmgen(LLVMGen()) if not ParserState.module().name.startswith( "rial:builtin:always_imported"): ParserState.module().dependencies.extend( CompilationManager.always_imported) # Remove the current module in case it's in the always imported list if module_name in ParserState.module().dependencies: ParserState.module().dependencies.remove(module_name) with run_with_profiling(filename, ExecutionStep.READ_FILE): with open(path, "r") as file: contents = file.read() desugar_transformer = DesugarTransformer() primitive_transformer = PrimitiveASTTransformer() parser = Lark_StandAlone(postlex=Postlexer()) # Parse the file with run_with_profiling(filename, ExecutionStep.PARSE_FILE): try: ast = parser.parse(contents) except Exception as e: log_fail(f"Exception when parsing {filename}") log_fail(e) exceptions = True CompilationManager.finish_file(path) CompilationManager.files_to_compile.task_done() return if CompilationManager.config.raw_opts.print_tokens: print(ast.pretty()) # Generate primitive IR (things we don't need other modules for) with run_with_profiling(filename, ExecutionStep.GEN_IR): ast = desugar_transformer.transform(ast) ast = primitive_transformer.transform(ast) struct_declaration_transformer = StructDeclarationTransformer() function_declaration_transformer = FunctionDeclarationTransformer() global_declaration_transformer = GlobalDeclarationTransformer() transformer = ASTVisitor() with run_with_profiling(filename, ExecutionStep.GEN_IR): ast = struct_declaration_transformer.visit(ast) if ast is not None: ast = function_declaration_transformer.visit(ast) if ast is not None: ast = global_declaration_transformer.visit(ast) # Declarations are all already collected so we can move on. CompilationManager.modules[str(path)] = ParserState.module() CompilationManager.finish_file(path) if ast is not None: transformer.visit(ast) cache_path = str(CompilationManager.get_cache_path_str(path)).replace( ".rial", ".cache") if Path(cache_path).exists( ) and CompilationManager.config.raw_opts.disable_cache: os.remove(cache_path) elif not CompilationManager.config.raw_opts.disable_cache: Cache.cache_module(ParserState.module(), path, cache_path, last_modified) CompilationManager.files_to_compile.task_done() except Exception as e: log_fail("Internal Compiler Error: ") log_fail(traceback.format_exc()) exceptions = True CompilationManager.finish_file(path) CompilationManager.files_to_compile.task_done()
def find_function( full_function_name: str, rial_arg_types: List[str] = None) -> Optional[RIALFunction]: # Check by canonical name if we got args to check if rial_arg_types is not None: func = ParserState.module().get_function(full_function_name) # If couldn't find it, iterate through usings and try to find function if func is None: functions_found: List[Tuple[str, RIALFunction]] = list() for use in ParserState.module().dependencies: path = CompilationManager.path_from_mod_name(use) if path not in CompilationManager.modules: continue module = CompilationManager.modules[path] function = module.get_function(full_function_name) if function is None: continue functions_found.append(( use, function, )) # Check each function if the arguments match the passed arguments if len(functions_found) > 1: for tup in functions_found: function = tup[1] matches = True for i, arg in enumerate(function.definition.rial_args): if arg[0] != rial_arg_types[i]: matches = False break if matches: func = function break # Check for number of functions found elif len(functions_found) == 1: func = functions_found[0][1] if func is not None: # Function is in current module and only a declaration, safe to assume that it's a redeclared function # from another module or originally declared in this module anyways if func.module.name != ParserState.module( ).name and not func.is_declaration: # Function cannot be accessed if: # - Function is not public and # - Function is internal but not in same TLM (top level module) or # - Function is private but not in same module func_def: FunctionDefinition = func.definition if func_def.access_modifier != RIALAccessModifier.PUBLIC and \ ((func_def.access_modifier == RIALAccessModifier.INTERNAL and func.module.name.split(':')[0] != ParserState.module().name.split(':')[0]) or (func_def.access_modifier == RIALAccessModifier.PRIVATE and func.module.name != ParserState.module().name)): log_fail( f"Cannot access method {full_function_name} in module {func.module.name}!" ) return None # Try to find function in current module func: RIALFunction = ParserState.module().get_global_safe( full_function_name) # Try to find function in current module with module specifier if func is None: func = ParserState.module().get_global_safe( f"{ParserState.module().name}:{full_function_name}") # If func isn't in current module if func is None: # If couldn't find it, iterate through usings and try to find function if func is None: functions_found: List[Tuple[str, RIALFunction]] = list() for use in ParserState.module().dependencies: path = CompilationManager.path_from_mod_name(use) if path not in CompilationManager.modules: continue module = CompilationManager.modules[path] function = module.get_global_safe(full_function_name) if function is None: function = module.get_global_safe( f"{use}:{full_function_name}") if function is None: continue functions_found.append(( use, function, )) if len(functions_found) > 1: log_fail( f"Function {full_function_name} has been declared multiple times!" ) log_fail( f"Specify the specific function to use by adding the namespace to the function call" ) log_fail( f"E.g. {functions_found[0][0]}:{full_function_name}()") return None # Check for number of functions found if len(functions_found) == 1: func = functions_found[0][1] if func is not None: # Function is in current module and only a declaration, safe to assume that it's a redeclared function # from another module or originally declared in this module anyways if func.module.name != ParserState.module( ).name and not func.is_declaration: # Function cannot be accessed if: # - Function is not public and # - Function is internal but not in same TLM (top level module) or # - Function is private but not in same module func_def: FunctionDefinition = func.definition if func_def.access_modifier != RIALAccessModifier.PUBLIC and \ ((func_def.access_modifier == RIALAccessModifier.INTERNAL and func.module.name.split(':')[0] != ParserState.module().name.split(':')[0]) or (func_def.access_modifier == RIALAccessModifier.PRIVATE and func.module.name != ParserState.module().name)): log_fail( f"Cannot access method {full_function_name} in module {func.module.name}!" ) return None return func
def break_rule(self, tree: Tree): if self.llvmgen.end_block is None: log_fail(f"'break' outside of loop or conditional block") return None return self.llvmgen.create_jump(self.llvmgen.end_block)
def continue_rule(self, tree: Tree): if self.llvmgen.conditional_block is None: log_fail(f"'continue' outside of loop") return None return self.llvmgen.create_jump(self.llvmgen.conditional_block)
def struct_decl(self, tree: Tree): nodes: List = tree.children access_modifier = nodes[0].access_modifier name = nodes[1].value full_name = f"{ParserState.module().name}:{name}" if ParserState.search_structs(full_name) is not None: log_fail(f"Struct {full_name} has been previously declared!") raise Discard() body: List[RIALVariable] = list() function_decls: List[Tree] = list() bases: List[str] = list() # Find body of struct (variables) i = 2 while i < len(nodes): node: Tree = nodes[i] if isinstance(node, Tree) and node.data == "struct_property_declaration": variable = node.children acc_modifier = variable[0].access_modifier rial_type = variable[1].value variable_name = variable[2].value variable_value = None if len(variable) > 3: variable_value = variable[3] body.append( RIALVariable(variable_name, rial_type, backing_value=variable_value, access_modifier=acc_modifier)) elif isinstance(node, Tree) and node.data == "function_decl": function_decls.append(node) elif isinstance(node, Token) and node.type == "IDENTIFIER": bases.append(node.value) i += 1 base_llvm_structs = list() for base in bases: llvm_struct = ParserState.find_struct(base) if llvm_struct is not None: base_llvm_structs.append(llvm_struct) else: log_fail(f"Derived from undeclared type {base}") base_constructor = Tree('function_decl', [ RIALFunctionDeclarationModifier(access_modifier), Token('IDENTIFIER', "void"), Token('IDENTIFIER', "constructor"), *[ Tree('function_call', [ Token( 'IDENTIFIER', mangle_function_name("constructor", [base], base.name)), Tree('function_args', [ Tree('cast', [ Token('IDENTIFIER', base.name), Tree('var', [Token('IDENTIFIER', "this")]) ]) ]) ]) for base in base_llvm_structs ], *[ Tree('variable_assignment', [ Tree('var', [Token('IDENTIFIER', f"this.{bod.name}")]), Token('ASSIGN', '='), bod.backing_value ]) for bod in body ], Tree('return', [Token('IDENTIFIER', "void")]) ]) function_decls.insert(0, base_constructor) llvm_struct = self.llvmgen.create_identified_struct( full_name, access_modifier.get_linkage(), access_modifier, base_llvm_structs, body) declared_functions = list() # Create functions for function_decl in function_decls: metadata_node = self.fdt.visit(function_decl) if metadata_node is not None: declared_functions.append(metadata_node) try: nodes.remove(function_decl) except ValueError: pass self.llvmgen.finish_struct() node: Token = nodes[1] md_node = MetadataToken(node.type, node.value) md_node.metadata['struct_name'] = full_name md_node.metadata['functions'] = declared_functions nodes.remove(node) nodes.insert(0, md_node) return Tree('struct_decl', nodes)
def function_decl(self, tree: Tree): nodes = tree.children # Function specialisation if isinstance(nodes[0], Tree): return self.visit(nodes[0]) access_modifier: RIALAccessModifier = nodes[0].access_modifier unsafe: bool = nodes[0].unsafe linkage = access_modifier.get_linkage() main_function = False calling_convention = self.default_cc return_type = nodes[1].value name = nodes[2].value args: List[RIALVariable] = list() # Add class as implicit parameter if self.llvmgen.current_struct is not None: args.append(RIALVariable("this", self.llvmgen.current_struct.name, None)) i = 3 has_body = False while i < len(nodes): if not isinstance(nodes[i], Token): has_body = True break if nodes[i].type == "IDENTIFIER": arg_type = nodes[i].value i += 1 arg_name = nodes[i].value args.append(RIALVariable(arg_name, arg_type, None)) i += 1 else: break # Map RIAL args to llvm arg types llvm_args = [arg.llvm_type for arg in args if not arg.name.endswith("...")] # If the function has the NoMangleAttribute we need to use the normal name if not self.mangling: full_function_name = name else: if self.llvmgen.current_struct is not None: full_function_name = mangle_function_name(name, llvm_args, self.llvmgen.current_struct.name) else: full_function_name = mangle_function_name(name, llvm_args) full_function_name = f"{ParserState.module().name}:{full_function_name}" # Check if main method if full_function_name.endswith("main:main") and full_function_name.count(':') == 2: main_function = True # 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!") # Search for function in the archives # func = ParserState.search_function(full_function_name) # Function has been previously declared in other module # if func is not None and (func.module.name != ParserState.module().name or not func.is_declaration): # # Check if either: # # - has no body (cannot redeclare functions) or # # - is already implemented and # # - is either public or # # - internal and # # - is in same package (cannot reimplement functions) # # TODO: LLVM checks this anyways but maybe we wanna do it, too? # log_fail(f"Function {full_function_name} already declared elsewhere") # raise Discard() # Hasn't been declared previously, redeclare the function type here llvm_return_type = ParserState.map_type_to_llvm(return_type) func_type = self.llvmgen.create_function_type(llvm_return_type, llvm_args, False) # Create the actual function in IR func = self.llvmgen.create_function_with_type(full_function_name, name, func_type, linkage, calling_convention, FunctionDefinition(return_type, access_modifier, args, self.llvmgen.current_struct is not None and self.llvmgen.current_struct.name or "", unsafe)) # Update backing values for i, arg in enumerate(func.args): args[i].backing_value = arg if self.llvmgen.current_struct is not None: self.llvmgen.current_struct.definition.functions.append(func.name) # Always inline the main function into the compiler supplied one if main_function: func.attributes.add('alwaysinline') # If it has no body we do not need to go through it later as it's already declared with this method. if not has_body: raise Discard() token = nodes[2] metadata_token = MetadataToken(token.type, token.value) metadata_token.metadata["func"] = func metadata_token.metadata["body_start"] = i nodes.remove(token) nodes.insert(0, metadata_token) return Tree('function_decl', nodes)