Implement shared object linking.

This commit is contained in:
whitequark 2015-07-29 20:35:16 +03:00
parent 2cd25f85bf
commit e8c107925c
8 changed files with 155 additions and 23 deletions

View File

@ -387,12 +387,17 @@ class Argument(NamedValue):
class Function: class Function:
""" """
A function containing SSA IR. A function containing SSA IR.
:ivar is_internal:
(bool) if True, the function should not be accessible from outside
the module it is contained in
""" """
def __init__(self, typ, name, arguments): def __init__(self, typ, name, arguments):
self.type, self.name = typ, name self.type, self.name = typ, name
self.names, self.arguments, self.basic_blocks = set(), [], [] self.names, self.arguments, self.basic_blocks = set(), [], []
self.set_arguments(arguments) self.set_arguments(arguments)
self.is_internal = False
def _remove_name(self, name): def _remove_name(self, name):
self.names.remove(name) self.names.remove(name)

View File

@ -21,7 +21,6 @@ class Module:
artiq_ir_generator = transforms.ARTIQIRGenerator(engine=engine, module_name=self.name) artiq_ir_generator = transforms.ARTIQIRGenerator(engine=engine, module_name=self.name)
dead_code_eliminator = transforms.DeadCodeEliminator(engine=engine) dead_code_eliminator = transforms.DeadCodeEliminator(engine=engine)
local_access_validator = validators.LocalAccessValidator(engine=engine) local_access_validator = validators.LocalAccessValidator(engine=engine)
llvm_ir_generator = transforms.LLVMIRGenerator(engine=engine, module_name=self.name)
self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine)
self.typedtree = asttyped_rewriter.visit(self.parsetree) self.typedtree = asttyped_rewriter.visit(self.parsetree)
@ -34,7 +33,15 @@ class Module:
self.artiq_ir = artiq_ir_generator.visit(self.typedtree) self.artiq_ir = artiq_ir_generator.visit(self.typedtree)
dead_code_eliminator.process(self.artiq_ir) dead_code_eliminator.process(self.artiq_ir)
# local_access_validator.process(self.artiq_ir) # local_access_validator.process(self.artiq_ir)
self.llvm_ir = llvm_ir_generator.process(self.artiq_ir)
def build_llvm_ir(self, target):
"""Compile the module to LLVM IR for the specified target."""
llvm_ir_generator = transforms.LLVMIRGenerator(module_name=self.name, target=target)
return llvm_ir_generator.process(self.artiq_ir)
def entry_point(self):
"""Return the name of the function that is the entry point of this module."""
return self.name + ".__modinit__"
@classmethod @classmethod
def from_string(cls, source_string, name="input.py", first_line=1, engine=None): def from_string(cls, source_string, name="input.py", first_line=1, engine=None):

View File

@ -0,0 +1,87 @@
import tempfile, subprocess
from llvmlite_artiq import ir as ll, binding as llvm
llvm.initialize()
llvm.initialize_all_targets()
llvm.initialize_all_asmprinters()
class Target:
"""
A description of the target environment where the binaries
generaed by the ARTIQ compiler will be deployed.
:var triple: (string)
LLVM target triple, e.g. ``"or1k"``
:var features: (list of string)
LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]``
:var print_function: (string)
Name of a formatted print functions (with the signature of ``printf``)
provided by the target, e.g. ``"printf"``.
"""
triple = "unknown"
features = []
print_function = "printf"
def __init__(self):
self.llcontext = ll.Context()
def link(self, objects, init_fn):
"""Link the relocatable objects into a shared library for this target."""
files = []
def make_tempfile(data=b""):
f = tempfile.NamedTemporaryFile()
files.append(f)
f.write(data)
f.flush()
return f
output_file = make_tempfile()
cmdline = [self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] + \
[make_tempfile(obj).name for obj in objects] + \
["-o", output_file.name]
linker = subprocess.Popen(cmdline, stderr=subprocess.PIPE)
stdout, stderr = linker.communicate()
if linker.returncode != 0:
raise Exception("Linker invocation failed: " + stderr.decode('utf-8'))
output = output_file.read()
for f in files:
f.close()
return output
def compile(self, module):
"""Compile the module to a relocatable object for this target."""
llmod = module.build_llvm_ir(self)
llparsedmod = llvm.parse_assembly(str(llmod))
llparsedmod.verify()
llpassmgrbuilder = llvm.create_pass_manager_builder()
llpassmgrbuilder.opt_level = 2 # -O2
llpassmgrbuilder.size_level = 1 # -Os
llpassmgr = llvm.create_module_pass_manager()
llpassmgrbuilder.populate(llpassmgr)
llpassmgr.run(llparsedmod)
lltarget = llvm.Target.from_triple(self.triple)
llmachine = lltarget.create_target_machine(
features=",".join(self.features),
reloc="pic", codemodel="default")
return llmachine.emit_object(llparsedmod)
def compile_and_link(self, modules):
return self.link([self.compile(module) for module in modules],
init_fn=modules[0].entry_point())
class NativeTarget(Target):
def __init__(self):
super().__init__()
self.triple = llvm.get_default_triple()
class OR1KTarget(Target):
triple = "or1k-linux"
attributes = ["mul", "div", "ffl1", "cmov", "addc"]
print_function = "log"

