#!/usr/bin/env python
##
## Name: findsizes.py
## Purpose: Find acceptable digit and word types for IMath.
## Author: M. J. Fromberger
##
## Copyright (C) 2002-2008 Michael J. Fromberger, All Rights Reserved.
##
## Permission is hereby granted, free of charge, to any person obtaining a
## copy of this software and associated documentation files (the "Software"),
## to deal in the Software without restriction, including without limitation
## the rights to use, copy, modify, merge, publish, distribute, sublicense,
## and/or sell copies of the Software, and to permit persons to whom the
## Software is furnished to do so, subject to the following conditions:
##
## The above copyright notice and this permission notice shall be included in
## all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
## THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
## DEALINGS IN THE SOFTWARE.
##
import getopt, os, re, subprocess, sys, tempfile
# These are the type names to test for suitability. If your compiler
# does not support "long long", e.g., it is strict ANSI C90, then you
# should remove "long long" from this list.
try_types = [ "unsigned %s" % s for s in
("char", "short", "int", "long", "long long") ]
def main(args):
"""Scan the Makefile to find appropriate compiler settings, and then
compile a test program to emit the sizes of the various types that are
considered candidates. The -L (--nolonglong) command line option disables
the use of the"long long" type, which is not standard ANSI C90; by default,
"long long" is considered.
"""
try:
opts, args = getopt.getopt(args, 'L', ('nolonglong',))
except getopt.GetoptError, e:
print >> sys.stderr, "Usage: findsizes.py [-L/--nolonglong]"
sys.exit(1)
for opt, arg in opts:
if opt in ('-L', '--nolonglong'):
try:
try_types.pop(try_types.index("unsigned long long"))
except ValueError: pass
vars = get_make_info()
sizes = get_type_sizes(try_types, vars.get('CC', 'cc'),
vars.get('CFLAGS', '').split())
stypes = sorted(sizes.keys(), key = lambda k: sizes[k], reverse = True)
word_type = stypes[0]
for t in stypes[1:]:
if sizes[t] <= sizes[word_type] / 2:
digit_type = t
break
else:
print >> sys.stderr, "Unable to find a compatible digit type."
sys.exit(1)
print "typedef %-20s mp_word; /* %d bytes */\n" \
"typedef %-20s mp_digit; /* %d bytes */" % \
(word_type, sizes[word_type], digit_type, sizes[digit_type])
def get_type_sizes(types, cc, cflags = ()):
"""Return a dictionary mapping the names of the specified types to
their sizes in bytes, based on the output of the C compiler whose
path and arguments are given.
"""
fd, tpath = tempfile.mkstemp(suffix = '.c')
fp = os.fdopen(fd, 'r+')
fp.seek(0)
fp.write("#include \n\nint main(void)\n{\n")
for t in types:
fp.write(' printf("%%lu\\t%%s\\n", (unsigned long) sizeof(%s), '
'\"%s\");\n' % (t, t))
fp.write('\n return 0;\n}\n')
fp.close()
print >> sys.stderr, \
"Compiler: %s\n" \
"Flags: %s\n" \
"Source: %s\n" % (cc, ' '.join(cflags), tpath)
cmd = [cc] + list(cflags) + [tpath]
if subprocess.call(cmd) <> 0:
raise ValueError("Error while running '%s'" % ' '.join(cmd))
os.unlink(tpath)
if not os.path.isfile('a.out'):
raise ValueError("No executable a.out found")
result = subprocess.Popen(['./a.out'],
stdout = subprocess.PIPE).communicate()[0]
out = {}
for line in result.split('\n'):
if line.strip() == '':
continue
size, type = re.split(r'\s+', line, 1)
out[type] = int(size)
os.unlink("a.out")
return out
def sub_make_vars(input, vars):
"""Perform Make style variable substitution in the given input
string, using vars as a dictionary of variables to substitute.
"""
def frep(m):
try:
return vars[m.group(1).strip()]
except KeyError:
return ' '
expr = re.compile(r'\$\((\s*\w+\s*)\)')
out = input
while True:
next = expr.sub(frep, out)
if next == out:
break
out = next
return out
def get_make_info(target = None, makefile = None, makepath = "make",
defs = ()):
"""Extract a listing of all of the variables defined by Make.
Returns a dictionary mapping variable names to their string
values.
Optional arguments:
target -- the name of the target to request that Make build.
makefile -- the path to the Makefile.
makepath -- the path to the make executable.
defs -- a sequence of strings, additional make arguments.
"""
cmd = [makepath]
if defs:
cmd.extend(defs)
cmd.extend(("-p", "-n"))
if makefile is not None:
cmd.extend(["-f", makefile])
if target is not None:
cmd.append(target)
output = subprocess.Popen(cmd,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE).communicate()[0]
vrule = re.compile(r'^(\w+)\s*=\s*(.*)$')
vars = {}
for line in output.split('\n'):
m = vrule.match(line)
if m:
vars[m.group(1)] = m.group(2)
for key, val in vars.iteritems():
vars[key] = sub_make_vars(val, vars)
return vars
if __name__ == "__main__":
main(sys.argv[1:])
# Here there be dragons