import os, sys, tempfile, subprocess, io from artiq.compiler import types, ir from llvmlite import ir as ll, binding as llvm llvm.initialize() llvm.initialize_all_targets() llvm.initialize_all_asmprinters() class RunTool: def __init__(self, pattern, **tempdata): self._pattern = pattern self._tempdata = tempdata self._tempnames = {} self._tempfiles = {} def __enter__(self): for key, data in self._tempdata.items(): if data is None: fd, filename = tempfile.mkstemp() os.close(fd) self._tempnames[key] = filename else: with tempfile.NamedTemporaryFile(delete=False) as f: f.write(data) self._tempnames[key] = f.name cmdline = [] for argument in self._pattern: cmdline.append(argument.format(**self._tempnames)) # https://bugs.python.org/issue17023 windows = os.name == "nt" process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=windows) stdout, stderr = process.communicate() if process.returncode != 0: raise Exception("{} invocation failed: {}". format(cmdline[0], stderr)) self._tempfiles["__stdout__"] = io.StringIO(stdout) for key in self._tempdata: if self._tempdata[key] is None: self._tempfiles[key] = open(self._tempnames[key], "rb") return self._tempfiles def __exit__(self, exc_typ, exc_value, exc_trace): for file in self._tempfiles.values(): file.close() for filename in self._tempnames.values(): os.unlink(filename) def _dump(target, kind, suffix, content): if target is not None: print("====== {} DUMP ======".format(kind.upper()), file=sys.stderr) content_value = content() if isinstance(content_value, str): content_value = bytes(content_value, 'utf-8') if target == "": file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) else: file = open(target + suffix, "wb") file.write(content_value) file.close() print("{} dumped as {}".format(kind, file.name), file=sys.stderr) class Target: """ A description of the target environment where the binaries generated by the ARTIQ compiler will be deployed. :var triple: (string) LLVM target triple, e.g. ``"riscv32"`` :var data_layout: (string) LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"`` :var features: (list of string) LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]`` :var additional_linker_options: (list of string) Linker options for the target in addition to the target-independent ones, e.g. ``["--target2=rel"]`` :var print_function: (string) Name of a formatted print functions (with the signature of ``printf``) provided by the target, e.g. ``"printf"``. :var now_pinning: (boolean) Whether the target implements the now-pinning RTIO optimization. """ triple = "unknown" data_layout = "" features = [] additional_linker_options = [] print_function = "printf" now_pinning = True tool_ld = "ld.lld" tool_strip = "llvm-strip" tool_symbolizer = "llvm-symbolizer" tool_cxxfilt = "llvm-cxxfilt" def __init__(self, subkernel_id=None): self.llcontext = ll.Context() self.subkernel_id = subkernel_id def target_machine(self): lltarget = llvm.Target.from_triple(self.triple) llmachine = lltarget.create_target_machine( features=",".join(["+{}".format(f) for f in self.features]), reloc="pic", codemodel="default", abiname="ilp32d" if isinstance(self, RV32GTarget) else "") llmachine.set_asm_verbosity(True) return llmachine def optimize(self, llmodule): llpassmgr = llvm.create_module_pass_manager() # Register our alias analysis passes. llpassmgr.add_basic_alias_analysis_pass() llpassmgr.add_type_based_alias_analysis_pass() # Start by cleaning up after our codegen and exposing as much # information to LLVM as possible. llpassmgr.add_constant_merge_pass() llpassmgr.add_cfg_simplification_pass() llpassmgr.add_instruction_combining_pass() llpassmgr.add_sroa_pass() llpassmgr.add_dead_code_elimination_pass() llpassmgr.add_function_attrs_pass() llpassmgr.add_global_optimizer_pass() # Now, actually optimize the code. llpassmgr.add_function_inlining_pass(275) llpassmgr.add_ipsccp_pass() llpassmgr.add_instruction_combining_pass() llpassmgr.add_gvn_pass() llpassmgr.add_cfg_simplification_pass() llpassmgr.add_licm_pass() # Clean up after optimizing. llpassmgr.add_dead_arg_elimination_pass() llpassmgr.add_global_dce_pass() llpassmgr.run(llmodule) def compile(self, module): """Compile the module to a relocatable object for this target.""" if os.getenv("ARTIQ_DUMP_SIG"): print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr) print(module, file=sys.stderr) if os.getenv("ARTIQ_IR_NO_LOC") is not None: ir.BasicBlock._dump_loc = False type_printer = types.TypePrinter() suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else "" _dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt", lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir)) llmod = module.build_llvm_ir(self) try: llparsedmod = llvm.parse_assembly(str(llmod)) llparsedmod.verify() except RuntimeError: _dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod)) raise _dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll", lambda: str(llparsedmod)) self.optimize(llparsedmod) _dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll", lambda: str(llparsedmod)) return llparsedmod def assemble(self, llmodule): llmachine = self.target_machine() _dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s", lambda: llmachine.emit_assembly(llmodule)) _dump(os.getenv("ARTIQ_DUMP_OBJ"), "Object file", ".o", lambda: llmachine.emit_object(llmodule)) return llmachine.emit_object(llmodule) def link(self, objects): """Link the relocatable objects into a shared library for this target.""" with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] + self.additional_linker_options + ["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] + ["{{obj{}}}".format(index) for index in range(len(objects))] + ["-x"] + ["-o", "{output}"], output=None, **{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \ as results: library = results["output"].read() _dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".elf", lambda: library) return library def compile_and_link(self, modules): return self.link([self.assemble(self.compile(module)) for module in modules]) def strip(self, library): with RunTool([self.tool_strip, "--strip-debug", "{library}", "-o", "{output}"], library=library, output=None) \ as results: return results["output"].read() def symbolize(self, library, addresses): if addresses == []: return [] # We got a list of return addresses, i.e. addresses of instructions # just after the call. Offset them back to get an address somewhere # inside the call instruction (or its delay slot), since that's what # the backtrace entry should point at. last_inlined = None offset_addresses = [hex(addr - 1) for addr in addresses] with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines", "--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses, library=library) \ as results: lines = iter(results["__stdout__"].read().rstrip().split("\n")) backtrace = [] while True: try: address_or_function = next(lines) except StopIteration: break if address_or_function[:2] == "0x": address = int(address_or_function[2:], 16) + 1 # remove offset function = next(lines) inlined = False else: address = backtrace[-1][4] # inlined function = address_or_function inlined = True location = next(lines) filename, line = location.rsplit(":", 1) if filename == "??" or filename == "": continue if line == "?": line = -1 else: line = int(line) # can't get column out of addr2line D: if inlined: last_inlined.append((filename, line, -1, function, address)) else: last_inlined = [] backtrace.append((filename, line, -1, function, address, last_inlined)) return backtrace def demangle(self, names): if not any(names): return names with RunTool([self.tool_cxxfilt] + names) as results: return results["__stdout__"].read().rstrip().split("\n") class NativeTarget(Target): def __init__(self): super().__init__() self.triple = llvm.get_default_triple() self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data) class RV32IMATarget(Target): triple = "riscv32-unknown-linux" data_layout = "e-m:e-p:32:32-i64:64-n32-S128" features = ["m", "a"] additional_linker_options = ["-m", "elf32lriscv"] print_function = "core_log" now_pinning = True tool_ld = "ld.lld" tool_strip = "llvm-strip" tool_symbolizer = "llvm-symbolizer" tool_cxxfilt = "llvm-cxxfilt" class RV32GTarget(Target): triple = "riscv32-unknown-linux" data_layout = "e-m:e-p:32:32-i64:64-n32-S128" features = ["m", "a", "f", "d"] additional_linker_options = ["-m", "elf32lriscv"] print_function = "core_log" now_pinning = True tool_ld = "ld.lld" tool_strip = "llvm-strip" tool_symbolizer = "llvm-symbolizer" tool_cxxfilt = "llvm-cxxfilt" class CortexA9Target(Target): triple = "armv7-unknown-linux-gnueabihf" data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" features = ["dsp", "fp16", "neon", "vfp3"] additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"] print_function = "core_log" now_pinning = False tool_ld = "ld.lld" tool_strip = "llvm-strip" tool_symbolizer = "llvm-symbolizer" tool_cxxfilt = "llvm-cxxfilt"