import itertools from random import randint import time import inspect import functools import copy from Compiler.exceptions import * from Compiler.config import * from Compiler import util from Compiler import tools ### ### Opcode constants ### ### Whenever these are changed the corresponding enums in Processor/instruction.h ### MUST also be changed. (+ the documentation) ### opcodes = dict( # Load/store LDI = 0x1, LDSI = 0x2, LDMC = 0x3, LDMS = 0x4, STMC = 0x5, STMS = 0x6, LDMCI = 0x7, LDMSI = 0x8, STMCI = 0x9, STMSI = 0xA, MOVC = 0xB, MOVS = 0xC, PROTECTMEMS = 0xD, PROTECTMEMC = 0xE, PROTECTMEMINT = 0xF, LDMINT = 0xCA, STMINT = 0xCB, LDMINTI = 0xCC, STMINTI = 0xCD, PUSHINT = 0xCE, POPINT = 0xCF, MOVINT = 0xD0, # Machine LDTN = 0x10, LDARG = 0x11, REQBL = 0x12, STARG = 0x13, TIME = 0x14, START = 0x15, STOP = 0x16, USE = 0x17, USE_INP = 0x18, RUN_TAPE = 0x19, JOIN_TAPE = 0x1A, CRASH = 0x1B, USE_PREP = 0x1C, STARTGRIND = 0x1D, STOPGRIND = 0x1E, # Addition ADDC = 0x20, ADDS = 0x21, ADDM = 0x22, ADDCI = 0x23, ADDSI = 0x24, SUBC = 0x25, SUBS = 0x26, SUBML = 0x27, SUBMR = 0x28, SUBCI = 0x29, SUBSI = 0x2A, SUBCFI = 0x2B, SUBSFI = 0x2C, # Multiplication/division MULC = 0x30, MULM = 0x31, MULCI = 0x32, MULSI = 0x33, DIVC = 0x34, DIVCI = 0x35, MODC = 0x36, MODCI = 0x37, LEGENDREC = 0x38, DIGESTC = 0x39, INV2M = 0x3a, GMULBITC = 0x136, GMULBITM = 0x137, # Open OPEN = 0xA5, MULS = 0xA6, MULRS = 0xA7, DOTPRODS = 0xA8, # Data access TRIPLE = 0x50, BIT = 0x51, SQUARE = 0x52, INV = 0x53, GBITTRIPLE = 0x154, GBITGF2NTRIPLE = 0x155, INPUTMASK = 0x56, PREP = 0x57, # Input INPUT = 0x60, STARTINPUT = 0x61, STOPINPUT = 0x62, READSOCKETC = 0x63, READSOCKETS = 0x64, WRITESOCKETC = 0x65, WRITESOCKETS = 0x66, READSOCKETINT = 0x69, WRITESOCKETINT = 0x6a, WRITESOCKETSHARE = 0x6b, LISTEN = 0x6c, ACCEPTCLIENTCONNECTION = 0x6d, CONNECTIPV4 = 0x6e, READCLIENTPUBLICKEY = 0x6f, # Bitwise logic ANDC = 0x70, XORC = 0x71, ORC = 0x72, ANDCI = 0x73, XORCI = 0x74, ORCI = 0x75, NOTC = 0x76, # Bitwise shifts SHLC = 0x80, SHRC = 0x81, SHLCI = 0x82, SHRCI = 0x83, # Branching and comparison JMP = 0x90, JMPNZ = 0x91, JMPEQZ = 0x92, EQZC = 0x93, LTZC = 0x94, LTC = 0x95, GTC = 0x96, EQC = 0x97, JMPI = 0x98, # Integers BITDECINT = 0x99, LDINT = 0x9A, ADDINT = 0x9B, SUBINT = 0x9C, MULINT = 0x9D, DIVINT = 0x9E, PRINTINT = 0x9F, # Conversion CONVINT = 0xC0, CONVMODP = 0xC1, GCONVGF2N = 0x1C1, # IO PRINTMEM = 0xB0, PRINTREG = 0XB1, RAND = 0xB2, PRINTREGPLAIN = 0xB3, PRINTCHR = 0xB4, PRINTSTR = 0xB5, PUBINPUT = 0xB6, RAWOUTPUT = 0xB7, STARTPRIVATEOUTPUT = 0xB8, STOPPRIVATEOUTPUT = 0xB9, PRINTCHRINT = 0xBA, PRINTSTRINT = 0xBB, PRINTFLOATPLAIN = 0xBC, WRITEFILESHARE = 0xBD, READFILESHARE = 0xBE, CONDPRINTSTR = 0xBF, PRINTFLOATPREC = 0xE0, GBITDEC = 0x184, GBITCOM = 0x185, # Secure socket INITSECURESOCKET = 0x1BA, RESPSECURESOCKET = 0x1BB ) def int_to_bytes(x): """ 32 bit int to big-endian 4 byte conversion. """ return [(x >> 8*i) % 256 for i in (3,2,1,0)] global_vector_size_stack = [] global_instruction_type_stack = ['modp'] def set_global_vector_size(size): stack = global_vector_size_stack if size == 1 and not stack: return stack.append(size) def set_global_instruction_type(t): if t == 'modp' or t == 'gf2n': global_instruction_type_stack.append(t) else: raise CompilerError('Invalid type %s for setting global instruction type') def reset_global_vector_size(): stack = global_vector_size_stack if global_vector_size_stack: stack.pop() def reset_global_instruction_type(): global_instruction_type_stack.pop() def get_global_vector_size(): stack = global_vector_size_stack if stack: return stack[-1] else: return 1 def get_global_instruction_type(): return global_instruction_type_stack[-1] def vectorize(instruction, global_dict=None): """ Decorator to vectorize instructions. """ if global_dict is None: global_dict = inspect.getmodule(instruction).__dict__ class Vectorized_Instruction(instruction): __slots__ = ['size'] def __init__(self, size, *args, **kwargs): self.size = size super(Vectorized_Instruction, self).__init__(*args, **kwargs) for arg,f in zip(self.args, self.arg_format): if issubclass(ArgFormats[f], RegisterArgFormat): arg.set_size(size) def get_code(self): return (self.size << 9) + self.code def get_pre_arg(self): return "%d, " % self.size def is_vec(self): return self.size > 1 def get_size(self): return self.size def expand(self): set_global_vector_size(self.size) super(Vectorized_Instruction, self).expand() reset_global_vector_size() @functools.wraps(instruction) def maybe_vectorized_instruction(*args, **kwargs): size = get_global_vector_size() for arg in args: try: size = arg.size break except: pass if size == 1: return instruction(*args, **kwargs) else: return Vectorized_Instruction(size, *args, **kwargs) maybe_vectorized_instruction.vec_ins = Vectorized_Instruction maybe_vectorized_instruction.std_ins = instruction vectorized_name = 'v' + instruction.__name__ Vectorized_Instruction.__name__ = vectorized_name global_dict[vectorized_name] = Vectorized_Instruction global_dict[instruction.__name__ + '_class'] = instruction return maybe_vectorized_instruction def gf2n(instruction): """ Decorator to create GF_2^n instruction corresponding to a given modp instruction. Adds the new GF_2^n instruction to the globals dictionary. Also adds a vectorized GF_2^n instruction if a modp version exists. """ global_dict = inspect.getmodule(instruction).__dict__ if global_dict.has_key('v' + instruction.__name__): vectorized = True else: vectorized = False if isinstance(instruction, type) and issubclass(instruction, Instruction): instruction_cls = instruction else: try: instruction_cls = global_dict[instruction.__name__ + '_class'] except KeyError: raise CompilerError('Cannot decorate instruction %s' % instruction) def reformat(arg_format): if isinstance(arg_format, list): __format = [] for __f in arg_format: if __f in ('int', 'p', 'ci', 'str'): __format.append(__f) else: __format.append(__f[0] + 'g' + __f[1:]) arg_format[:] = __format elif isinstance(arg_format, property): pass else: for __f in arg_format.args: reformat(__f) class GF2N_Instruction(instruction_cls): __doc__ = instruction_cls.__doc__.replace('c_', 'c^g_').replace('s_', 's^g_') __slots__ = [] field_type = 'gf2n' if isinstance(instruction_cls.code, int): code = (1 << 8) + instruction_cls.code # set modp registers in arg_format to GF2N registers if 'gf2n_arg_format' in instruction_cls.__dict__: arg_format = instruction_cls.gf2n_arg_format elif isinstance(instruction_cls.arg_format, itertools.repeat): __f = instruction_cls.arg_format.next() if __f != 'int' and __f != 'p': arg_format = itertools.repeat(__f[0] + 'g' + __f[1:]) else: arg_format = copy.deepcopy(instruction_cls.arg_format) reformat(arg_format) def is_gf2n(self): return True def expand(self): set_global_instruction_type('gf2n') super(GF2N_Instruction, self).expand() reset_global_instruction_type() GF2N_Instruction.__name__ = 'g' + instruction_cls.__name__ if vectorized: vec_GF2N = vectorize(GF2N_Instruction, global_dict) @functools.wraps(instruction) def maybe_gf2n_instruction(*args, **kwargs): if get_global_instruction_type() == 'gf2n': if vectorized: return vec_GF2N(*args, **kwargs) else: return GF2N_Instruction(*args, **kwargs) else: return instruction(*args, **kwargs) # If instruction is vectorized, new GF2N instruction must also be if vectorized: global_dict[GF2N_Instruction.__name__] = vec_GF2N else: global_dict[GF2N_Instruction.__name__] = GF2N_Instruction global_dict[instruction.__name__ + '_class'] = instruction_cls return maybe_gf2n_instruction #return instruction class RegType(object): """ enum-like static class for Register types """ ClearModp = 'c' SecretModp = 's' ClearGF2N = 'cg' SecretGF2N = 'sg' ClearInt = 'ci' Types = [ClearModp, SecretModp, ClearGF2N, SecretGF2N, ClearInt] @staticmethod def create_dict(init_value_fn): """ Create a dictionary with all the RegTypes as keys """ res = defaultdict(init_value_fn) # initialization for legacy for t in RegType.Types: res[t] return res class ArgFormat(object): @classmethod def check(cls, arg): return NotImplemented @classmethod def encode(cls, arg): return NotImplemented class RegisterArgFormat(ArgFormat): @classmethod def check(cls, arg): if not isinstance(arg, program.curr_tape.Register): raise ArgumentError(arg, 'Invalid register argument') if arg.i > REG_MAX: raise ArgumentError(arg, 'Register index too large') if arg.program != program.curr_tape: raise ArgumentError(arg, 'Register from other tape, trace: %s' % \ util.format_trace(arg.caller)) if arg.reg_type != cls.reg_type: raise ArgumentError(arg, "Wrong register type '%s', expected '%s'" % \ (arg.reg_type, cls.reg_type)) @classmethod def encode(cls, arg): return int_to_bytes(arg.i) class ClearModpAF(RegisterArgFormat): reg_type = RegType.ClearModp class SecretModpAF(RegisterArgFormat): reg_type = RegType.SecretModp class ClearGF2NAF(RegisterArgFormat): reg_type = RegType.ClearGF2N class SecretGF2NAF(RegisterArgFormat): reg_type = RegType.SecretGF2N class ClearIntAF(RegisterArgFormat): reg_type = RegType.ClearInt class IntArgFormat(ArgFormat): @classmethod def check(cls, arg): if not isinstance(arg, (int, long)): raise ArgumentError(arg, 'Expected an integer-valued argument') @classmethod def encode(cls, arg): return int_to_bytes(arg) class ImmediateModpAF(IntArgFormat): @classmethod def check(cls, arg): super(ImmediateModpAF, cls).check(arg) if arg >= 2**32 or arg < -2**32: raise ArgumentError(arg, 'Immediate value outside of 32-bit range') class ImmediateGF2NAF(IntArgFormat): @classmethod def check(cls, arg): # bounds checking for GF(2^n)??? super(ImmediateGF2NAF, cls).check(arg) class PlayerNoAF(IntArgFormat): @classmethod def check(cls, arg): super(PlayerNoAF, cls).check(arg) if arg > 256: raise ArgumentError(arg, 'Player number > 256') class String(ArgFormat): length = 16 @classmethod def check(cls, arg): if not isinstance(arg, str): raise ArgumentError(arg, 'Argument is not string') if len(arg) > cls.length: raise ArgumentError(arg, 'String longer than ' + cls.length) if '\0' in arg: raise ArgumentError(arg, 'String contains zero-byte') @classmethod def encode(cls, arg): return arg + '\0' * (cls.length - len(arg)) ArgFormats = { 'c': ClearModpAF, 's': SecretModpAF, 'cw': ClearModpAF, 'sw': SecretModpAF, 'cg': ClearGF2NAF, 'sg': SecretGF2NAF, 'cgw': ClearGF2NAF, 'sgw': SecretGF2NAF, 'ci': ClearIntAF, 'ciw': ClearIntAF, 'i': ImmediateModpAF, 'ig': ImmediateGF2NAF, 'int': IntArgFormat, 'p': PlayerNoAF, 'str': String, } def format_str_is_reg(format_str): return issubclass(ArgFormats[format_str], RegisterArgFormat) def format_str_is_writeable(format_str): return format_str_is_reg(format_str) and format_str[-1] == 'w' class Instruction(object): """ Base class for a RISC-type instruction. Has methods for checking arguments, getting byte encoding, emulating the instruction, etc. """ __slots__ = ['args', 'arg_format', 'code', 'caller'] count = 0 def __init__(self, *args, **kwargs): """ Create an instruction and append it to the program list. """ self.args = list(args) self.check_args() if not program.FIRST_PASS: if kwargs.get('add_to_prog', True): program.curr_block.instructions.append(self) if program.DEBUG: self.caller = [frame[1:] for frame in inspect.stack()[1:]] else: self.caller = None if program.EMULATE: self.execute() Instruction.count += 1 if Instruction.count % 100000 == 0: print "Compiled %d lines at" % self.__class__.count, time.asctime() def get_code(self): return self.code def get_encoding(self): enc = int_to_bytes(self.get_code()) # add the number of registers if instruction flagged as has var args if self.has_var_args(): enc += int_to_bytes(len(self.args)) for arg,format in zip(self.args, self.arg_format): enc += ArgFormats[format].encode(arg) return enc def get_bytes(self): return bytearray(self.get_encoding()) def execute(self): """ Emulate execution of this instruction """ raise NotImplementedError('execute method must be implemented') def check_args(self): """ Check the args match up with that specified in arg_format """ for n,(arg,f) in enumerate(itertools.izip_longest(self.args, self.arg_format)): if arg is None: if not isinstance(self.arg_format, (list, tuple)): break # end of optional arguments else: raise CompilerError('Incorrect number of arguments for instruction %s' % (self)) try: ArgFormats[f].check(arg) except ArgumentError as e: raise CompilerError('Invalid argument "%s" to instruction: %s' % (e.arg, self) + '\n' + e.msg) except KeyError as e: raise CompilerError('Unknown argument %s for instruction %s' % (f, self)) def get_used(self): """ Return the set of registers that are read in this instruction. """ return set(arg for arg,w in zip(self.args, self.arg_format) if \ format_str_is_reg(w) and not format_str_is_writeable(w)) def get_def(self): """ Return the set of registers that are written to in this instruction. """ return set(arg for arg,w in zip(self.args, self.arg_format) if \ format_str_is_writeable(w)) def get_pre_arg(self): return "" def has_var_args(self): try: len(self.arg_format) return False except: return True def is_vec(self): return False def is_gf2n(self): return False def get_size(self): return 1 def add_usage(self, req_node): pass # String version of instruction attempting to replicate encoded version def __str__(self): if self.has_var_args(): varargCount = str(len(self.args)) + ', ' else: varargCount = '' return self.__class__.__name__ + ' ' + self.get_pre_arg() + varargCount + ', '.join(str(a) for a in self.args) def __repr__(self): return self.__class__.__name__ + '(' + self.get_pre_arg() + ','.join(str(a) for a in self.args) + ')' ### ### Basic arithmetic ### class AddBase(Instruction): __slots__ = [] def execute(self): self.args[0].value = (self.args[1].value + self.args[2].value) % program.P class SubBase(Instruction): __slots__ = [] def execute(self): self.args[0].value = (self.args[1].value - self.args[2].value) % program.P class MulBase(Instruction): __slots__ = [] def execute(self): self.args[0].value = (self.args[1].value * self.args[2].value) % program.P ### ### Basic arithmetic with immediate values ### class ImmediateBase(Instruction): __slots__ = ['op'] def execute(self): exec('self.args[0].value = self.args[1].value.%s(self.args[2]) %% program.P' % self.op) class SharedImmediate(ImmediateBase): __slots__ = [] arg_format = ['sw', 's', 'i'] class ClearImmediate(ImmediateBase): __slots__ = [] arg_format = ['cw', 'c', 'i'] ### ### Memory access instructions ### class DirectMemoryInstruction(Instruction): __slots__ = [] def __init__(self, *args, **kwargs): super(DirectMemoryInstruction, self).__init__(*args, **kwargs) class ReadMemoryInstruction(Instruction): __slots__ = [] class WriteMemoryInstruction(Instruction): __slots__ = [] class DirectMemoryWriteInstruction(DirectMemoryInstruction, \ WriteMemoryInstruction): __slots__ = [] def __init__(self, *args, **kwargs): if program.curr_tape.prevent_direct_memory_write: raise CompilerError('Direct memory writing prevented') super(DirectMemoryWriteInstruction, self).__init__(*args, **kwargs) ### ### I/O instructions ### class DoNotEliminateInstruction(Instruction): """ What do you think? """ __slots__ = [] class IOInstruction(DoNotEliminateInstruction): """ Instruction that uses stdin/stdout during runtime. These are linked to prevent instruction reordering during optimization. """ __slots__ = [] @classmethod def str_to_int(cls, s): """ Convert a 4 character string to an integer. """ if len(s) > 4: raise CompilerError('String longer than 4 characters') n = 0 for c in reversed(s.ljust(4)): n <<= 8 n += ord(c) return n class AsymmetricCommunicationInstruction(DoNotEliminateInstruction): """ Instructions involving sending from or to only one party. """ __slots__ = [] class RawInputInstruction(AsymmetricCommunicationInstruction): """ Raw input instructions. """ __slots__ = [] class PublicFileIOInstruction(DoNotEliminateInstruction): """ Instruction to reads/writes public information from/to files. """ __slots__ = [] ### ### Data access instructions ### class DataInstruction(Instruction): __slots__ = [] field_type = 'modp' def add_usage(self, req_node): req_node.increment((self.field_type, self.data_type), self.get_size() * self.get_repeat()) def get_repeat(self): return 1 ### ### Integer operations ### class IntegerInstruction(Instruction): """ Base class for integer operations. """ __slots__ = [] arg_format = ['ciw', 'ci', 'ci'] class StackInstruction(Instruction): """ Base class for thread-local stack instructions. """ __slots__ = [] ### ### Clear comparison instructions ### class UnaryComparisonInstruction(Instruction): """ Base class for unary comparisons. """ __slots__ = [] arg_format = ['ciw', 'ci'] ### ### Clear shift instructions ### class ClearShiftInstruction(ClearImmediate): __slots__ = [] def check_args(self): super(ClearShiftInstruction, self).check_args() bits = float('nan') if self.is_gf2n(): if program.galois_length > 64: bits = 127 else: # assume 64-bit machine bits = 63 elif program.options.ring: bits = int(program.options.ring) - 1 if self.args[2] > bits: raise CompilerError('Shifting by more than %d bits ' 'not implemented' % bits) elif self.args[2] < 0: raise CompilerError('negative shift') ### ### Jumps etc ### class dummywrite(Instruction): """ Dummy instruction to create source node in the dependency graph, preventing read-before-write warnings. """ __slots__ = [] def __init__(self, *args, **kwargs): self.arg_format = [arg.reg_type + 'w' for arg in args] super(dummywrite, self).__init__(*args, **kwargs) def execute(self): pass def get_encoding(self): return [] class JumpInstruction(Instruction): __slots__ = ['jump_arg'] def set_relative_jump(self, value): if value == -1: raise CompilerException('Jump by -1 would cause infinite loop') self.args[self.jump_arg] = value def get_relative_jump(self): return self.args[self.jump_arg] class VarArgsInstruction(Instruction): def has_var_args(self): return True class CISC(Instruction): """ Base class for a CISC instruction. Children must implement expand(self) to process the instruction. """ __slots__ = [] code = None def __init__(self, *args): self.args = args self.check_args() #if EMULATE: # self.expand() if not program.FIRST_PASS: self.expand() def expand(self): """ Expand this into a sequence of RISC instructions. """ raise NotImplementedError('expand method must be implemented') class InvertInstruction(Instruction): __slots__ = [] def __init__(self, *args, **kwargs): if program.options.ring and not self.is_gf2n(): raise CompilerError('inverse undefined in rings') super(InvertInstruction, self).__init__(*args, **kwargs)