View File

@ -2,10 +2,7 @@ import os, sys, fileinput, ctypes
from pythonparser import diagnostic from pythonparser import diagnostic
from llvmlite_artiq import binding as llvm from llvmlite_artiq import binding as llvm
from .. import Module from .. import Module
from ..targets import NativeTarget
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
def main(): def main():
libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY') libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY')
@ -22,14 +19,15 @@ def main():
source = "".join(fileinput.input()) source = "".join(fileinput.input())
source = source.replace("#ARTIQ#", "") source = source.replace("#ARTIQ#", "")
llmod = Module.from_string(source.expandtabs(), engine=engine).llvm_ir mod = Module.from_string(source.expandtabs(), engine=engine)
lltarget = llvm.Target.from_default_triple() target = NativeTarget()
llmachine = lltarget.create_target_machine() llmod = mod.build_llvm_ir(target)
llparsedmod = llvm.parse_assembly(str(llmod)) llparsedmod = llvm.parse_assembly(str(llmod))
llparsedmod.verify() llparsedmod.verify()
llmachine = llvm.Target.from_triple(target.triple).create_target_machine()
lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine) lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine)
lljit.finalize_object()
llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__")) llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__"))
ctypes.CFUNCTYPE(None)(llmain)() ctypes.CFUNCTYPE(None)(llmain)()

View File

@ -2,6 +2,7 @@ import sys, fileinput
from pythonparser import diagnostic from pythonparser import diagnostic
from llvmlite_artiq import ir as ll from llvmlite_artiq import ir as ll
from .. import Module from .. import Module
from ..targets import NativeTarget
def main(): def main():
def process_diagnostic(diag): def process_diagnostic(diag):
@ -12,7 +13,10 @@ def main():
engine = diagnostic.Engine() engine = diagnostic.Engine()
engine.process = process_diagnostic engine.process = process_diagnostic
llmod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine).llvm_ir mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine)
target = NativeTarget()
llmod = mod.build_llvm_ir(target=target)
# Add main so that the result can be executed with lli # Add main so that the result can be executed with lli
llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main") llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main")

View File

@ -0,0 +1,30 @@
import sys, os
from pythonparser import diagnostic
from .. import Module
from ..targets import OR1KTarget
def main():
if not len(sys.argv) > 1:
print("Expected at least one module filename", file=sys.stderr)
exit(1)
def process_diagnostic(diag):
print("\n".join(diag.render()), file=sys.stderr)
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
modules = []
for filename in sys.argv[1:]:
modules.append(Module.from_filename(filename, engine=engine))
llobj = OR1KTarget().compile_and_link(modules)
basename, ext = os.path.splitext(sys.argv[-1])
with open(basename + ".so", "wb") as f:
f.write(llobj)
if __name__ == "__main__":
main()

View File

