Files
MP-SPDZ/Compiler/instructions_base.py
2019-06-07 15:26:28 +10:00

821 lines
22 KiB
Python

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. """
assert(x < 2**32 and x >= -2**32)
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 (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 (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
def merge_id(self):
return type(self), self.get_size()
# 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)