3 ### Generate multiprecision integer representations
5 ### (c) 2013 Straylight/Edgeware
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of Catacomb.
12 ### Catacomb is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU Library General Public License as
14 ### published by the Free Software Foundation; either version 2 of the
15 ### License, or (at your option) any later version.
17 ### Catacomb is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ### GNU Library General Public License for more details.
22 ### You should have received a copy of the GNU Library General Public
23 ### License along with Catacomb; if not, write to the Free
24 ### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25 ### MA 02111-1307, USA.
27 from __future__ import with_statement
33 from sys import stdout
35 ###--------------------------------------------------------------------------
38 def write_header(mode, name):
40 Write a C-language file header.
42 The header comment identifies the processing MODE, and the NAME of the
46 /* -*-c-*- GENERATED by mpgen (%s)
53 def write_banner(text):
54 """Write a separator banner to the output, with header TEXT."""
55 stdout.write("/*----- %s %s*/\n" % (text, '-' * (66 - len(text))))
57 class struct (object):
59 A struct object exists so that you can set attributes on it.
63 R_IDBAD = RX.compile('[^0-9A-Za-z]')
65 """Replace non-alphanumeric characters in NAME with underscores."""
66 return R_IDBAD.sub('_', name)
68 ###--------------------------------------------------------------------------
69 ### Determining the appropriate types.
71 ## A dictionary mapping type tags to subclasses of BasicIntType.
74 class IntClass (type):
76 The IntClass is a metaclass for integer-type classes.
78 It associates the type class with its tag in the `TYPEMAP' dictionary.
80 def __new__(cls, name, supers, dict):
81 c = type.__new__(cls, name, supers, dict)
82 try: TYPEMAP[c.tag] = c
83 except AttributeError: pass
86 class BasicIntType (object):
88 A base class for integer-type classes, providing defaults and protocol.
90 Integer-type classes have the following attributes and methods.
92 preamble Some code to be emitted to a header file to make use
93 of the type. Defaults to the empty string.
95 typedef_prefix A prefix to be written before the type's name in a
96 `typedef' declaration, possibly to muffle warnings.
97 Defaults to the empty string.
99 literalfmt A Python `%' format string for generating literal
100 values of the type; used by the default `literal'
101 method (so if you override it then you don't need to
102 set this). Defaults to `%su'.
104 literal(VALUE, [FMT]) Emit a literal value of the type, encoding VALUE.
105 The default FMT has the form `0x%0Nx', so as to emit
106 in hex with the appropriate number of leading zeros.
108 Instances also carry additional attributes.
110 bits The width of the integer type, in bits.
112 rank An integer giving the conversion rank of the type.
113 Higher values generally denote wider types.
115 litwd The width of a literal of the type, in characters.
117 __metaclass__ = IntClass
121 def __init__(me, bits, rank):
124 me.litwd = len(me.literal(0))
125 def literal(me, value, fmt = None):
126 if fmt is None: fmt = '0x%0' + str((me.bits + 3)//4) + 'x'
127 return me.literalfmt % (fmt % value)
129 class UnsignedCharType (BasicIntType):
131 name = 'unsigned char'
133 class UnsignedShortType (BasicIntType):
135 name = 'unsigned short'
137 class UnsignedIntType (BasicIntType):
139 name = 'unsigned int'
141 class UnsignedLongType (BasicIntType):
143 name = 'unsigned long'
146 class UnsignedLongLongType (BasicIntType):
148 name = 'unsigned long long'
150 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 91)
151 # define CATACOMB_GCC_EXTENSION __extension__
153 # define CATACOMB_GCC_EXTENSION
156 typedef_prefix = 'CATACOMB_GCC_EXTENSION '
157 literalfmt = 'CATACOMB_GCC_EXTENSION %sull'
159 class UIntMaxType (BasicIntType):
162 preamble = "\n#include <stdint.h>\n"
164 class TypeChoice (object):
166 A TypeChoice object selects C integer types for multiprecision integers.
168 It exports its decisions as attributes:
170 mpwbits The width of an `mpw', in bits.
172 mpw The integer type for single-precision values.
174 mpd The integer type for double-precision values.
176 ti An object bearing raw information about the available
177 integer types, as follows...
179 TYPEINFO A list of items (TAG, BITS) describing the widths of
180 the available types suitable for multiprecision use,
181 in ascending order of rank.
183 LIMITS A list of items (TAG, LO, HI) describing the ranges
184 of integer types, for constructing the `mplimits'
188 def __init__(me, tifile):
190 Read the definitions from TIFILE, and select types appropriately.
192 The TIFILE is a tiny fragment of Python code which should set `TYPEINFO'
193 and `LIMITS' in its global namespace.
196 ## Load the captured type information.
197 me.ti = TY.ModuleType('typeinfo')
198 execfile(opts.typeinfo, me.ti.__dict__)
200 ## Build a map of the available types.
203 for tag, bits in me.ti.TYPEINFO:
206 byrank.append(TYPEMAP[tag](bits, rank))
208 ## First pass: determine a suitable word size. The criteria are (a)
209 ## there exists another type at least twice as long (so that we can do a
210 ## single x single -> double multiplication), and (b) operations on a
211 ## word are efficient (so we'd prefer a plain machine word). We'll start
212 ## at `int' and work down. Maybe this won't work: there's a plan B.
215 while not mpwbits and i >= 0:
216 ibits = byrank[i].bits
217 for j in xrange(i + 1, len(byrank)):
218 if byrank[j].bits >= 2*ibits:
222 ## If that didn't work, then we'll start with the largest type available
223 ## and go with half its size.
225 mpwbits = byrank[-1].bits//2
227 ## Make sure we've not ended up somewhere really silly.
229 raise Exception, "`mpw' type is too small: your C environment is weird"
231 ## Now figure out suitable types for `mpw' and `mpd'.
232 def find_type(bits, what):
234 if ty.bits >= bits: return ty
236 "failed to find suitable %d-bit type, for %s" % (bits, what)
238 ## Store our decisions.
240 me.mpw = find_type(mpwbits, 'mpw')
241 me.mpd = find_type(mpwbits*2, 'mpd')
243 ###--------------------------------------------------------------------------
244 ### Outputting constant multiprecision integers.
246 ## The margin for outputting MP literals.
249 def write_preamble():
251 Write a preamble for files containing multiprecision literals.
253 We define a number of macros for use by the rest of the code:
255 ZERO_MP An `mp' initializer denoting the value zero.
257 POS_MP(NAME) Constructs an `mp' initializer denoting a positive
258 integer whose limbs were emitted with the given
261 NEG_MP(NAME) Constructs an `mp' initializer denoting a negative
262 integer whose limbs were emitted with the given
266 #include <mLib/macros.h>
267 #define MP_(name, flags) \\
268 { (/*unconst*/ mpw *)name##__mpw, \\
269 (/*unconst*/ mpw *)name##__mpw + N(name##__mpw), \\
270 N(name##__mpw), 0, MP_CONST | flags, 0 }
271 #define ZERO_MP { 0, 0, 0, 0, MP_CONST, 0 }
272 #define POS_MP(name) MP_(name, 0)
273 #define NEG_MP(name) MP_(name, MP_NEG)
276 def write_limbs(name, x):
278 Write the limbs of the value X, with the given NAME.
281 ## We don't need to do anything special for zero.
284 ## Start on the limb vector. No delimiter needed, and we shall need to
285 ## start a new line before any actual output. We want to write the
286 ## absolute value here, because we use a signed-magnitude representation.
287 stdout.write("\nstatic const mpw %s__mpw[] = {" % name)
291 mask = (1 << TC.mpwbits) - 1
293 ## We work from the little-end up, picking off `mpwbits' at a time. Start
294 ## a new line if we can't fit the value on the current one.
296 w, x = x & mask, x >> TC.mpwbits
297 f = TC.mpw.literal(w)
298 if pos + 2 + len(f) <= MARGIN:
299 stdout.write(sep + ' ' + f)
302 stdout.write(sep + '\n ' + f)
306 ## We're done. Finish off the initializer.
307 stdout.write("\n};\n")
309 def mp_body(name, x):
311 Write the body of an `mp' object, for the value NAME.
313 return "%s_MP(%s)" % (x >= 0 and "POS" or "NEG", name)
315 ###--------------------------------------------------------------------------
316 ### Mode definition machinery.
318 ## A dictionary mapping mode names to their handler functions.
323 Function decorator: associate the function with the name of a mode.
325 The mode name is taken from the function's name: a leading `m_' is stripped
326 off, if there is one. Mode functions are invoked with the positional
327 arguments from the command and are expected to write their output to
330 name = func.func_name
331 if name.startswith('m_'): name = name[2:]
335 ###--------------------------------------------------------------------------
336 ### The basic types header.
341 Write the `mptypes.h' header.
343 This establishes the following types.
345 mpw An integer type for single-precision values.
347 mpd An integer type for double-precision values.
349 And, for t being each of `w' or `d', the following constants:
351 MPt_BITS The width of the type, in bits.
353 MPt_P2 The smallest integer k such that 2^k is not less than
354 MPt_BITS. (This is used for binary searches.)
356 MPt_MAX The largest value which may be stored in an object of
360 ## Write the preamble.
361 write_header("mptypes", "mptypes.h")
363 #ifndef CATACOMB_MPTYPES_H
364 #define CATACOMB_MPTYPES_H
367 ## Write any additional premable for the types we've selected.
368 have = set([TC.mpw, TC.mpd])
370 stdout.write(t.preamble)
372 ## Emit the types and constants.
373 for label, t, bits in [('mpw', TC.mpw, TC.mpwbits),
374 ('mpd', TC.mpd, TC.mpwbits*2)]:
375 LABEL = label.upper()
376 stdout.write("\n%stypedef %s %s;\n" % (t.typedef_prefix, t.name, label))
377 stdout.write("#define %s_BITS %d\n" % (LABEL, bits))
379 while 2*i < bits: i *= 2
380 stdout.write("#define %s_P2 %d\n" % (LABEL, i))
381 stdout.write("#define %s_MAX %s\n" % (LABEL,
382 t.literal((1 << bits) - 1, "%d")))
385 stdout.write("\n#endif\n")
387 ###--------------------------------------------------------------------------
393 Write the `mplimits.c' source file.
395 This contains `mp' constants corresponding to the various integer types'
396 upper and lower bounds. The output is a vector `mp_limits' consisting of
397 the distinct nonzero bounds, in order of their first occurrence in the
401 ## Write the preamble.
402 write_header("mplimits_c", "mplimits.c")
403 stdout.write('#include "mplimits.h"\n')
406 ## Write out limbs for limits as we come across them.
410 if not x or x in seen: return
412 write_limbs('limits_%d' % len(v), x)
414 for tag, lo, hi in TC.ti.LIMITS:
418 ## Write the main vector.
419 stdout.write("\nmp mp_limits[] = {")
423 stdout.write("%s%s_MP(limits_%d)" % (sep, x < 0 and "NEG" or "POS", i))
426 stdout.write("\n};\n");
431 Write the `mplimits.h' source file.
433 For each type TAG, this defines constants MP_TAG_MIN and MP_TAG_MAX
434 representing the lower and upper bounds of the type.
437 ## Write the preamble.
438 write_header("mplimits_h", "mplimits.h")
440 #ifndef CATACOMB_MPLIMITS_H
441 #define CATACOMB_MPLIMITS_H
443 #ifndef CATACOMB_MP_H
447 extern mp mp_limits[];
451 ## Now define constants for the bounds. Things which are zero can go to
452 ## our existing `MP_ZERO'; otherwise we index the `mp_limits' vector.
453 seen = { 0: "MP_ZERO" }
459 r = seen[x] = '(&mp_limits[%d])' % slot[0]
462 for tag, lo, hi in TC.ti.LIMITS:
463 stdout.write("#define MP_%s_MIN %s\n" % (tag, find(lo)))
464 stdout.write("#define MP_%s_MAX %s\n" % (tag, find(hi)))
467 stdout.write("\n#endif\n")
469 ###--------------------------------------------------------------------------
472 class GroupTableClass (type):
474 Metaclass for group tables, which registers them in the `MODEMAP'.
476 Such a class must define an attribute `mode' giving the mode name, and a
477 class method `run' which writes the necessary output.
479 def __new__(cls, name, supers, dict):
480 c = type.__new__(cls, name, supers, dict)
482 except AttributeError: pass
483 else: MODEMAP[c.mode] = c.run
486 class GroupTable (object):
488 Base class for group tables objects.
490 A `group table' is a table of constants, typically defining a cyclic group
491 or something similar. We read the values from an input file, and write
492 them out as C definitions. These have a somewhat stereotyped format, so we
493 can mostly handle them uniformly.
495 Specifically, input files consist of lines which are split into
496 whitespace-separated words. Blank lines, and lines beginning with `#', are
497 ignored. The remaining lines are gathered together into stanzas of the
500 KEYWORD NAME [HEAD-VALUE ...]
504 (Indentation is shown for clarity only.) Such a stanza describes a group
505 NAME; some slots are assigned values from the headline, and others from
506 their own individual lines.
508 Subclasses must define the following attributes.
510 data_t The name of the type for describing a particular
513 entry_t The name of the type which associates a name with
514 some group data; this will be defined as
516 typedef struct ENTRY_T {
523 filename The filename, typically `SOMETHING.c', to put in the
526 header The header file to include, so as to establish the
527 necessary types and other definitions.
529 keyword The keyword beginning a new definition in the input
530 file. The default is `group'.
532 mode The mode name, used to invoke this kind of table
533 operation (used by GroupTableClass).
535 slots A vector of slot objects (see BaseSlot for the
536 protocol) describing the structure of this particular
537 kind of group, in the order they should be written in
540 Instances carry an `st' attribute, which contains a `struct' object in
541 which slots can maintain some state. This object carries the following
542 attributes maintained by this class.
544 d A dictionary mapping slots (not their names!) to
545 values assigned in the current stanza. This is reset
546 at the start of each stanza. Slot implementations
547 a free to use this or not, and the representation is
548 internal to the specific slot class.
550 mpmap A dictionary mapping values (integers, or `None') to
551 C initializers (typically, actually, macro
554 name The name of the group currently being parsed.
556 nextmp Index for the next `mp' object to be written.
559 ## Additional attributes, for internal use:
561 ## _defs A set of known names for groups.
563 ## _headslots A list of slots filled in from the headline.
565 ## _names A list of pairs (ALIAS, DATA) mapping alias names to
566 ## the actual group data.
568 ## _slotmap A dictionary mapping slot names to their
571 __metaclass__ = GroupTableClass
579 Initialize a group table object.
582 me.st = st = struct()
584 st.mpmap = { None: 'NO_MP', 0: 'ZERO_MP' }
589 me._slotmap = dict([(s.name, s) for s in me.slots])
590 me._headslots = [s for s in me.slots if s.headline]
594 Write out the data for a group once we've detected the end of its stanza.
597 ## If there's no current stanza, then do nothing.
598 if me.st.name is None: return
600 ## Start emitting the object.
601 stdout.write("/* --- %s --- */\n" % me.st.name)
603 ## Get the various slots to compute themselves.
604 for s in me.slots: s.setup(me.st)
606 ## Write the initializer.
607 stdout.write("\nstatic %s c_%s = {" % (me.data_t, fix_name(me.st.name)))
613 stdout.write("\n};\n\n")
615 ## Clear the state for the next stanza.
622 Main output for a group table. Reads the file INPUT.
625 ## Make an object for us to work on.
628 ## Write the output preamble.
629 write_header(me.mode, me.filename)
630 stdout.write('#include "%s"\n' % me.header)
632 stdout.write("#define NO_MP { 0, 0, 0, 0, 0, 0 }\n\n")
634 ## The main group data. This will contain a `data_t' object for each
635 ## group we read. We'll also build the name to data map as we go.
636 write_banner("Group data")
638 with open(input) as file:
641 ## Parse the line into fields.
643 if not ff or ff[0].startswith('#'): continue
646 ## An alias. Just remember this.
647 if len(ff) != 3: raise Exception, "wrong number of alias arguments"
649 me._names.append((ff[1], ff[2]))
651 elif ff[0] == me.keyword:
652 ## A headline for a new group.
654 ## Check the headline syntax. Headline slots may be set here, or
656 if len(ff) < 2 or len(ff) > 2 + len(me._headslots):
657 raise Exception, "bad number of headline arguments"
659 ## Flush out the previous stanza.
662 ## Remember the new stanza's name, and add it to the list.
663 me.st.name = name = ff[1]
665 me._names.append((name, name))
667 ## Set headline slots from the remaining headline words.
668 for f, s in zip(ff[2:], me._headslots): s.set(me.st, f)
670 elif ff[0] in me._slotmap:
671 ## A slot assignment. Get the slot to store a value.
672 if me.st.name is None:
673 raise Exception, "no group currently being defined"
675 raise Exception, "bad number of values for slot `%s'" % ff[0]
676 me._slotmap[ff[0]].set(me.st, ff[1])
679 ## Something incomprehensible.
680 raise Exception, "unknown keyword `%s'" % ff[0]
682 ## End of the input. Write out the final stanza.
685 ## Now for the name-to-data mapping.
686 write_banner("Main table")
687 stdout.write("\nconst %s %s[] = {\n" % (me.entry_t, me.tabname))
688 for a, n in me._names:
689 if n not in me._defs:
690 raise Exception, "alias `%s' refers to unknown group `%s'" % (a, n)
691 stdout.write(' { "%s", &c_%s },\n' % (a, fix_name(n)))
692 stdout.write(" { 0, 0 }\n};\n\n")
695 write_banner("That's all, folks")
697 class BaseSlot (object):
699 Base class for slot types.
701 The slot protocol works as follows. Throughout, ST is a state object as
702 maintained by a GroupTable.
704 __init__(NAME, [HEADLINE], [OMITP], [ALLOWP], ...)
705 Initialize the slot. The NAME identifies the slot,
706 and the keyword used to set it in input files. If
707 HEADLINE is true then the slot can be set from the
708 stanza headline. OMITP and ALLOWP are optional
709 functions: if OMITP(ST) returns true then the slot
710 may be omitted; conversely, if ALLOWP(ST, VALUE)
711 returns false then the slot cannot be assigned the
712 given VALUE. Other arguments may be allowed by
715 set(ST, VALUE) Set the slot to the given VALUE, typically by setting
716 ST.d[me]. The default just stores the VALUE without
719 setup(ST) Prepare the slot for output. The default method just
720 checks that ST.d contains a mapping for the slot.
721 All of the stanza's slots are set up before starting
722 on the initializer for the group data, so slots can
723 use this opportunity to emit preparatory definitions.
725 write(ST) Write an initializer for the slot to standard
726 output. There is no default.
728 The following attributes are exported.
730 headline A flag: can the slot be initialized from the stanza
733 name The slot's name.
736 def __init__(me, name, headline = False, omitp = None, allowp = None):
738 Initialize a new slot object, setting the necessary attributes.
741 me.headline = headline
745 def set(me, st, value):
747 Store a VALUE for the slot.
749 if me._allowp and not me._allowp(st, value):
750 raise Exception, "slot `%s' not allowed here" % me.name
755 Prepare the slot for output, checking its value and so on.
757 if me not in st.d and (not me._omitp or not me._omitp(st)):
758 raise Exception, "missing slot `%s'" % me.name
760 class EnumSlot (BaseSlot):
762 An EnumSlot object represents a slot which can contain one of a number of
765 An omitted value is written as a literal `0'.
768 def __init__(me, name, prefix, values, **kw):
770 Initialize an EnumSlot object.
772 The VALUES are a set of value names. On output, a value is converted to
773 uppercase, and prefixed by the PREFIX and an underscore.
775 super(EnumSlot, me).__init__(name, **kw)
776 me._values = set(values)
779 def set(me, st, value):
781 Check that the VALUE is one of the ones we know.
783 if value not in me._values:
784 raise Exception, "invalid %s value `%s'" % (me.name, value)
785 super(EnumSlot, me).set(st, value)
789 Convert the slot value to the C constant name.
791 try: stdout.write('%s_%s' % (me._prefix, st.d[me].upper()))
792 except KeyError: stdout.write('0')
794 class MPSlot (BaseSlot):
796 An MPSlot object represents a slot which can contain a multiprecision
799 An omitted value is written as a invalid `mp' object.
802 def set(me, st, value):
804 Set a value; convert it to a Python integer.
806 super(MPSlot, me).set(st, long(value, 0))
810 Prepare to write the slot.
812 If this is a new integer, then write out a limb vector. Names for the
813 limbs are generated unimaginitively, using a counter.
815 super(MPSlot, me).setup(st)
817 if v not in st.mpmap:
818 write_limbs('v%d' % st.nextmp, v)
819 st.mpmap[v] = mp_body('v%d' % st.nextmp, v)
824 Write out an `mp' initializer for the slot.
826 stdout.write(st.mpmap[st.d.get(me)])
828 class BinaryGroupTable (GroupTable):
830 filename = 'bintab.c'
835 slots = [MPSlot('p'), MPSlot('q'), MPSlot('g')]
837 class EllipticCurveTable (GroupTable):
845 _typeslot = EnumSlot('type', 'FTAG',
846 ['prime', 'niceprime', 'binpoly', 'binnorm'],
851 allowp = lambda st, _:
852 st.d[EllipticCurveTable._typeslot] == 'binnorm',
854 st.d[EllipticCurveTable._typeslot] != 'binnorm'),
855 MPSlot('a'), MPSlot('b'), MPSlot('r'), MPSlot('h'),
856 MPSlot('gx'), MPSlot('gy')]
858 class PrimeGroupTable (GroupTable):
865 slots = [MPSlot('p'), MPSlot('q'), MPSlot('g')]
867 ###--------------------------------------------------------------------------
870 op = OP.OptionParser(
871 description = 'Generate multiprecision integer representations',
872 usage = 'usage: %prog [-t TYPEINFO] MODE [ARGS ...]',
873 version = 'Catacomb, version @VERSION@')
874 for shortopt, longopt, kw in [
875 ('-t', '--typeinfo', dict(
876 action = 'store', metavar = 'PATH', dest = 'typeinfo',
877 help = 'alternative typeinfo file'))]:
878 op.add_option(shortopt, longopt, **kw)
879 op.set_defaults(typeinfo = './typeinfo.py')
880 opts, args = op.parse_args()
882 ## Parse the positional arguments.
883 if len(args) < 1: op.error('missing MODE')
886 ## Establish the choice of low-level C types.
887 TC = TypeChoice(opts.typeinfo)
889 ## Find the selected mode, and invoke the appropriate handler.
890 try: modefunc = MODEMAP[mode]
891 except KeyError: op.error("unknown mode `%s'" % mode)
894 ###----- That's all, folks --------------------------------------------------