def modulo(c, m): """Modulo statement. For instance: al %= 6 """ dst = Operand(c, m.group(1)) src = Operand(c, m.group(2)) if src.value == 0: raise ValueError('Cannot perform modulo 0') if src.value < 0: raise ValueError('Negative modulo not implemented') if src.value is not None and is_power_of_2(src.value): # AND optimization: modulo is a power of 2 # We only need to mask with 111… until its power. # For instance: # 1000 (8) - 1 = 111 (7) # # So n % 8, e.g., 100101, is as simple as masking with 111 -> 101 mask = src.value - 1 c.add_code(f'and {dst}, {mask}') return perform_divide(c, dst, src, result_in8='ah', result_in16='dx')
def if_(c, m): """If control flow statement. For instance: if ax < 5 { ; code } if cx is even { ; code } """ a = Operand(c, m.group(1)) comparision = m.group(2) b = Operand(c, m.group(3)) even_odd = m.group(4) label = c.get_uid(m.group(5)) if even_odd is None: c.add_code([f'cmp {a}, {b}', f'{ctoi[comparision]} {label}']) else: # If even, then bit is 0, then jump to skip if not zero # If odd, then bit is 1, then jump to skip if zero jump = 'jnz' if even_odd == 'even' else 'jz' c.add_code([f'test {a}, 1', f'{jump} {label}']) c.add_pending_code(f'{label}:')
def while_(c, m): """While control flow statement. For instance: while bx < 4 { ; code } To force at least one iteration while dx > 7 or once { ; code } """ a = Operand(c, m.group(1)) comparision = m.group(2) b = Operand(c, m.group(3)) label = c.get_uid(m.group(5)) labelstart = label + '_s' labelend = label + '_e' if m.group(4) is None: # 'or once' is not present, we might not need to enter the loop c.add_code([f'cmp {a}, {b}', f'{ctoi[comparision]} {labelend}']) c.add_code(f'{labelstart}:') # Reenter the loop if condition is met c.add_pending_code( [f'cmp {a}, {b}', f'{cti[comparision]} {labelstart}', f'{labelend}:'])
def setcursor(c, m): """Sets the cursor on screen. For instance: setcursor(8, 20) """ # TODO Possibly add support for inlining setc = define_set_cursor(c) row, col = Operand.parseint(m.group(1)), Operand.parseint(m.group(2)) # Assertion if row is not None and not 0 <= row < 256: raise ValueError('The row for setcursor must be between 0 and 255') if col is not None and not 0 <= col < 256: raise ValueError('The column for setcursor must be between 0 and 255') # Assertion passed, save the previous value c.add_code(f'push dx') # Special case: both integer values known if row is not None and col is not None: c.add_code(f'mov dx, {(row << 8) | col}') else: # Need to move manually helperassign(c, 'dh', m.group(1)) helperassign(c, 'dl', m.group(2)) c.add_code([ f'call {setc.name}', f'pop dx' ])
def check_for_arrows(image, equation, ncp, ind): x0, y0, h0, w0, x, y, w, h, x1, y1, w1, h1 = get_coords_of_adjacent( ncp, ind) if math.fabs(x - x0) < 0.4 * w and math.fabs((x0 + w0) - (x + w)) < 0.4 * w and y0 < y: obj = Operand(x=x, y=y, top=Operand(x=x0, y=y0, image=image[y0:y0 + h0, x0:x0 + w0]), image=image[y:y + h, x:x + w]) equation.add_operand(obj) return True elif math.fabs(x - x1) < 0.4 * w and math.fabs( (x1 + w1) - (x + w)) < 0.4 * w and y1 < y: obj = Operand(x=x, y=y, top=Operand(x=x1, y=y1, image=image[y1:y1 + h0, x1:x1 + w1]), image=image[y:y + h, x:x + w]) equation.add_operand(obj) return True else: equation.add_operand(Operand(x=x, y=y, image=image[y:y + h, x:x + w])) return False
def divide(c, m): """Division statement. For instance: al /= 4 """ dst = Operand(c, m.group(1)) src = Operand(c, m.group(2)) if src.value == 0: raise ValueError('Cannot divide by 0') if src.value and abs(src.value) == 1: # One optimization: do nothing or negate if src.value < 0: c.add_code(f'neg {dst}') return if src.value is not None: # Bit shift optimization: divisor is a small multiple of 2 # http://zsmith.co/intel_d.html#div or # http://zsmith.co/intel_n.html#neg and http://zsmith.co/intel_s.html#sar if dst.is_reg or dst.size == 8: limit = 80 else: limit = 150 if dst.is_reg: if src.value < 0: cost = 3 + 8 + 4 * (-src.value) else: cost = 8 + 4 * src.value if dst.is_mem: if src.value < 0: cost = 16 + 20 + 4 * (-src.value) else: cost = 20 + 4 * src.value # Now we know our limit, beyond which division is easier # and the cost per shift, so we know which values we can use if cost < limit: # The cost of negating and shifting is less, so we'll do that amount = limit // cost powers = [2**i for i in range(1, amount + 1)] if abs(src.value) in powers: # We have a power of two, so perform we can use bit shift if src.value < 0: c.add_code(f'neg {dst}') src.value = -src.value count = int(src.value ** 0.5) c.add_code(f'sar {dst}, {count}') return perform_divide(c, dst, src, result_in8='al', result_in16='ax')
def __init__(self, inputPipe=None, config={}, simulation=False, dataManager=None): self.traders = [] self.config = config self.input = inputPipe self.simulation = simulation self.zero = Operand(d=0) self.one = Operand(d=1) self.dataManager = dataManager self.initialize()
def get_operands(self, csv): """Converts the possibly comma separated values to a list of operands (inmediate, registers and variables """ if isinstance(csv, list): return [ v if isinstance(v, Operand) else Operand(self, v) for v in csv ] if isinstance(csv, Operand): return [csv] return [Operand(self, v) for v in Operand.get_csv(csv)]
def get_operation(self, arg): if (type(arg) is str): #it is an operation operation = create_operation(data=json.loads(arg), dataline=self.dataManager) return operation elif (type(arg) is float or type(arg) is int): #return the integer or float as it is return Operand(d=arg) elif (type(arg) is list): return Operand(d=0) elif (arg is None): #return 0 return Operand(d=0)
def run(self, data): d = { 'price': data['lastPrice'], 'time': data['time'], 'token': data['token'], 'lastPrice': data['lastPrice'] # TODO change to common as price } if (data.get('isCandle')): d.update({ 'open': data['open'], 'high': data['high'], 'low': data['low'], 'close': data['close'], 'interval': data['interval'], 'isCandle': data['isCandle'] }) #TODO change this as soon as you can !important true = Operand(d=True) begin = (self.begin_on.update(d) == true).d end = (self.end_on.update(d) == true).d r = self.update(d) if (begin and not end): if (self.phase == 'sleeping'): self.begin(d) self.phase = 'running' if (data.get('noTrade')): return None return r if (begin and end and self.phase == 'running'): self.end(d) self.phase = 'sleeping' return None
def dualoperands(c, m): """Operations with two operands statement. For instance: ax |= bx ; Bitwise OR dx ^= cx ; Bitwise XOR ax &= bx ; Bitwise AND dx += cx ; Integer addition ax -= bx ; Integer subtraction """ op = m.group(2) dst = Operand(c, m.group(1)) src = Operand(c, m.group(3)) if src.value == 1 and op in translation1: c.add_code(f'{translation1[op]} {dst}') else: helperoperate(c, translation[op], dst, src)
def __init__(self, name, vartype, value, vector_size=None): self.name = name # Variable offset in memory, set by the compiler state self.offset = None self.length = 1 # 'vector_size' should be None for non-vector types self.is_vector = vector_size is not None if self.is_vector: if vector_size < 1: raise ValueError('Vector size must be ≥ 1') self.value = Operand.get_csv(value) self.length = vector_size # One value is OK with any vector size, 'dup()' is assumed if len(self.value) != 1 and self.length != len(self.value): raise ValueError( f'Specified vector length ({self.length}) and supplied ' f'values count ({len(self.value)}) do not match') else: # Not a vector, so save its single value self.value = value.strip() # Name and vector handled, now define the type self.type = vartype # Type defined, now determine its code self.is_constant = vartype == 'const' if self.is_constant: if self.is_vector: raise ValueError('Cannot define a constant vector') self.typecode = None else: if vartype == 'byte': self.typecode = 'DB' self.size = 8 elif vartype == 'short': self.typecode = 'DW' self.size = 16 elif vartype == 'string': if self.is_vector: raise ValueError('Cannot define a vector of strings') self.typecode = 'DB' self.size = 8 self.value, self.length = self.escape_string(value) else: raise ValueError(f'Unknown variable type "{vartype}"')
def repeat(c, m): """Repeat control flow statement. For instance: repeat 10 with cx { ; code } """ count = Operand(c, m.group(1)) used = Operand(c, m.group(2)) label = c.get_uid(m.group(3)) labelstart = label + '_s' labelend = label + '_e' # Initialize our loop counter helperassign(c, used, count) # Sanity check, if 0 don't enter the loop unless we know it's not 0 # This won't optimize away the case where the value is 0 if count.value is None or count.value <= 0: # count unknown or zero c.add_code([ f'test {used}, {used}', f'jz {labelend}' ]) # Add the start label so we can jump here c.add_code(f'{labelstart}:') if used.code == 'cx': # We can use 'loop' if using 'cx' c.add_pending_code([ f'loop {labelstart}', f'{labelend}:' ]) else: c.add_pending_code([ f'dec {used}', f'jnz {labelstart}', f'{labelend}:' ])
def __init__(self, name, params, returns=None, mangle=True): # Convert a comma separated list to normal parameters if required self.params = Operand.get_csv(params) # Name mangling, add as many underscores as parameter count if mangle: self.name = self.mangle(name, len(self.params)) else: self.name = name # Where the value is returned self.returns = returns # Used to store the code of the function self.code = []
def putchar(c, m): """Puts a character on the screen. For instance: put char 'L' put digit cx """ # TODO Possibly add support for not inlining src = Operand(c, m.group(2)) c.add_code('push ax') helperassign(c, 'al' if src.size == 8 else 'ax', src) if m.group(1) == 'digit': c.add_code("add al, '0'") c.add_code([ 'mov ah, 14', 'int 10h', 'pop ax' ])
def variable(c, m): """Variable or constant definition. For instance: byte little = 42 short big = 1234 byte[5] vector1 byte[] vector2 = 1, 2, 3, 4, 5 const VALUE = 7 """ # TODO Perform more checks to ensure the value is correct vartype = m.group(1) vector_size = m.group(2) name = m.group(3) value = m.group(4) if not value: value = '?' if vector_size is None: # Single item variable c.add_variable(Variable(name=name, vartype=vartype, value=value)) else: # We have a vector, get the comma-separated values values = Operand.get_csv(value) # Determine its size (remove '[]' by slicing) if given vector_size = vector_size[1:-1].strip() if vector_size: vector_size = int(vector_size) else: if value == '?': raise ValueError('A list of values must be supplied when ' 'no vector size is specified') vector_size = len(values) c.add_variable( Variable(name=name, vartype=vartype, value=values, vector_size=vector_size))
def multiply(c, m): """Multiplication statement. For instance: al *= 7 """ dst = Operand(c, m.group(1)) src = Operand(c, m.group(2)) if src.value == 0: # Zero optimization: second product is 0 if dst.is_mem: c.add_code(f'and {dst}, 0') else: c.add_code(f'xor {dst}, {dst}') return if src.value and abs(src.value) == 1: # One optimization: do nothing or negate if src.value < 0: c.add_code(f'neg {dst}') return if src.value is not None: # Bit shift optimization: second product is a small multiple of 2 # http://zsmith.co/intel_m.html#mul or # http://zsmith.co/intel_n.html#neg and http://zsmith.co/intel_s.html#sal if dst.is_reg or dst.size == 8: limit = 70 else: limit = 124 if dst.is_reg: if src.value < 0: cost = 3 + 8 + 4 * (-src.value) else: cost = 8 + 4 * src.value if dst.is_mem: if src.value < 0: cost = 16 + 20 + 4 * (-src.value) else: cost = 20 + 4 * src.value # Now we know our limit, beyond which multiplication is easier # and the cost per shift, so we know which values we can use if cost < limit: # The cost of negating and shifting is less, so we'll do that amount = limit // cost powers = [2**i for i in range(1, amount + 1)] if abs(src.value) in powers: # We have a power of two, so perform we can use bit shift if src.value < 0: c.add_code(f'neg {dst}') src.value = -src.value count = int(src.value**0.5) c.add_code(f'sal {dst}, {count}') return # We're likely going to need to save some temporary variables # No 'push' or 'pop' are used because 'ah *= 3', for instance, destroys 'al' # 'al' itself can be pushed to the stack, but a temporary variable can hold tmps = TmpVariables(c) # We will define 'dst, src, fa1, fa2' as: # dst *= src # fa1 *= fa2 large_multiply = max(dst.size, src.size) > 8 if large_multiply: # 16-bits mode, so we use 'ax' as the first factor fa1 = 'ax' # The second factor cannot be an inmediate value neither 'ax' if src.code[0] == 'a' and src.code[-1] in 'xhl' \ or src.value is not None: fa2 = 'dx' else: fa2 = src.code # Determine which registers we need to save saves = [] # al *= 260 # ^ dst # # ax *= 260 # ^ used # # dst ≠ used, used gets saved # Special case where we want to use al/ah as destination # We're using ax for multiplication, so we can't save the whole if dst[0] == 'a' and dst[-1] in 'hl': saves.append('al' if dst.code == 'ah' else 'ah') elif dst.code != fa1: # Save fa1 unless it's the destination saves.append(fa1) # Save fa2 unless it's the destination if dst.code != fa2: saves.append(fa2) # Save the used registers tmps.save_all(saves) # Load the factors into their correct location helperassign(c, [fa1, fa2], [dst, src]) # Perform the multiplication c.add_code(f'mul {fa2}') # Move the result, 'ax', to wherever it should be helperassign(c, dst, 'ax') # Restore the used registers tmps.restore_all() else: # 8-bits mode, so we use 'al' as the first factor fa1 = 'al' # The second factor cannot be an inmediate value neither 'al' fa2 = src.code if src.value is None and src.code != 'al' else 'ah' # Determine which registers we need to save saves = [] # Save fa1 unless it's the destination if dst.code != fa1: saves.append(fa1) # Save fa2 if we moved it (thus it's not the same) # unless it's be overrode if it is the destination if src.code != fa2 and dst.code != fa2: saves.append(fa2) # Save the used registers tmps.save_all(saves) # Load the factors into their correct location helperassign(c, [fa1, fa2], [dst, src]) # Perform the multiplication c.add_code(f'mul {fa2}') # Move the result, 'al', to wherever it should be helperassign(c, dst, 'al') # Restore the used registers tmps.restore_all()
def printf(c, m): """Prints a string with optional formatted values. For instance: printf "I'm %d years old, you %d" % ax, bx printf stringVariable """ # TODO Possibly add support for not inlining # Inlining really shines when there are actually formatted values: # # If inlining: # 5 instructions; push/pop ax/dx, mov ah 9 # + 2n (where n is the number of strings, lea + int) # # If not inlining (the first 7 are only written once, ran many times): # 7n instructions; function definition, push/pop ax, mov ah 9, int # + 2 instructions; push/pop dx # + 2n (where n is the number of strings, lea + int) # # Retrieve the captured values string = m.group(1) args = c.get_operands(m.group(2)) var = m.group(3) # Save the registers used for calling the print interruption c.add_code(['push ax', 'push dx']) # string/args and var are mutually exclusive if var: op = Operand(c, var, assert_vector_access=False) if op.is_mem: # Using a variable, load and call the interruption if op.type == 'string': c.add_code(f'lea dx, {op}') else: load_integer(c, op.code) c.add_code([f'mov ah, 9', f'int 21h']) elif op.is_reg: # Using a register, which means we need to load an integer load_integer(c, op) c.add_code([f'mov ah, 9', f'int 21h']) else: # Assume inmediate integer value c.add_code(f'mov ah, 9') define_and_print(c, f'"{op.name}"') else: # Captured a string, with possibly formatted arguments # # Since 'ax' and 'dx' are needed for printing, we need to save them. # We could use the stack, however, we would need to push as many times # as we encountered one of the offending registers. Also, the stack # doesn't support the 8-bit versions of these registers, so we simply # use a temporary variable. tmps = TmpVariables(c) for a in args: if is_ax_or_dx_variant(a): tmps.save(a) # After we saved the values we may later need, set up the print function c.add_code('mov ah, 9') # Now, we have the right string and the arguments used if args: # 1. Find %X's substrings = re.split(r'%[sd]', string) arg_types = re.findall(r'%[sd]', string) # 2. Print: substring, formatted, substring, formatted, etc. for i in range(len(substrings)): if i > 0: # Format the arguments, skipping 1 normal string (thus -1) op = args[i - 1] format_type = arg_types[i - 1] # Load the argument into 'dx' depending on its type # # This assumes that the user entered the right type # TODO Don't assume that the user entered the right type if format_type == '%s': c.add_code(f'lea dx, {op}') elif format_type == '%d': if op in tmps: # Special, this was saved previously load_integer(c, tmps[op]) c.add_code('mov ah, 9') else: load_integer(c, op) else: raise ValueError( f'Regex should not have allowed {format_type}') # Call the interruption to print the argument c.add_code(f'int 21h') # Argument formatted, print the next part of the string define_and_print(c, substrings[i]) else: # No arguments at all, a define and load will do define_and_print(c, string) # All done, restore the original dx/ax values c.add_code(['pop dx', 'pop ax'])
def divmod_(c, m): """Division & Modulo statement. For instance: ax, dx = divmod bx, 7 After this operation takes place, the values will be: ax = bx / 7 ; (integer division) dx = bx % 7 ; (bx modulo 7) """ a = Operand.parseint(m.group(3)) b = Operand.parseint(m.group(4)) if a is not None and b is not None: # Both are integers, we can optimize this away # and just plug the right values in a, b = divmod(a, b) helperassign(c, m.group(1), a) helperassign(c, m.group(2), b) else: # Need to perform the operation quotient = m.group(1) remainder = m.group(2) dividend = m.group(3) divisor = m.group(4) # TODO Support for AH/AL (8 bits division mode) # Only AX and DX will be affected, so unless we're # assigning a result to them, they need to be pushed push_ax = quotient != 'ax' and remainder != 'ax' push_dx = quotient != 'dx' and remainder != 'dx' if push_ax: c.add_code('push ax') if push_dx: c.add_code('push dx') push_divisor = False # Determine the divisor to be used, it cannot be 'dx' since # it has to be zero because it's taken into account when dividing, # and it cannot be 'ax' because it's where the dividend is # # The divisor cannot be an inmediate value either ('b' not None) if divisor in ['ax', 'dx'] or b is not None: # Pick either the quotient or remainder (will be overwritten later) if quotient not in ['ax', 'dx']: used_divisor = quotient elif remainder not in ['ax', 'dx']: used_divisor = remainder else: # No luck, use 'bx' for instance (we need to save it) used_divisor = 'bx' push_divisor = True c.add_code('push bx') # Update whatever register we used helperassign(c, used_divisor, divisor) else: # We have the right value for the divisor, no move required used_divisor = divisor # Divisor set up, neither in 'ax' nor 'dx' so it's OK # Now set up the dividend, which must always be in 'ax' helperassign(c, 'ax', dividend) # Everything set, perform the division c.add_code([ f'xor dx, dx', # Upper bits are considered! f'div {used_divisor}' ]) if push_divisor: # Division done, restore our temporary register c.add_code('pop bx') # Now move the result if required # Special case, 'ax' and 'dx are swapped if quotient == 'dx' and remainder == 'ax': c.add_code(['push ax', 'mov ax, dx', 'pop dx']) else: # Although helperassign would take care of these, we can # slightly optimize away the otherwise involved push/pop # # TODO Actually, perhaps helper assign could note this and # swap the order of assignment if quotient == 'dx': # Special case, we need to move remainder, in 'dx', first helperassign(c, [remainder, quotient], ['dx', 'ax']) else: # Normal case, first 'ax', then 'dx' helperassign(c, [quotient, remainder], ['ax', 'dx']) if push_dx: c.add_code('pop dx') if push_ax: c.add_code('pop ax')
def helperoperate(c, op, dst, src): """Helper operation with support to operate on any value (memory or not) of the form 'instruction dst, src' """ if not isinstance(dst, Operand): dst = Operand(c, dst) if not isinstance(src, Operand): src = Operand(c, src) # Sanity check: destination cannot be an inmediate value if dst.value is not None: raise ValueError( f'Cannot {op} "{src.name}" to the inmediate value "{dst.name}"') # If the source has an integer value, make sure it fits on destination if src.value is not None: if src.size > dst.size: raise ValueError(f'"{src.name}" is too big to fit in "{dst.name}"') # It's an okay inmediate value, simply operate and early exit c.add_code(f'{op} {dst}, {src}') return # Special case where both are memory, we need to use a temporary # register ('ax' for instance); recursive calls to helperoperate() # will then take care of the cases where operating with memory + 'ax' if dst.is_mem and src.is_mem: # # # [Case memory to memory] # TODO Perhaps we could use a temporary variable instead of the # stack to pick either AL or AX (which is better suited) c.add_code('push ax') helperassign(c, 'ax', src) helperoperate(c, op, dst, 'ax') c.add_code('pop ax') return # Now that we know the size, and that not both are memory, # we can get our hands dirty if dst.size == src.size: # # # [Case same size] # Both sizes are the same, we can directly operate (unless 'mov') if op != 'mov' or dst != src: c.add_code(f'{op} {dst}, {src}') elif dst.size < src.size: # The destination is smaller, we have to ignore the high part if src.is_mem or src.low is None: # # # [Case large memory/register to small register] # The source is memory, then the destination is a register # # We need an auxiliary register because we don't want to # touch the other part (either of r'[HL]'), and we can't # pop a masked value not to lose the rest # # This is also the case when the source is not memory, but # the register doesn't support accessing r'[HL]' # # Use either 'dx' or 'ax' as auxiliar register (arbitrary # as long as it doesn't match the one we want to move to) aux = 'dx' if dst[0] == 'a' else 'ax' c.add_code(f'push {aux}') helperassign(c, aux, src) helperoperate(c, op, dst, f'{aux[0]}l') c.add_code(f'pop {aux}') else: # # # [Case large register to small register/memory] # We know that we have acces to the low part since it was # checked above, otherwise we would have needed that # auxiliary to be able to pick only the low part helperoperate(c, op, dst, src.low) else: # The destination is larger, we need to mask the high part. # The only case where this is possible is on r'[ABCD][HL]', # so we already know that we have access to the 'X' version # unless the source is memory if src.is_mem: # # # [Case small memory to large register] # Check if the register supports accessing to the r'[HL]' # Otherwise we need to use a temporary register such as 'ax' if dst.low is None: c.add_code(f'push ax') c.add_code(f'xor ah, ah') helperassign(c, 'al', src) helperoperate(c, op, dst, 'ax') c.add_code(f'pop ax') else: c.add_code(f'xor {dst.high}, {dst.high}') helperoperate(c, op, dst.low, src) elif dst.is_mem: # # # [Case small register to large memory] # All the 8 bit registers support accessing to 'X', so we # can just mask away the high part, move it and restore c.add_code(f'push {src.full}') # We might need to move the high to the low part before masking if src[-1] == 'h': helperassign(c, src.low, src.high) c.add_code(f'xor {src.high}, {src.high}') helperoperate(c, op, dst, src.full) c.add_code(f'pop {src.full}') else: # # # [Case small register to large register] if src[-1] == 'l': # We're using the low part, we can directly copy # and then mask the high part with AND or XOR helperoperate(c, op, dst, src.full) if dst.high is None: c.add_code(f'and {dst}, 0xff') else: c.add_code(f'xor {dst.high}, {dst.high}') else: # We want to assign the src = YH, but it's 8-bits if dst.low is None: # No support to move the 8-bits directly, we need # to save the value, move it, mask it, and move it c.add_code(f'push {src.full}') helperassign(c, src.low, src) c.add_code(f'xor {src}, {src}') helperoperate(c, op, dst, src.full) c.add_code(f'pop {src.full}') else: # Destination supports 8-bits access so we can # directly move those and mask away the high part helperoperate(c, op, dst.low, src) c.add_code(f'xor {dst.high}, {dst.high}')