Skip to content

Commit

Permalink
moves functions to simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
kolinko committed Oct 5, 2019
1 parent 35ba825 commit 704aea7
Show file tree
Hide file tree
Showing 5 changed files with 1,973 additions and 1,862 deletions.
1 change: 0 additions & 1 deletion pano/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

from utils.signatures import get_func_name, set_func, get_abi_name, get_func_params, set_func_params_if_none

import pano.folder
from core.masks import mask_to_type, type_to_mask

import json
Expand Down
333 changes: 9 additions & 324 deletions pano/rewriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
from utils.profiler import *


'''
Some nasty last-minute hacks and heurestics I wrote to finally get the April release to production.
One of the very few places in Panoramix that are blatantly mathematically incorrect, but
help to make a ton of contracts way more readable (and - in practice - being always valid)
'''

def postprocess_exp(exp):
if exp ~ ('data', *terms):
# make arrays in data
Expand Down Expand Up @@ -191,327 +200,3 @@ def rewrite_memcpy(lines): # 2
'''



@cached
def simplify_exp(exp):

if type(exp) == list:
return exp

if exp ~ ('mask_shl', 246, 5, 0, :exp):
exp = ('mask_shl', 251, 5, 0, exp) # mathematically incorrect, but this appears as an artifact of other
# ops often.

if exp ~ ('and', *terms):
real = 2**256-1
symbols = []
for t in terms:
if type(t) == int and t >=0:
real = real & t
elif t ~ ('and', *tms):
symbols += tms
else:
symbols.append(t)

if real != 2**256-1:
res = (real, )
else:
res = tuple()

res += tuple(symbols)
exp = ('and', ) + res

if exp ~ ('data', *terms) and \
all([t == 0 for t in terms]):
return 0

if exp ~ ('mask_shl', int:size, int:off, -off, ('cd', int:num)) and \
size in (8, 16, 32, 64, 128) and off > 0:
return ('mask_shl', size, 0, 0, ('cd', num)) # calldata params are left-padded usually, it seems

if exp ~ ('bool', ('bool', :e)):
exp = ('bool', e)

if exp ~ ('eq', :sth, 0) or \
exp ~ ('eq', 0, sth):
exp = ('iszero', sth)

if exp ~ ('mask_shl', int:size, 5, 0, ('add', int:num, *terms)) and \
size > 240 and num % 32 == 31 and num > 32:
exp = ('add', num//32, ('mask_shl', 256, 5, 0, ('add', 31, )+terms))

if exp ~ ('iszero', ('mask_shl', :size, :off, :shl, :val)):
exp = ('iszero', ('mask_shl', size, off, 0, val))

if exp ~ ('max', :single):
exp = single

if exp ~ ('mem', ('range', _, 0)):
return None # sic. this happens usually in params to logs etc, we probably want None here

if exp ~ ('mod', :exp2, int:num) and (size:=to_exp2(num)):
return mask_op(exp2, size=size)

# same thing is added in both expressions ?
if exp ~ (:op, ('add', *e1), ('add', *e2)) and op in ('lt', 'le', 'gt', 'ge'):
t1 = tuple(t for t in e1 if t not in e2)
t2 = tuple(t for t in e2 if t not in e1)
exp = (op, add_op(*t1), add_op(*t2))

if exp ~ ('add', :e):
# print('single add')
return simplify_exp(e)

if exp ~ ('mul', 1, :e):
return simplify_exp(e)

if exp ~ ('div', :e, 1):
return simplify_exp(e)

if exp ~ ('mask_shl', 256, 0, 0, :val):
return simplify_exp(val)

if exp ~ ('mask_shl', int:size, int:offset, int:shl, :e):
exp = mask_op(simplify_exp(e), size, offset, shl)

if exp ~ ('mask_shl', :size, 0, 0, ('div', :expr, ('exp', 256, :shr))):
exp = mask_op(simplify_exp(expr), size, 0, shr=bits(shr))

if exp ~ ('mask_shl', _, _, :shl, ('storage', :size, _, _)) and \
safe_le_op(size, minus_op(shl)):
return 0

if exp ~ ('or', :sth, 0):
return sth

if exp ~ ('add', *terms):
res = 0
for el in terms:
el = simplify_exp(el)
if el ~ ('add', ...:pms):
for e in pms:
res = add_op(res, e)
else:
res = add_op(res, el)
exp = res

if exp ~ ('mask_shl', ...):
exp = cleanup_mask_data(exp)

if exp ~ ('mask_shl', :size, 0, 0, ('mem', ('range', :mem_loc, :mem_size))):
if divisible_bytes(size) and safe_le_op(to_bytes(size)[0], mem_size):
return ('mem', apply_mask_to_range(('range', mem_loc, mem_size), size, 0))

if exp ~ ('mask_shl', :size, :off, :shl, ('mem', ('range', :mem_loc, :mem_size))) and shl == minus_op(off):
if divisible_bytes(size) and safe_le_op(to_bytes(size)[0], mem_size) and divisible_bytes(off):
return ('mem', apply_mask_to_range(('range', mem_loc, mem_size), size, off))


if exp ~ ('data', *params):
res = []

# simplify inner expressions, and remove nested 'data's
for e in params:
e = simplify_exp(e) # removes further nested datas, and does other simplifications
if opcode(e) == 'data':
res.extend(e[1:])
else:
res.append(e)

# sometimes we may have on expression split into two masks next to each other
# e.g. (mask_shl 192 64 -64 (cd 36)))) (mask_shl 64 0 0 (cd 36))
# (in solidstamp withdrawRequest)
# merge those into one.

res2 = res
res = None
while res2 != res: # every swipe merges up to two elements next to each other. repeat until there are no new merges
# there's a more efficient way of doing this for sure.
res, res2 = res2, []
idx = 0
while idx < len(res):
el = res[idx]
if idx == len(res) -1:
res2.append(el)
break

next_el = res[idx+1]
idx += 1

if el ~ ('mask_shl', :size, :offset, :shl, :val) \
and next_el == ('mask_shl', offset, 0, 0, val) \
and add_op(offset, shl) == 0:
res2.append(('mask_shl', add_op(size, offset), 0, 0, val))
idx += 1

else:
res2.append(el)

res = res2

# could do the same for mem slices, but no case of that happening yet

if len(res) == 1:
return res[0]
else:
return ('data', ) + tuple(res)

if exp ~ ('mul', -1, ('mask_shl', :size, :offset, :shl, ('mul', -1, :val))):
return ('mask_shl', simplify_exp(size), simplify_exp(offset), simplify_exp(shl), simplify_exp(val))

if type(exp) == int and to_real_int(exp)>-(8**22): # if it's larger than 30 bytes, it's probably
# an address, not a negative number
return to_real_int(exp)

if exp ~ ('and', :num, :num2):
num = arithmetic.eval(num)
num2 = arithmetic.eval(num2)
if type(num) == int or type(num2) == int:
return simplify_mask(exp)

if type(exp) == list:
r = simplify_exp(tuple(exp))
return list(r)

if type(exp) != tuple:
return exp

if exp ~ ('mask_shl', int:size, int:offset, int:shl, int:val):
return apply_mask(val, size, offset, shl)

if exp ~ ('mask_shl', :size, 5, :shl, ('add', 31, ('mask_shl', 251, 0, 5, :val))):
return simplify_exp(('mask_shl', size, 5, shl, val))

if exp ~ ('mul', *terms):
res = 1
for e in terms:
res = mul_op(res, simplify_exp(e))

res ~ ('mul', 1, res)

return res

if exp ~ ('max', *terms):
els = [simplify_exp(e) for e in terms]
res = 0
for e in els:
res = _max_op(res, e)
return res

res = tuple()
for e in exp:
res += (simplify_exp(e), )

return res


def simplify_mask(exp):
op = opcode(exp)

if op in arithmetic.opcodes:
exp = arithmetic.eval(exp)

if exp ~ ('and', :left, :right):

if mask := to_mask(left):
exp = mask_op(right, *mask)

elif mask := to_mask(right):
exp = mask_op(left, *mask)

elif bounds := to_neg_mask(left):
exp = neg_mask_op(right, *bounds)

elif bounds := to_neg_mask(right):
exp = neg_mask_op(left, *bounds)

elif exp ~ ('div' , :left, int:right) and (shift := to_exp2(right)):
exp = mask_op(left, size = 256-shift, offset = shift, shr = shift)

elif exp ~ ('mul', int:left, :right) and (shift := to_exp2(left)):
exp = mask_op(right, size=256-shift, shl = shift)

elif exp ~ ('mul', :left, int:right) and (shift := to_exp2(right)):
exp = mask_op(left, size=256-shift, shl = shift)

return exp


def cleanup_mask_data(exp):
# if there is a mask over some data,
# it removes pieces of data that for sure won't
# fit into the mask (doesn't truncate if something)
# partially fits
# e.g. mask(200, 8, 8 data(123, mask(201, 0, 0, sth), mask(8,0, 0, sth_else)))
# -> mask(200, 0, 0, mask(201, 0, 0, sth))

def _cleanup_right(exp):
# removes elements that are cut off by offset
assert exp ~ ('mask_shl', :size, :offset, :shl, :val)

if opcode(val) != 'data':
return exp

last = val[-1]
if sizeof(last) is not None and safe_le_op(sizeof(last), offset):
offset = sub_op(offset, sizeof(last))
shl = add_op(shl, sizeof(last))
if len(val) == 3:
val = val[1]
else:
val = val[:-1]

return mask_op(val, size, offset, shl)

return exp

def _cleanup_left(exp):
# removes elements that are cut off by size+offset
assert exp ~ ('mask_shl', :size, :offset, :shl, :val)

if opcode(val) != 'data':
return exp

total_size = add_op(size, offset) # simplify_exp

sum_sizes = 0
val = list(val[1:])
res = []
while len(val) > 0:
last = val.pop()
if sizeof(last) is None:
return exp
sum_sizes = simplify_exp(add_op(sum_sizes, sizeof(last)))
res.insert(0, last)
if safe_le_op(total_size, sum_sizes):
return exp[:4] + (('data', )+tuple(res), )


return exp

assert opcode(exp) == 'mask_shl'

prev_exp = None
while prev_exp != exp:
prev_exp = exp
exp = _cleanup_right(exp)

prev_exp = None
while prev_exp != exp:
prev_exp = exp
exp = _cleanup_left(exp)

if exp ~ ('mask_shl', :size, 0, 0, ('data', *terms)):
# if size of data is size of mask, we can remove the mask altogether
sum_sizes = 0
for e in terms:
s = sizeof(e)
if s is None:
return exp
sum_sizes = add_op(sum_sizes, s)

if sub_op(sum_sizes, size) == 0:
return ('data', ) + terms

return exp
Loading

0 comments on commit 704aea7

Please sign in to comment.