From 6e01600398262c6939ea084376dd6ffa69b88335 Mon Sep 17 00:00:00 2001 From: Daniel Dunbar Date: Mon, 5 Jan 2009 19:53:30 +0000 Subject: [PATCH] Add prototype ccc rewrite. - Entry point is tools/ccc/xcc until we are a functional replacement for ccc. This is highly experimental (FIXME/LOC ratio of 3.4%), quite crufty, and barely usable (and then only on my specific Darwin). However, many of the right ideas are present, and it already fixes a number of things gcc gets wrong. The major missing component is argument translation for tools (translating driver arguments into cc1/ld/as/etc. arguments). This is a large part of the driver functionality and will probably double the LOC, but my hope is that the current architecture is relatively stable. Documentation & motivation to follow soon... llvm-svn: 61739 --- clang/tools/ccc/ccclib/Arguments.py | 446 ++++++++++++++ clang/tools/ccc/ccclib/Driver.py | 666 +++++++++++++++++++++ clang/tools/ccc/ccclib/Jobs.py | 60 ++ clang/tools/ccc/ccclib/Phases.py | 86 +++ clang/tools/ccc/ccclib/Tools.py | 169 ++++++ clang/tools/ccc/ccclib/Types.py | 123 ++++ clang/tools/ccc/ccclib/Util.py | 52 ++ clang/tools/ccc/ccclib/__init__.py | 0 clang/tools/ccc/test/ccc/Xarch.c | 8 + clang/tools/ccc/test/ccc/argument-types.c | 16 + clang/tools/ccc/test/ccc/hello.c | 7 + clang/tools/ccc/test/ccc/integrated-cpp.c | 3 + clang/tools/ccc/test/ccc/invalid.c | 1 + clang/tools/ccc/test/ccc/phases.c | 46 ++ clang/tools/ccc/test/ccc/universal-hello.c | 7 + clang/tools/ccc/xcc | 12 + 16 files changed, 1702 insertions(+) create mode 100644 clang/tools/ccc/ccclib/Arguments.py create mode 100644 clang/tools/ccc/ccclib/Driver.py create mode 100644 clang/tools/ccc/ccclib/Jobs.py create mode 100644 clang/tools/ccc/ccclib/Phases.py create mode 100644 clang/tools/ccc/ccclib/Tools.py create mode 100644 clang/tools/ccc/ccclib/Types.py create mode 100644 clang/tools/ccc/ccclib/Util.py create mode 100644 clang/tools/ccc/ccclib/__init__.py create mode 100644 clang/tools/ccc/test/ccc/Xarch.c create mode 100644 clang/tools/ccc/test/ccc/argument-types.c create mode 100644 clang/tools/ccc/test/ccc/hello.c create mode 100644 clang/tools/ccc/test/ccc/integrated-cpp.c create mode 100644 clang/tools/ccc/test/ccc/invalid.c create mode 100644 clang/tools/ccc/test/ccc/phases.c create mode 100644 clang/tools/ccc/test/ccc/universal-hello.c create mode 100755 clang/tools/ccc/xcc diff --git a/clang/tools/ccc/ccclib/Arguments.py b/clang/tools/ccc/ccclib/Arguments.py new file mode 100644 index 000000000000..ef8466fd6232 --- /dev/null +++ b/clang/tools/ccc/ccclib/Arguments.py @@ -0,0 +1,446 @@ +class Option(object): + """Root option class.""" + def __init__(self, name): + self.name = name + + def accept(self, index, arg, it): + """accept(index, arg, iterator) -> Arg or None + + Accept the argument at the given index, returning an Arg, or + return None if the option does not accept this argument. + + May raise MissingArgumentError. + """ + abstract + + def __repr__(self): + return '<%s name=%r>' % (self.__class__.__name__, + self.name) + +class FlagOption(Option): + """An option which takes no arguments.""" + + def accept(self, index, arg, it): + if arg == self.name: + return Arg(index, self) + +class JoinedOption(Option): + """An option which literally prefixes its argument.""" + + def accept(self, index, arg, it): + if arg.startswith(self.name): + return JoinedValueArg(index, self) + +class SeparateOption(Option): + """An option which is followed by its value.""" + + def accept(self, index, arg, it): + if arg == self.name: + try: + _,value = it.next() + except StopIteration: + raise MissingArgumentError,self + return SeparateValueArg(index, self) + +class MultiArgOption(Option): + """An option which takes multiple arguments.""" + + def __init__(self, name, numArgs): + assert numArgs > 1 + super(MultiArgOption, self).__init__(name) + self.numArgs = numArgs + + def accept(self, index, arg, it): + if arg.startswith(self.name): + try: + values = [it.next()[1] for i in range(self.numArgs)] + except StopIteration: + raise MissingArgumentError,self + return MultipleValuesArg(index, self) + +class JoinedOrSeparateOption(Option): + """An option which either literally prefixes its value or is + followed by an value.""" + + def accept(self, index, arg, it): + if arg.startswith(self.name): + if len(arg) != len(self.name): # Joined case + return JoinedValueArg(index, self) + else: + try: + _,value = it.next() + except StopIteration: + raise MissingArgumentError,self + return SeparateValueArg(index, self) + +class JoinedAndSeparateOption(Option): + """An option which literally prefixes its value and is followed by + an value.""" + + def accept(self, index, arg, it): + if arg.startswith(self.name): + try: + _,value = it.next() + except StopIteration: + raise MissingArgumentError,self + return JoinedAndSeparateValuesArg(index, self) + +### + +class Arg(object): + """Arg - Base class for actual driver arguments.""" + def __init__(self, index, opt): + self.index = index + self.opt = opt + + def __repr__(self): + return '<%s index=%r opt=%r>' % (self.__class__.__name__, + self.index, + self.opt) + + def render(self, args): + """render(args) -> [str] + + Map the argument into a list of actual program arguments, + given the source argument array.""" + assert self.opt + return [self.opt.name] + +class ValueArg(Arg): + """ValueArg - An instance of an option which has an argument.""" + + def getValue(self, args): + abstract + + def setValue(self, args, value): + abstract + +class UnknownArg(ValueArg): + def __init__(self, index): + super(UnknownArg, self).__init__(index, None) + + def getValue(self, args): + return args[self.index] + + def setValue(self, args, value): + args[self.index] = value + + def render(self, args): + return [args[self.index]] + +class JoinedValueArg(ValueArg): + def getValue(self, args): + return args[self.index][len(self.opt.name):] + + def setValue(self, args, value): + assert self.opt.name == args[self.index][:len(self.opt.name)] + args[self.index] = self.opt.name + value + + def render(self, args): + return [self.opt.name + self.getValue(args)] + +class SeparateValueArg(ValueArg): + def getValue(self, args): + return args[self.index+1] + + def setValue(self, args, value): + args[self.index+1] = value + + def render(self, args): + return [self.opt.name, self.getValue(args)] + +class MultipleValuesArg(Arg): + def getValues(self, args): + return args[self.index + 1:self.index + 1 + self.opt.numArgs] + + def setValues(self, args, value): + assert self.opt.numArgs == len(value) + args[self.index + 1:self.index + 1 + self.opt.numArgs] = value + + def render(self, args): + return [self.opt.name] + self.getValues(args) + +# FIXME: Man, this is lame. It is only used by -Xarch. Maybe easier to +# just special case? +class JoinedAndSeparateValuesArg(Arg): + """JoinedAndSeparateValuesArg - An argument with both joined and + separate values.""" + + def getJoinedValue(self, args): + return args[self.index][len(self.opt.name):] + + def getSeparateValue(self, args): + return args[self.index+1] + + def setJoinedValue(self, args, value): + assert self.opt.name == args[self.index][:len(self.opt.name)] + args[self.index] = self.opt.name + value + + def setSeparateValue(self, args, vaue): + args[self.index+1] = value + + def render(self, args): + return ([self.opt.name + self.getJoinedValue(args)] + + [self.getSeparateValue(args)]) + +class InputArg(ValueArg): + """InputArg - An input file (positional) argument.""" + + def __init__(self, index): + super(ValueArg, self).__init__(index, None) + + def getValue(self, args): + return args[self.index] + + def setValue(self, args, value): + args[self.index] = value + + def render(self, args): + return [self.getValue(args)] + +class DerivedArg(ValueArg): + """DerivedArg - A synthesized argument which does not correspend + to the actual input arguments array.""" + + def __init__(self, value): + super(ValueArg, self).__init__(-1, None) + self.value = value + + def getValue(self, args): + return self.value + + def setValue(self, args): + raise ValueError,"Cannot call setValue() on a DerivedArg." + + def render(self, args): + return [self.value] + +class OptionParser: + def __init__(self): + self.options = [] + + def addOption(self, opt): + self.options.append(opt) + + def chunkArgs(self, argv): + """ + chunkArgs([str]) -> [Arg] + + Parse command line into individual option instances. + """ + + iargs = enumerate(argv) + it = iter(iargs) + args = [] + for i,a in it: + # FIXME: Handle '@' + if not a: + # gcc's handling of empty arguments doesn't make + # sense, but this is not a common use case. :) + # + # We just ignore them here (note that other things may + # still take them as arguments). + pass + elif a[0] == '-' and a != '-': + args.append(self.lookupOptForArg(i, a, it)) + else: + args.append(InputArg(i)) + return args + + def lookupOptForArg(self, i, arg, it): + for op in self.options: + opt = op.accept(i, arg, it) + if opt is not None: + return opt + return UnknownArg(i) + +def createOptionParser(): + op = OptionParser() + + # Driver driver options + op.addOption(SeparateOption('-arch')) + + # Misc driver options + op.addOption(FlagOption('-pass-exit-codes')) + op.addOption(FlagOption('--help')) + op.addOption(FlagOption('--target-help')) + + op.addOption(FlagOption('-dumpspecs')) + op.addOption(FlagOption('-dumpversion')) + op.addOption(FlagOption('-dumpmachine')) + op.addOption(FlagOption('-print-search-dirs')) + op.addOption(FlagOption('-print-libgcc-file-name')) + # FIXME: Hrm, where does this come from? It isn't always true that + # we take both - and --. For example, gcc --S ... ends up sending + # -fS to cc1. Investigate. + op.addOption(FlagOption('--print-libgcc-file-name')) + op.addOption(JoinedOption('-print-file-name=')) + op.addOption(JoinedOption('-print-prog-name=')) + op.addOption(JoinedOption('--print-prog-name=')) + op.addOption(FlagOption('-print-multi-directory')) + op.addOption(FlagOption('-print-multi-lib')) + op.addOption(FlagOption('-print-multi-os-directory')) + + # Hmmm, who really takes this? + op.addOption(FlagOption('--version')) + + # Pipeline control + op.addOption(FlagOption('-###')) + op.addOption(FlagOption('-E')) + op.addOption(FlagOption('-S')) + op.addOption(FlagOption('-c')) + op.addOption(FlagOption('-combine')) + op.addOption(FlagOption('-no-integrated-cpp')) + op.addOption(FlagOption('-pipe')) + op.addOption(FlagOption('-save-temps')) + op.addOption(FlagOption('--save-temps')) + op.addOption(JoinedOption('-specs=')) + op.addOption(FlagOption('-time')) + op.addOption(FlagOption('-v')) + + # Input/output stuff + op.addOption(JoinedOrSeparateOption('-o')) + op.addOption(JoinedOrSeparateOption('-x')) + + # FIXME: What do these actually do? The documentation is less than + # clear. + op.addOption(FlagOption('-ObjC')) + op.addOption(FlagOption('-ObjC++')) + + # FIXME: Weird, gcc claims this here in help but I'm not sure why; + # perhaps interaction with preprocessor? Investigate. + op.addOption(JoinedOption('-std=')) + op.addOption(JoinedOrSeparateOption('--sysroot')) + + # Version control + op.addOption(JoinedOrSeparateOption('-B')) + op.addOption(JoinedOrSeparateOption('-V')) + op.addOption(JoinedOrSeparateOption('-b')) + + # Blanket pass-through options. + + op.addOption(JoinedOption('-Wa,')) + op.addOption(SeparateOption('-Xassembler')) + + op.addOption(JoinedOption('-Wp,')) + op.addOption(SeparateOption('-Xpreprocessor')) + + op.addOption(JoinedOption('-Wl,')) + op.addOption(SeparateOption('-Xlinker')) + + #### + # Bring on the random garbage. + + op.addOption(FlagOption('-MD')) + op.addOption(FlagOption('-MP')) + op.addOption(FlagOption('-MM')) + op.addOption(JoinedOrSeparateOption('-MF')) + op.addOption(JoinedOrSeparateOption('-MT')) + op.addOption(FlagOption('-undef')) + + op.addOption(FlagOption('-w')) + op.addOption(JoinedOrSeparateOption('-allowable_client')) + op.addOption(JoinedOrSeparateOption('-client_name')) + op.addOption(JoinedOrSeparateOption('-compatibility_version')) + op.addOption(JoinedOrSeparateOption('-current_version')) + op.addOption(JoinedOrSeparateOption('-exported_symbols_list')) + op.addOption(JoinedOrSeparateOption('-idirafter')) + op.addOption(JoinedOrSeparateOption('-iquote')) + op.addOption(JoinedOrSeparateOption('-isysroot')) + op.addOption(JoinedOrSeparateOption('-keep_private_externs')) + op.addOption(JoinedOrSeparateOption('-seg1addr')) + op.addOption(JoinedOrSeparateOption('-segprot')) + op.addOption(JoinedOrSeparateOption('-sub_library')) + op.addOption(JoinedOrSeparateOption('-sub_umbrella')) + op.addOption(JoinedOrSeparateOption('-umbrella')) + op.addOption(JoinedOrSeparateOption('-undefined')) + op.addOption(JoinedOrSeparateOption('-unexported_symbols_list')) + op.addOption(JoinedOrSeparateOption('-weak_framework')) + op.addOption(JoinedOption('-headerpad_max_install_names')) + op.addOption(FlagOption('-twolevel_namespace')) + op.addOption(FlagOption('-prebind')) + op.addOption(FlagOption('-prebind_all_twolevel_modules')) + op.addOption(FlagOption('-single_module')) + op.addOption(FlagOption('-nomultidefs')) + op.addOption(FlagOption('-nostdlib')) + op.addOption(FlagOption('-nostdinc')) + op.addOption(FlagOption('-static')) + op.addOption(FlagOption('-shared')) + op.addOption(FlagOption('-C')) + op.addOption(FlagOption('-CC')) + op.addOption(FlagOption('-R')) + op.addOption(FlagOption('-P')) + op.addOption(FlagOption('-all_load')) + op.addOption(FlagOption('--constant-cfstrings')) + op.addOption(FlagOption('-traditional')) + op.addOption(FlagOption('--traditional')) + op.addOption(FlagOption('-no_dead_strip_inits_and_terms')) + op.addOption(MultiArgOption('-sectalign', numArgs=3)) + op.addOption(MultiArgOption('-sectcreate', numArgs=3)) + op.addOption(MultiArgOption('-sectorder', numArgs=3)) + + # I dunno why these don't end up working when joined. Maybe + # because of translation? + op.addOption(SeparateOption('-filelist')) + op.addOption(SeparateOption('-framework')) + op.addOption(SeparateOption('-install_name')) + op.addOption(SeparateOption('-seg_addr_table')) + op.addOption(SeparateOption('-seg_addr_table_filename')) + + # Where are these coming from? I can't find them... + op.addOption(JoinedOrSeparateOption('-e')) # Gets forwarded to linker + op.addOption(JoinedOrSeparateOption('-r')) + + # Is this actually declared anywhere? I can only find it in a + # spec. :( + op.addOption(FlagOption('-pg')) + + doNotReallySupport = 1 + if doNotReallySupport: + # Archaic gcc option. + op.addOption(FlagOption('-cpp-precomp')) + op.addOption(FlagOption('-no-cpp-precomp')) + + # C options for testing + + op.addOption(JoinedOrSeparateOption('-include')) + op.addOption(JoinedOrSeparateOption('-A')) + op.addOption(JoinedOrSeparateOption('-D')) + op.addOption(JoinedOrSeparateOption('-F')) + op.addOption(JoinedOrSeparateOption('-I')) + op.addOption(JoinedOrSeparateOption('-L')) + op.addOption(JoinedOrSeparateOption('-U')) + op.addOption(JoinedOrSeparateOption('-l')) + op.addOption(JoinedOrSeparateOption('-u')) + + # FIXME: What is going on here? '-X' goes to linker, and -X ... goes nowhere? + op.addOption(FlagOption('-X')) + # Not exactly sure how to decompose this. I split out -Xarch_ + # because we need to recognize that in the driver driver part. + # FIXME: Man, this is lame it needs its own option. + op.addOption(JoinedAndSeparateOption('-Xarch_')) + op.addOption(JoinedOption('-X')) + + # The driver needs to know about this flag. + op.addOption(FlagOption('-fsyntax-only')) + + # FIXME: Wrong? + # FIXME: What to do about the ambiguity of options like + # -dumpspecs? How is this handled in gcc? + op.addOption(JoinedOption('-d')) + op.addOption(JoinedOption('-g')) + op.addOption(JoinedOption('-f')) + op.addOption(JoinedOption('-m')) + op.addOption(JoinedOption('-i')) + op.addOption(JoinedOption('-O')) + op.addOption(JoinedOption('-W')) + # FIXME: Weird. This option isn't really separate, --param=a=b + # works. An alias somewhere? + op.addOption(SeparateOption('--param')) + + # FIXME: What is this? Seems to do something on Linux. I think + # only one is valid, but have a log that uses both. + op.addOption(FlagOption('-pthread')) + op.addOption(FlagOption('-pthreads')) + + return op diff --git a/clang/tools/ccc/ccclib/Driver.py b/clang/tools/ccc/ccclib/Driver.py new file mode 100644 index 000000000000..4bf0478a505f --- /dev/null +++ b/clang/tools/ccc/ccclib/Driver.py @@ -0,0 +1,666 @@ +import os +import sys +import tempfile +from pprint import pprint + +### + +import Arguments +import Jobs +import Phases +import Tools +import Types +import Util + +# FIXME: Clean up naming of options and arguments. Decide whether to +# rename Option and be consistent about use of Option/Arg. + +#### + +class MissingArgumentError(ValueError): + """MissingArgumentError - An option required an argument but none + was given.""" + +### + +class Driver(object): + def __init__(self): + self.parser = Arguments.createOptionParser() + + def run(self, args): + # FIXME: Things to support from environment: GCC_EXEC_PREFIX, + # COMPILER_PATH, LIBRARY_PATH, LPATH, CC_PRINT_OPTIONS, + # QA_OVERRIDE_GCC3_OPTIONS, ...? + + # FIXME: -V and -b processing + + # Handle some special -ccc- options used for testing which are + # only allowed at the beginning of the command line. + cccPrintOptions = False + cccPrintPhases = False + cccUseDriverDriver = True + while args and args[0].startswith('-ccc-'): + opt,args = args[0][5:],args[1:] + + if opt == 'print-options': + cccPrintOptions = True + elif opt == 'print-phases': + cccPrintPhases = True + elif opt == 'no-driver-driver': + # FIXME: Remove this once we have some way of being a + # cross compiler driver (cross driver compiler? compiler + # cross driver? etc.). + cccUseDriverDriver = False + else: + raise ValueError,"Invalid ccc option: %r" % cccPrintOptions + + options = self.parser.chunkArgs(args) + + # FIXME: Ho hum I have just realized -Xarch_ is broken. We really + # need to reparse the Arguments after they have been expanded by + # -Xarch. How is this going to work? + # + # Scratch that, we aren't going to do that; it really disrupts the + # organization, doesn't consistently work with gcc-dd, and is + # confusing. Instead we are going to enforce that -Xarch_ is only + # used with options which do not alter the driver behavior. Let's + # hope this is ok, because the current architecture is a little + # tied to it. + + if cccPrintOptions: + self.printOptions(args, options) + sys.exit(0) + + self.handleImmediateOptions(args, options) + + if cccUseDriverDriver: + phases = self.buildPipeline(options, args) + else: + phases = self.buildNormalPipeline(options, args) + + if cccPrintPhases: + self.printPhases(args, phases) + sys.exit(0) + + if 0: + print Util.pprint(phases) + + jobs = self.bindPhases(phases, options, args) + + # FIXME: We should provide some basic sanity checking of the + # pipeline as a "verification" sort of stage. For example, the + # pipeline should never end up writing to an output file in two + # places (I think). The pipeline should also never end up writing + # to an output file that is an input. + # + # This is intended to just be a "verify" step, not a functionality + # step. It should catch things like the driver driver not + # preventing -save-temps, but it shouldn't change behavior (so we + # can turn it off in Release-Asserts builds). + + # Print in -### syntax. + hasHashHashHash = None + for oi in options: + if oi.opt and oi.opt.name == '-###': + hasHashHashHash = oi + + if hasHashHashHash: + self.claim(hasHashHashHash) + for j in jobs.iterjobs(): + if isinstance(j, Jobs.Command): + print '"%s"' % '" "'.join(j.render(args)) + elif isinstance(j, Jobs.PipedJob): + for c in j.commands: + print '"%s" %c' % ('" "'.join(c.render(args)), + "| "[c is j.commands[-1]]) + elif not isinstance(j, JobList): + raise ValueError,'Encountered unknown job.' + sys.exit(0) + + for j in jobs.iterjobs(): + if isinstance(j, Jobs.Command): + cmd_args = j.render(args) + res = os.spawnvp(os.P_WAIT, cmd_args[0], cmd_args) + if res: + sys.exit(res) + elif isinstance(j, Jobs.PipedJob): + raise NotImplementedError,"Piped jobs aren't implemented yet." + else: + raise ValueError,'Encountered unknown job.' + + def claim(self, option): + # FIXME: Move to OptionList once introduced and implement. + pass + + def warning(self, message): + print >>sys.stderr,'%s: %s' % (sys.argv[0], message) + + def printOptions(self, args, options): + for i,oi in enumerate(options): + if isinstance(oi, Arguments.InputArg): + name = "" + elif isinstance(oi, Arguments.UnknownArg): + name = "" + else: + assert oi.opt + name = oi.opt.name + if isinstance(oi, Arguments.MultipleValuesArg): + values = list(oi.getValues(args)) + elif isinstance(oi, Arguments.ValueArg): + values = [oi.getValue(args)] + elif isinstance(oi, Arguments.JoinedAndSeparateValuesArg): + values = [oi.getJoinedValue(args), oi.getSeparateValue(args)] + else: + values = [] + print 'Option %d - Name: "%s", Values: {%s}' % (i, name, + ', '.join(['"%s"' % v + for v in values])) + + def printPhases(self, args, phases): + def printPhase(p, f, steps, arch=None): + if p in steps: + return steps[p] + elif isinstance(p, Phases.BindArchAction): + for kid in p.inputs: + printPhase(kid, f, steps, p.arch) + steps[p] = len(steps) + return + + if isinstance(p, Phases.InputAction): + phaseName = 'input' + inputStr = '"%s"' % p.filename.getValue(args) + else: + phaseName = p.phase.name + inputs = [printPhase(i, f, steps, arch) + for i in p.inputs] + inputStr = '{%s}' % ', '.join(map(str, inputs)) + if arch is not None: + phaseName += '-' + arch.getValue(args) + steps[p] = index = len(steps) + print "%d: %s, %s, %s" % (index,phaseName,inputStr,p.type.name) + return index + steps = {} + for phase in phases: + printPhase(phase, sys.stdout, steps) + + def handleImmediateOptions(self, args, options): + # FIXME: Some driver Arguments are consumed right off the bat, + # like -dumpversion. Currently the gcc-dd handles these + # poorly, so we should be ok handling them upfront instead of + # after driver-driver level dispatching. + # + # FIXME: The actual order of these options in gcc is all over the + # place. The -dump ones seem to be first and in specification + # order, but there are other levels of precedence. For example, + # -print-search-dirs is evaluated before -print-prog-name=, + # regardless of order (and the last instance of -print-prog-name= + # wins verse itself). + # + # FIXME: Do we want to report "argument unused" type errors in the + # presence of things like -dumpmachine and -print-search-dirs? + # Probably not. + for oi in options: + if oi.opt is not None: + if oi.opt.name == '-dumpmachine': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-dumpspecs': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-dumpversion': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-file-name=': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-multi-directory': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-multi-lib': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-prog-name=': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-libgcc-file-name': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-search-dirs': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + + def buildNormalPipeline(self, args, inputArgs): + hasCombine = None + hasSyntaxOnly = None + hasDashC = hasDashE = hasDashS = None + + inputType = None + inputTypeOpt = None + inputs = [] + for a in args: + if isinstance(a, Arguments.InputArg): + if inputType is None: + base,ext = os.path.splitext(a.getValue(inputArgs)) + if ext and ext in Types.kTypeSuffixMap: + klass = Types.kTypeSuffixMap[ext] + else: + # FIXME: Its not clear why we shouldn't just + # revert to unknown. I think this is more likely a + # bug / unintended behavior in gcc. Not very + # important though. + klass = Types.ObjectType + else: + assert inputTypeOpt is not None + self.claim(inputTypeOpt) + klass = inputType + inputs.append((klass, a)) + elif a.opt is not None: + # FIXME: We should warn about inconsistent and duplicate + # usage of these flags. + if a.opt.name == '-E': + hasDashE = a + elif a.opt.name == '-S': + hasDashS = a + elif a.opt.name == '-c': + hasDashC = a + elif a.opt.name == '-fsyntax-only': + hasSyntaxOnly = a + elif a.opt.name == '-combine': + hasCombine = a + elif a.opt.name == '-filelist': + # FIXME: This might not be good enough. We may + # need to introduce another type of InputArg for + # this case, so that other code which needs to + # know the inputs handles this properly. Best not + # to try and lipo this, for example. + # + # Treat as a linker input. + inputs.append((Types.ObjectType, a)) + elif a.opt.name == '-x': + self.claim(a) + inputTypeOpt = a + value = a.getValue(inputArgs) + if value in Types.kTypeSpecifierMap: + inputType = Types.kTypeSpecifierMap[value] + else: + # FIXME: How are we going to handle diagnostics. + self.warning("language %s not recognized" % value) + + # FIXME: Its not clear why we shouldn't just + # revert to unknown. I think this is more likely a + # bug / unintended behavior in gcc. Not very + # important though. + inputType = ObjectType + + # We claim things here so that options for which we silently allow + # override only ever claim the used option. + if hasCombine: + self.claim(hasCombine) + + finalPhase = Phases.Phase.eOrderPostAssemble + finalPhaseOpt = None + + # Determine what compilation mode we are in. + if hasDashE: + finalPhase = Phases.Phase.eOrderPreprocess + finalPhaseOpt = hasDashE + elif hasSyntaxOnly: + finalPhase = Phases.Phase.eOrderCompile + finalPhaseOpt = hasSyntaxOnly + elif hasDashS: + finalPhase = Phases.Phase.eOrderCompile + finalPhaseOpt = hasDashS + elif hasDashC: + finalPhase = Phases.Phase.eOrderAssemble + finalPhaseOpt = hasDashC + + if finalPhaseOpt: + self.claim(finalPhaseOpt) + + # FIXME: Support -combine. + if hasCombine: + raise NotImplementedError,"-combine is not yet supported." + + actions = [] + linkerInputs = [] + # FIXME: This is gross. + linkPhase = Phases.LinkPhase() + for klass,input in inputs: + # Figure out what step to start at. + + # FIXME: This should be part of the input class probably? + # Altough it doesn't quite fit there either, things like + # asm-with-preprocess don't easily fit into a linear scheme. + + # FIXME: I think we are going to end up wanting to just build + # a simple FSA which we run the inputs down. + sequence = [] + if klass.preprocess: + sequence.append(Phases.PreprocessPhase()) + if klass == Types.ObjectType: + sequence.append(linkPhase) + elif klass.onlyAssemble: + sequence.extend([Phases.AssemblePhase(), + linkPhase]) + elif klass.onlyPrecompile: + sequence.append(Phases.PrecompilePhase()) + else: + sequence.extend([Phases.CompilePhase(), + Phases.AssemblePhase(), + linkPhase]) + + if sequence[0].order > finalPhase: + assert finalPhaseOpt and finalPhaseOpt.opt + # FIXME: Explain what type of input file is. Or just match + # gcc warning. + self.warning("%s: %s input file unused when %s is present" % (input.getValue(inputArgs), + sequence[0].name, + finalPhaseOpt.opt.name)) + else: + # Build the pipeline for this file. + + current = Phases.InputAction(input, klass) + for transition in sequence: + # If the current action produces no output, or we are + # past what the user requested, we are done. + if (current.type is Types.NothingType or + transition.order > finalPhase): + break + else: + if isinstance(transition, Phases.PreprocessPhase): + assert isinstance(klass.preprocess, Types.InputType) + current = Phases.JobAction(transition, + [current], + klass.preprocess) + elif isinstance(transition, Phases.PrecompilePhase): + current = Phases.JobAction(transition, + [current], + Types.PCHType) + elif isinstance(transition, Phases.CompilePhase): + if hasSyntaxOnly: + output = Types.NothingType + else: + output = Types.AsmTypeNoPP + current = Phases.JobAction(transition, + [current], + output) + elif isinstance(transition, Phases.AssemblePhase): + current = Phases.JobAction(transition, + [current], + Types.ObjectType) + elif transition is linkPhase: + linkerInputs.append(current) + current = None + break + else: + raise RuntimeError,'Unrecognized transition: %s.' % transition + pass + + if current is not None: + assert not isinstance(current, Phases.InputAction) + actions.append(current) + + if linkerInputs: + actions.append(Phases.JobAction(linkPhase, + linkerInputs, + Types.ImageType)) + + return actions + + def buildPipeline(self, args, inputArgs): + # FIXME: We need to handle canonicalization of the specified arch. + + archs = [] + hasOutput = None + hasDashM = hasSaveTemps = None + for o in args: + if o.opt is None: + continue + + if isinstance(o, Arguments.ValueArg): + if o.opt.name == '-arch': + archs.append(o) + elif o.opt.name.startswith('-M'): + hasDashM = o + elif o.opt.name in ('-save-temps','--save-temps'): + hasSaveTemps = o + + if not archs: + # FIXME: Need to infer arch so that we sub -Xarch + # correctly. + archs.append(Arguments.DerivedArg('i386')) + + actions = self.buildNormalPipeline(args, inputArgs) + + # FIXME: Use custom exception for this. + # + # FIXME: We killed off some others but these aren't yet detected in + # a functional manner. If we added information to jobs about which + # "auxiliary" files they wrote then we could detect the conflict + # these cause downstream. + if len(archs) > 1: + if hasDashM: + raise ValueError,"Cannot use -M options with multiple arch flags." + elif hasSaveTemps: + raise ValueError,"Cannot use -save-temps with multiple arch flags." + + # Execute once per arch. + finalActions = [] + for p in actions: + # Make sure we can lipo this kind of output. If not (and it + # is an actual output) then we disallow, since we can't + # create an output file with the right name without + # overwriting it. We could remove this oddity by just + # changing the output names to include the arch, which would + # also fix -save-temps. Compatibility wins for now. + # + # FIXME: Is this error substantially less useful than + # gcc-dd's? The main problem is that "Cannot use compiler + # output with multiple arch flags" won't make sense to most + # developers. + if (len(archs) > 1 and + p.type not in (Types.NothingType,Types.ObjectType,Types.ImageType)): + raise ValueError,'Cannot use %s output with multiple arch flags.' % p.type.name + + inputs = [] + for arch in archs: + inputs.append(Phases.BindArchAction(p, arch)) + + # Lipo if necessary. We do it this way because we need to set + # the arch flag so that -Xarch_ gets rewritten. + if len(inputs) == 1 or p.type == Types.NothingType: + finalActions.extend(inputs) + else: + finalActions.append(Phases.JobAction(Phases.LipoPhase(), + inputs, + p.type)) + + # FIXME: We need to add -Wl,arch_multiple and -Wl,final_output in + # certain cases. This may be icky because we need to figure out the + # mode first. Current plan is to hack on the pipeline once it is built + # and we know what is being spit out. This avoids having to handling + # things like -c and -combine in multiple places. + # + # The annoying one of these is -Wl,final_output because it involves + # communication across different phases. + # + # Hopefully we can do this purely as part of the binding, but + # leaving comment here for now until it is clear this works. + + return finalActions + + def bindPhases(self, phases, args, inputArgs): + jobs = Jobs.JobList() + + finalOutput = None + hasSaveTemps = hasNoIntegratedCPP = hasPipe = None + forward = [] + for a in args: + if isinstance(a, Arguments.InputArg): + pass + elif a.opt is not None: + if a.opt.name == '-save-temps': + hasSaveTemps = a + elif a.opt.name == '-no-integrated-cpp': + hasNoIntegratedCPP = a + elif a.opt.name == '-o': + finalOutput = a + elif a.opt.name == '-pipe': + hasPipe = a + elif a.opt.name in ('-E', '-S', '-c', + '-arch', '-fsyntax-only', '-combine', '-x', + '-###'): + pass + else: + forward.append(a) + else: + forward.append(a) + + # We claim things here so that options for which we silently allow + # override only ever claim the used option. + if hasPipe: + self.claim(hasPipe) + # FIXME: Hack, override -pipe till we support it. + hasPipe = None + # Claim these here. Its not completely accurate but any warnings + # about these being unused are likely to be noise anyway. + if hasSaveTemps: + self.claim(hasSaveTemps) + if hasNoIntegratedCPP: + self.claim(hasNoIntegratedCPP) + + toolMap = { + Phases.PreprocessPhase : Tools.GCC_PreprocessTool(), + Phases.CompilePhase : Tools.GCC_CompileTool(), + Phases.PrecompilePhase : Tools.GCC_PrecompileTool(), + Phases.AssemblePhase : Tools.DarwinAssemblerTool(), + Phases.LinkPhase : Tools.Collect2Tool(), + Phases.LipoPhase : Tools.LipoTool(), + } + + class InputInfo: + def __init__(self, source, type, baseInput): + self.source = source + self.type = type + self.baseInput = baseInput + + def __repr__(self): + return '%s(%r, %r, %r)' % (self.__class__.__name__, + self.source, self.type, self.baseInput) + + def createJobs(phase, forwardArgs, + canAcceptPipe=False, atTopLevel=False, arch=None): + if isinstance(phase, Phases.InputAction): + return InputInfo(phase.filename, phase.type, phase.filename) + elif isinstance(phase, Phases.BindArchAction): + archName = phase.arch.getValue(inputArgs) + filteredArgs = [] + for oi in forwardArgs: + if oi.opt is None: + filteredArgs.append(oi) + elif oi.opt.name == '-arch': + if oi is phase.arch: + filteredArgs.append(oi) + elif oi.opt.name == '-Xarch_': + # FIXME: gcc-dd has another conditional for passing + # through, when the arch conditional array has an empty + # string. Why? + if oi.getJoinedValue(inputArgs) == archName: + # FIXME: This is wrong, we don't want a + # DerivedArg we want an actual parsed version + # of this arg. + filteredArgs.append(Arguments.DerivedArg(oi.getSeparateValue(inputArgs))) + else: + filteredArgs.append(oi) + + return createJobs(phase.inputs[0], filteredArgs, + canAcceptPipe, atTopLevel, phase.arch) + + assert isinstance(phase, Phases.JobAction) + tool = toolMap[phase.phase.__class__] + + # See if we should use an integrated CPP. We only use an + # integrated cpp when we have exactly one input, since this is + # the only use case we care about. + useIntegratedCPP = False + inputList = phase.inputs + if (not hasNoIntegratedCPP and + not hasSaveTemps and + tool.hasIntegratedCPP()): + if (len(phase.inputs) == 1 and + isinstance(phase.inputs[0].phase, Phases.PreprocessPhase)): + useIntegratedCPP = True + inputList = phase.inputs[0].inputs + + # Only try to use pipes when exactly one input. + canAcceptPipe = len(inputList) == 1 and tool.acceptsPipedInput() + inputs = [createJobs(p, forwardArgs, canAcceptPipe, False, arch) for p in inputList] + + # Determine if we should output to a pipe. + canOutputToPipe = canAcceptPipe and tool.canPipeOutput() + outputToPipe = False + if canOutputToPipe: + # Some things default to writing to a pipe if the final + # phase and there was no user override. + # + # FIXME: What is the best way to handle this? + if (atTopLevel and + isinstance(phase, Phases.PreprocessPhase) and + not finalOutput): + outputToPipe = True + elif hasPipe: + outputToPipe = True + + # Figure out where to put the job (pipes). + jobList = jobs + if canAcceptPipe and isinstance(inputs[0].source, Jobs.PipedJob): + jobList = inputs[0].source + + # Figure out where to put the output. + baseInput = inputs[0].baseInput + if phase.type == Types.NothingType: + output = None + elif outputToPipe: + if isinstance(jobList, Jobs.PipedJob): + output = jobList + else: + jobList = output = Jobs.PipedJob([]) + jobs.addJob(output) + else: + # Figure out what the derived output location would be. + # + # FIXME: gcc has some special case in here so that it doesn't + # create output files if they would conflict with an input. + inputName = baseInput.getValue(inputArgs) + if phase.type is Types.ImageType: + namedOutput = "a.out" + else: + base,_ = os.path.splitext(inputName) + assert phase.type.tempSuffix is not None + namedOutput = base + phase.type.tempSuffix + + # Output to user requested destination? + if atTopLevel and finalOutput: + output = finalOutput + # Contruct a named destination? + elif atTopLevel or hasSaveTemps: + output = Arguments.DerivedArg(namedOutput) + else: + # Output to temp file... + fd,filename = tempfile.mkstemp(suffix=phase.type.tempSuffix) + output = Arguments.DerivedArg(filename) + + tool.constructJob(phase, arch, jobList, inputs, output, phase.type, forwardArgs) + + return InputInfo(output, phase.type, baseInput) + + # It is an error to provide a -o option if we are making multiple + # output files. + if finalOutput and len([a for a in phases if a.type is not Types.NothingType]) > 1: + # FIXME: Custom exception. + raise ValueError,"Cannot specify -o when generating multiple files." + + for phase in phases: + createJobs(phase, forward, canAcceptPipe=True, atTopLevel=True) + + return jobs diff --git a/clang/tools/ccc/ccclib/Jobs.py b/clang/tools/ccc/ccclib/Jobs.py new file mode 100644 index 000000000000..8ef8a03fc264 --- /dev/null +++ b/clang/tools/ccc/ccclib/Jobs.py @@ -0,0 +1,60 @@ +import Arguments +import Util + +class Job(object): + """Job - A set of commands to execute as a single task.""" + + def iterjobs(self): + abstract + +class Command(Job): + """Command - Represent the information needed to execute a single + process.""" + + def __init__(self, executable, args): + assert Util.all_true(args, lambda x: isinstance(x, Arguments.Arg)) + self.executable = executable + self.args = args + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, + (self.executable, self.args)) + + def render(self, args): + argv = [self.executable] + for oi in self.args: + argv.extend(oi.render(args)) + return argv + + def iterjobs(self): + yield self + +class PipedJob(Job): + """PipedJob - A sequence of piped commands.""" + + def __init__(self, commands): + assert all_true(args, lambda x: isinstance(x, Arguments.Command)) + self.commands = list(commands) + + def addJob(self, job): + assert isinstance(job, Command) + self.commands.append(job) + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, (self.commands,)) + +class JobList(Job): + """JobList - A sequence of jobs to perform.""" + + def __init__(self, jobs=[]): + self.jobs = list(jobs) + + def addJob(self, job): + self.jobs.append(job) + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, (self.jobs,)) + + def iterjobs(self): + for j in self.jobs: + yield j diff --git a/clang/tools/ccc/ccclib/Phases.py b/clang/tools/ccc/ccclib/Phases.py new file mode 100644 index 000000000000..0384b8fa4fc9 --- /dev/null +++ b/clang/tools/ccc/ccclib/Phases.py @@ -0,0 +1,86 @@ +import Util + +class Action(object): + def __init__(self, inputs, type): + self.inputs = inputs + self.type = type + +class BindArchAction(Action): + """BindArchAction - Represent an architecture binding for child + actions.""" + + def __init__(self, input, arch): + super(BindArchAction, self).__init__([input], input.type) + self.arch = arch + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, + (self.inputs[0], self.arch)) + +class InputAction(Action): + """InputAction - Adapt an input file to an action & type. """ + + def __init__(self, filename, type): + super(InputAction, self).__init__([], type) + self.filename = filename + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, + (self.filename, self.type)) + +class JobAction(Action): + """JobAction - Represent a job tied to a particular compilation + phase.""" + + def __init__(self, phase, inputs, type): + super(JobAction, self).__init__(inputs, type) + self.phase = phase + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, + (self.phase, self.inputs, self.type)) + +### + +class Phase(object): + """Phase - Represent an abstract task in the compilation + pipeline.""" + + eOrderNone = 0 + eOrderPreprocess = 1 + eOrderCompile = 2 + eOrderAssemble = 3 + eOrderPostAssemble = 4 + + def __init__(self, name, order): + self.name = name + self.order = order + + def __repr__(self): + return Util.prefixAndPPrint(self.__class__.__name__, + (self.name, self.order)) + +class PreprocessPhase(Phase): + def __init__(self): + super(PreprocessPhase, self).__init__("preprocessor", Phase.eOrderPreprocess) + +class PrecompilePhase(Phase): + def __init__(self): + super(PrecompilePhase, self).__init__("precompiler", Phase.eOrderCompile) + +class CompilePhase(Phase): + def __init__(self): + super(CompilePhase, self).__init__("compiler", Phase.eOrderCompile) + +class AssemblePhase(Phase): + def __init__(self): + super(AssemblePhase, self).__init__("assembler", Phase.eOrderAssemble) + +class LinkPhase(Phase): + def __init__(self): + super(LinkPhase, self).__init__("linker", Phase.eOrderPostAssemble) + +class LipoPhase(Phase): + def __init__(self): + super(LipoPhase, self).__init__("lipo", Phase.eOrderPostAssemble) + diff --git a/clang/tools/ccc/ccclib/Tools.py b/clang/tools/ccc/ccclib/Tools.py new file mode 100644 index 000000000000..ddfb02916e56 --- /dev/null +++ b/clang/tools/ccc/ccclib/Tools.py @@ -0,0 +1,169 @@ +import Arguments +import Jobs +import Types + +class Tool(object): + """Tool - A concrete implementation of a phase.""" + + eFlagsPipedInput = 1 << 0 + eFlagsPipedOutput = 1 << 1 + eFlagsIntegratedCPP = 1 << 2 + + def __init__(self, name, flags = 0): + self.name = name + self.flags = flags + + def acceptsPipedInput(self): + return not not (self.flags & Tool.eFlagsPipedInput) + def canPipeOutput(self): + return not not (self.flags & Tool.eFlagsPipedOutput) + def hasIntegratedCPP(self): + return not not (self.flags & Tool.eFlagsIntegratedCPP) + +class GCC_Common_Tool(Tool): + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args, + extraArgs): + assert len(inputs) == 1 + + input = inputs[0] + + cmd_args = args + extraArgs + if arch: + # FIXME: Clean this up. + if isinstance(arch, Arguments.DerivedArg): + cmd_args.extend([Arguments.DerivedArg('-arch'), + arch]) + else: + cmd_args.append(arch) + if isinstance(output, Jobs.PipedJob): + cmd_args.extend([Arguments.DerivedArg('-o'), Arguments.DerivedArg('-')]) + elif output is None: + cmd_args.append(Arguments.DerivedArg('-fsyntax-only')) + else: + # FIXME: Ditch this hack. + if isinstance(output, Arguments.DerivedArg): + cmd_args.extend([Arguments.DerivedArg('-o'), output]) + else: + cmd_args.append(output) + + cmd_args.extend([Arguments.DerivedArg('-x'), + Arguments.DerivedArg(input.type.name)]) + if isinstance(input.source, Jobs.PipedJob): + cmd_args.append(Arguments.DerivedArg('-')) + else: + cmd_args.append(input.source) + + jobs.addJob(Jobs.Command('gcc', cmd_args)) + +class GCC_PreprocessTool(GCC_Common_Tool): + def __init__(self): + super(GCC_PreprocessTool, self).__init__('gcc', + (Tool.eFlagsPipedInput | + Tool.eFlagsPipedOutput)) + + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args): + return super(GCC_PreprocessTool, self).constructJob(phase, arch, jobs, inputs, + output, outputType, args, + [Arguments.DerivedArg('-E')]) + +class GCC_CompileTool(GCC_Common_Tool): + def __init__(self): + super(GCC_CompileTool, self).__init__('gcc', + (Tool.eFlagsPipedInput | + Tool.eFlagsPipedOutput | + Tool.eFlagsIntegratedCPP)) + + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args): + return super(GCC_CompileTool, self).constructJob(phase, arch, jobs, inputs, + output, outputType, args, + [Arguments.DerivedArg('-S')]) + +class GCC_PrecompileTool(GCC_Common_Tool): + def __init__(self): + super(GCC_PrecompileTool, self).__init__('gcc', + (Tool.eFlagsPipedInput | + Tool.eFlagsIntegratedCPP)) + + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args): + return super(GCC_PrecompileTool, self).constructJob(phase, arch, jobs, inputs, + output, outputType, args, + []) + +class DarwinAssemblerTool(Tool): + def __init__(self): + super(DarwinAssemblerTool, self).__init__('as', + Tool.eFlagsPipedInput) + + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args): + assert len(inputs) == 1 + assert outputType is Types.ObjectType + + input = inputs[0] + + cmd_args = [] + if arch: + # FIXME: Clean this up. + if isinstance(arch, Arguments.DerivedArg): + cmd_args.extend([Arguments.DerivedArg('-arch'), + arch]) + else: + cmd_args.append(arch) + cmd_args.append(Arguments.DerivedArg('-force_cpusubtype_ALL')) + if isinstance(output, Arguments.DerivedArg): + cmd_args.extend([Arguments.DerivedArg('-o'), output]) + else: + cmd_args.append(output) + if isinstance(input.source, Jobs.PipedJob): + cmd_args.append(Arguments.DerivedArg('-')) + else: + cmd_args.append(input.source) + jobs.addJob(Jobs.Command('as', cmd_args)) + +class Collect2Tool(Tool): + kCollect2Path = '/usr/libexec/gcc/i686-apple-darwin10/4.2.1/collect2' + def __init__(self): + super(Collect2Tool, self).__init__('collect2') + + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args): + assert outputType is Types.ImageType + + cmd_args = [] + for arg in args: + if arg.opt: + if arg.opt.name in ('-framework',): + cmd_args.append(arg) + for input in inputs: + cmd_args.append(input.source) + if isinstance(output, Arguments.DerivedArg): + cmd_args.extend([Arguments.DerivedArg('-o'), output]) + else: + cmd_args.append(output) + cmd_args.extend([Arguments.DerivedArg('-L/usr/lib/gcc/i686-apple-darwin10/4.2.1'), + Arguments.DerivedArg('-lcrt1.10.5.o'), + Arguments.DerivedArg('-lgcc_s.10.5'), + Arguments.DerivedArg('-lgcc'), + Arguments.DerivedArg('-lSystem')]) + jobs.addJob(Jobs.Command(self.kCollect2Path, cmd_args)) + +class LipoTool(Tool): + def __init__(self): + super(LipoTool, self).__init__('lipo') + + def constructJob(self, phase, arch, jobs, inputs, + output, outputType, args): + assert outputType is Types.ImageType + + cmd_args = [Arguments.DerivedArg('-create')] + if isinstance(output, Arguments.DerivedArg): + cmd_args.extend([Arguments.DerivedArg('-o'), output]) + else: + cmd_args.append(output) + for input in inputs: + cmd_args.append(input.source) + jobs.addJob(Jobs.Command('lipo', cmd_args)) diff --git a/clang/tools/ccc/ccclib/Types.py b/clang/tools/ccc/ccclib/Types.py new file mode 100644 index 000000000000..4d04e0a3d1c8 --- /dev/null +++ b/clang/tools/ccc/ccclib/Types.py @@ -0,0 +1,123 @@ +class InputType(object): + """InputType - Information about various classes of files which + the driver recognizes and control processing.""" + + def __init__(self, name, preprocess=None, onlyAssemble=False, + onlyPrecompile=False, tempSuffix=None): + assert preprocess is None or isinstance(preprocess, InputType) + self.name = name + self.preprocess = preprocess + self.onlyAssemble = onlyAssemble + self.onlyPrecompile = onlyPrecompile + self.tempSuffix = tempSuffix + + def __repr__(self): + return '%s(%r, %r, %r, %r, %r)' % (self.__class__.__name__, + self.name, + self.preprocess, + self.onlyAssemble, + self.onlyPrecompile, + self.tempSuffix) + +# C family source language (with and without preprocessing). +CTypeNoPP = InputType('cpp-output', tempSuffix='i') +CType = InputType('c', CTypeNoPP) +ObjCTypeNoPP = InputType('objective-c-cpp-output', tempSuffix='mi') +ObjCType = InputType('objective-c', ObjCTypeNoPP) +CXXTypeNoPP = InputType('c++-cpp-output', tempSuffix='ii') +CXXType = InputType('c++', CXXTypeNoPP) +ObjCXXTypeNoPP = InputType('objective-c++-cpp-output', tempSuffix='mii') +ObjCXXType = InputType('c++', ObjCXXTypeNoPP) + +# C family input files to precompile. +CHeaderNoPPType = InputType('c-header-cpp-output', onlyPrecompile=True, tempSuffix='pch') +CHeaderType = InputType('c-header', CHeaderNoPPType, onlyPrecompile=True) +ObjCHeaderNoPPType = InputType('objective-c-header-cpp-output', onlyPrecompile=True, tempSuffix='pch') +ObjCHeaderType = InputType('objective-c-header', ObjCHeaderNoPPType, onlyPrecompile=True) +CXXHeaderNoPPType = InputType('c++-header-cpp-output', onlyPrecompile=True, tempSuffix='pch') +CXXHeaderType = InputType('c++-header', CXXHeaderNoPPType, onlyPrecompile=True) +ObjCXXHeaderNoPPType = InputType('objective-c++-header-cpp-output', onlyPrecompile=True, tempSuffix='pch') +ObjCXXHeaderType = InputType('objective-c++-header', ObjCXXHeaderNoPPType, onlyPrecompile=True) + +# Other languages. +AdaType = InputType('ada') +AsmTypeNoPP = InputType('assembler', onlyAssemble=True, tempSuffix='s') +AsmType = InputType('assembler-with-cpp', AsmTypeNoPP, onlyAssemble=True) +FortranTypeNoPP = InputType('fortran') +FortranType = InputType('fortran', FortranTypeNoPP) +JavaType = InputType('java') + +# Misc. +PCHType = InputType('precompiled-header') +ObjectType = InputType('object', tempSuffix='o') +TreelangType = InputType('treelang') +ImageType = InputType('image', tempSuffix='out') +NothingType = InputType('nothing') + +### + +kDefaultOutput = "a.out" +kTypeSuffixMap = { + '.c' : CType, + '.i' : CTypeNoPP, + '.ii' : CXXTypeNoPP, + '.m' : ObjCType, + '.mi' : ObjCTypeNoPP, + '.mm' : ObjCXXType, + '.M' : ObjCXXType, + '.mii' : ObjCXXTypeNoPP, + '.h' : CHeaderType, + '.cc' : CXXType, + '.cc' : CXXType, + '.cp' : CXXType, + '.cxx' : CXXType, + '.cpp' : CXXType, + '.CPP' : CXXType, + '.cXX' : CXXType, + '.C' : CXXType, + '.hh' : CXXHeaderType, + '.H' : CXXHeaderType, + '.f' : FortranTypeNoPP, + '.for' : FortranTypeNoPP, + '.FOR' : FortranTypeNoPP, + '.F' : FortranType, + '.fpp' : FortranType, + '.FPP' : FortranType, + '.f90' : FortranTypeNoPP, + '.f95' : FortranTypeNoPP, + '.F90' : FortranType, + '.F95' : FortranType, + # Apparently the Ada F-E hardcodes these suffixes in many + # places. This explains why there is only one -x option for ada. + '.ads' : AdaType, + '.adb' : AdaType, + # FIXME: Darwin always uses a preprocessor for asm input. Where + # does this fit? + '.s' : AsmTypeNoPP, + '.S' : AsmType, +} +kTypeSpecifierMap = { + 'none' : None, + + 'c' : CType, + 'c-header' : CHeaderType, + # NOTE: gcc.info claims c-cpp-output works but the actual spelling + # is cpp-output. Nice. + 'cpp-output' : CTypeNoPP, + 'c++' : CXXType, + 'c++-header' : CXXHeaderType, + 'c++-cpp-output' : CXXTypeNoPP, + 'objective-c' : ObjCType, + 'objective-c-header' : ObjCHeaderType, + 'objective-c-cpp-output' : ObjCTypeNoPP, + 'objective-c++' : ObjCXXType, + 'objective-c++-header' : ObjCXXHeaderType, + 'objective-c++-cpp-output' : ObjCXXTypeNoPP, + 'assembler' : AsmTypeNoPP, + 'assembler-with-cpp' : AsmType, + 'ada' : AdaType, + 'f95' : FortranType, + 'f95-cpp-input' : FortranTypeNoPP, + 'java' : JavaType, + 'treelang' : TreelangType, +} diff --git a/clang/tools/ccc/ccclib/Util.py b/clang/tools/ccc/ccclib/Util.py new file mode 100644 index 000000000000..0924e8c24890 --- /dev/null +++ b/clang/tools/ccc/ccclib/Util.py @@ -0,0 +1,52 @@ +def any_true(list, predicate): + for i in list: + if predicate(i): + return True + return False + +def any_false(list, predicate): + return any_true(list, lambda x: not predicate(x)) + +def all_true(list, predicate): + return not any_false(list, predicate) + +def all_false(list, predicate): + return not any_true(list, predicate) + +def prependLines(prependStr, str): + return ('\n'+prependStr).join(str.splitlines()) + +def pprint(object, useRepr=True): + def recur(ob): + return pprint(ob, useRepr) + def wrapString(prefix, string, suffix): + return '%s%s%s' % (prefix, + prependLines(' ' * len(prefix), + string), + suffix) + def pprintArgs(name, args): + return wrapString(name + '(', ',\n'.join(map(recur,args)), ')') + + if isinstance(object, tuple): + return wrapString('(', ',\n'.join(map(recur,object)), + [')',',)'][len(object) == 1]) + elif isinstance(object, list): + return wrapString('[', ',\n'.join(map(recur,object)), ']') + elif isinstance(object, set): + return pprintArgs('set', list(object)) + elif isinstance(object, dict): + elts = [] + for k,v in object.items(): + kr = recur(k) + vr = recur(v) + elts.append('%s : %s' % (kr, + prependLines(' ' * (3 + len(kr.splitlines()[-1])), + vr))) + return wrapString('{', ',\n'.join(elts), '}') + else: + if useRepr: + return repr(object) + return str(object) + +def prefixAndPPrint(prefix, object, useRepr=True): + return prefix + prependLines(' '*len(prefix), pprint(object, useRepr)) diff --git a/clang/tools/ccc/ccclib/__init__.py b/clang/tools/ccc/ccclib/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/clang/tools/ccc/test/ccc/Xarch.c b/clang/tools/ccc/test/ccc/Xarch.c new file mode 100644 index 000000000000..55014a757c7b --- /dev/null +++ b/clang/tools/ccc/test/ccc/Xarch.c @@ -0,0 +1,8 @@ +// RUN: xcc -### -fsyntax-only -Xarch_i386 -Wall -Xarch_ppc -Wunused -arch i386 -arch ppc %s > %t && +// RUN: grep '"-Xarch"' %t | count 0 && +// RUN: grep '"-Wall"' %t | count 1 && +// RUN: grep '"-arch" "i386"' %t | count 1 && +// RUN: grep '"-Wall"' %t | grep '"-arch" "i386"' | count 1 && +// RUN: grep '"-Wunused"' %t | count 1 && +// RUN: grep '"-arch" "ppc"' %t | count 1 && +// RUN: grep '"-Wunused"' %t | grep '"-arch" "ppc"' | count 1 diff --git a/clang/tools/ccc/test/ccc/argument-types.c b/clang/tools/ccc/test/ccc/argument-types.c new file mode 100644 index 000000000000..2de1593a9bb2 --- /dev/null +++ b/clang/tools/ccc/test/ccc/argument-types.c @@ -0,0 +1,16 @@ +// Input argument: +// RUN: xcc -ccc-print-options %s | grep 'Name: "", Values: {"%s"}' | count 1 && + +// Joined or separate arguments: +// RUN: xcc -ccc-print-options -xc -x c | grep 'Name: "-x", Values: {"c"}' | count 2 && + +// Joined and separate arguments: +// RUN: xcc -ccc-print-options -Xarch_mips -run | grep 'Name: "-Xarch_", Values: {"mips", "-run"}' | count 1 && + +// Multiple arguments: +// RUN: xcc -ccc-print-options -sectorder 1 2 3 | grep 'Name: "-sectorder", Values: {"1", "2", "3"}' | count 1 && + +// Unknown argument: +// RUN: xcc -ccc-print-options -=== | grep 'Name: "", Values: {"-==="}' | count 1 && + +// RUN: true diff --git a/clang/tools/ccc/test/ccc/hello.c b/clang/tools/ccc/test/ccc/hello.c new file mode 100644 index 000000000000..96f59f6747c6 --- /dev/null +++ b/clang/tools/ccc/test/ccc/hello.c @@ -0,0 +1,7 @@ +// RUN: xcc %s -o %t && +// RUN: %t | grep "Hello, World" + +int main() { + printf("Hello, World!\n"); + return 0; +} diff --git a/clang/tools/ccc/test/ccc/integrated-cpp.c b/clang/tools/ccc/test/ccc/integrated-cpp.c new file mode 100644 index 000000000000..47508610d9ec --- /dev/null +++ b/clang/tools/ccc/test/ccc/integrated-cpp.c @@ -0,0 +1,3 @@ +// RUN: xcc -fsyntax-only -### %s | count 1 && +// RUN: xcc -fsyntax-only -### %s -no-integrated-cpp | count 2 && +// RUN: xcc -fsyntax-only -### %s -save-temps | count 2 diff --git a/clang/tools/ccc/test/ccc/invalid.c b/clang/tools/ccc/test/ccc/invalid.c new file mode 100644 index 000000000000..3620b458ad2d --- /dev/null +++ b/clang/tools/ccc/test/ccc/invalid.c @@ -0,0 +1 @@ +// RUN: not xcc -### -c -o %t %s %s diff --git a/clang/tools/ccc/test/ccc/phases.c b/clang/tools/ccc/test/ccc/phases.c new file mode 100644 index 000000000000..2d350c2f2581 --- /dev/null +++ b/clang/tools/ccc/test/ccc/phases.c @@ -0,0 +1,46 @@ +// One C file. +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases a.c > %t && +// RUN: grep '0: input, "a.c", c' %t && +// RUN: grep '1: preprocessor, {0}, cpp-output' %t && +// RUN: grep '2: compiler, {1}, assembler' %t && +// RUN: grep '3: assembler, {2}, object' %t && +// RUN: grep '4: linker, {3}, image' %t && + +// PCH. +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -x c-header a.h > %t && +// RUN: grep '0: input, "a.h", c-header' %t && +// RUN: grep '1: preprocessor, {0}, c-header-cpp-output' %t && +// RUN: grep '2: precompiler, {1}, precompiled-header' %t && + +// Assembler w/ and w/o preprocessor. +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -x assembler a.s > %t && +// RUN: grep '0: input, "a.s", assembler' %t && +// RUN: grep '1: assembler, {0}, object' %t && +// RUN: grep '2: linker, {1}, image' %t && +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -x assembler-with-cpp a.s > %t && +// RUN: grep '0: input, "a.s", assembler-with-cpp' %t && +// RUN: grep '1: preprocessor, {0}, assembler' %t && +// RUN: grep '2: assembler, {1}, object' %t && +// RUN: grep '3: linker, {2}, image' %t && + +// Check the various ways of early termination. +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -E a.c > %t && +// RUN: not grep ': compiler, ' %t && +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -fsyntax-only a.c > %t && +// RUN: grep ': compiler, {1}, nothing' %t && +// RUN: not grep ': assembler, ' %t && +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -S a.c > %t && +// RUN: not grep ': assembler, ' %t && +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -c a.c > %t && +// RUN: not grep ': linker, ' %t && + +// Multiple output files. +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -c a.c b.c > %t && +// RUN: grep ': assembler,' %t | count 2 && + +// FIXME: Only for darwin. +// Treat -filelist as a linker input. +// RUN: xcc -ccc-no-driver-driver -ccc-print-phases -filelist /dev/null > %t && +// RUN: grep '1: linker, {0}, image' %t && + +// RUN: true diff --git a/clang/tools/ccc/test/ccc/universal-hello.c b/clang/tools/ccc/test/ccc/universal-hello.c new file mode 100644 index 000000000000..a482e536acec --- /dev/null +++ b/clang/tools/ccc/test/ccc/universal-hello.c @@ -0,0 +1,7 @@ +// RUN: xcc -arch ppc -arch i386 -arch x86_64 %s -o %t && +// RUN: %t | grep "Hello, World" + +int main() { + printf("Hello, World!\n"); + return 0; +} diff --git a/clang/tools/ccc/xcc b/clang/tools/ccc/xcc new file mode 100755 index 000000000000..70954aca9566 --- /dev/null +++ b/clang/tools/ccc/xcc @@ -0,0 +1,12 @@ +#!/usr/bin/python + +import sys +from ccclib import Driver + +def main(): + d = Driver.Driver() + # FIXME: We should pass program name here as well. + d.run(sys.argv[1:]) + +if __name__=='__main__': + main() -- GitLab