@ -155,7 +155,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
# Statement visitors # Statement visitors
def visit_function(self, node, is_lambda): def visit_function(self, node, is_lambda, is_internal):
if is_lambda: if is_lambda:
name = "lambda.{}.{}".format(node.loc.line(), node.loc.column()) name = "lambda.{}.{}".format(node.loc.line(), node.loc.column())
typ = node.type.find() typ = node.type.find()
@ -185,6 +185,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name)) optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name))
func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs) func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs)
func.is_internal = is_internal
self.functions.append(func) self.functions.append(func)
old_func, self.current_function = self.current_function, func old_func, self.current_function = self.current_function, func
@ -237,7 +238,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
return self.append(ir.Closure(func, self.current_env)) return self.append(ir.Closure(func, self.current_env))
def visit_FunctionDefT(self, node): def visit_FunctionDefT(self, node):
func = self.visit_function(node, is_lambda=False) func = self.visit_function(node, is_lambda=False, is_internal=len(name) > 2)
self._set_local(node.name, func) self._set_local(node.name, func)
def visit_Return(self, node): def visit_Return(self, node):
@ -614,7 +615,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
# the IR. # the IR.
def visit_LambdaT(self, node): def visit_LambdaT(self, node):
return self.visit_function(node, is_lambda=True) return self.visit_function(node, is_lambda=True, is_internal=True)
def visit_IfExpT(self, node): def visit_IfExpT(self, node):
cond = self.visit(node.test) cond = self.visit(node.test)

View File

@ -8,10 +8,11 @@ from llvmlite_artiq import ir as ll
from .. import types, builtins, ir from .. import types, builtins, ir
class LLVMIRGenerator: class LLVMIRGenerator:
def __init__(self, engine, module_name, context=ll.Context()): def __init__(self, module_name, target):
self.engine = engine self.target = target
self.llcontext = context self.llcontext = target.llcontext
self.llmodule = ll.Module(context=self.llcontext, name=module_name) self.llmodule = ll.Module(context=self.llcontext, name=module_name)
self.llmodule.triple = target.triple
self.llfunction = None self.llfunction = None
self.llmap = {} self.llmap = {}
self.fixups = [] self.fixups = []
@ -135,7 +136,7 @@ class LLVMIRGenerator:
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)]) llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)])
elif name == "llvm.copysign.f64": elif name == "llvm.copysign.f64":
llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()])
elif name == "printf": elif name == self.target.print_function:
llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True)
elif name == "__artiq_personality": elif name == "__artiq_personality":
llty = ll.FunctionType(ll.IntType(32), [], var_arg=True) llty = ll.FunctionType(ll.IntType(32), [], var_arg=True)
@ -161,10 +162,7 @@ class LLVMIRGenerator:
if llfun is None: if llfun is None:
llfun = ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), llfun = ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True),
value.name) value.name)
llfun.linkage = 'internal' return llfun
return llfun
else:
return llfun
else: else:
assert False assert False
@ -184,6 +182,8 @@ class LLVMIRGenerator:
llfunty = ll.FunctionType(args=llargtys, llfunty = ll.FunctionType(args=llargtys,
return_type=self.llty_of_type(func.type.ret, for_return=True)) return_type=self.llty_of_type(func.type.ret, for_return=True))
self.llfunction = ll.Function(self.llmodule, llfunty, func.name) self.llfunction = ll.Function(self.llmodule, llfunty, func.name)
self.llfunction.attributes.add('uwtable')
if func.is_internal:
self.llfunction.linkage = 'internal' self.llfunction.linkage = 'internal'
self.llmap = {} self.llmap = {}
@ -527,7 +527,7 @@ class LLVMIRGenerator:
elif insn.op == "printf": elif insn.op == "printf":
# We only get integers, floats, pointers and strings here. # We only get integers, floats, pointers and strings here.
llargs = map(self.map, insn.operands) llargs = map(self.map, insn.operands)
return self.llbuilder.call(self.llbuiltin("printf"), llargs, return self.llbuilder.call(self.llbuiltin(self.target.print_function), llargs,
name=insn.name) name=insn.name)
elif insn.op == "exncast": elif insn.op == "exncast":
# This is an identity cast at LLVM IR level. # This is an identity cast at LLVM IR level.