def visit_float_is_normal(self, e): arg = e.arg(0) self._check_fp_sort(arg) arg_sort = e.arg(0).sort() smallest_positive_normal = self._get_smallest_positive_normal_for( arg_sort) largest_negative_normal = self._get_largest_negative_normal_for( arg_sort) temp = z3.Or( z3.And(z3.fpGEQ(arg, smallest_positive_normal), z3.fpLT(arg, z3.fpPlusInfinity(arg_sort))), z3.And(z3.fpLEQ(arg, largest_negative_normal), z3.fpGT(arg, z3.fpMinusInfinity(arg_sort)))) self.visit(temp)
def _op_raw_fpGEQ(self, a, b): return z3.fpGEQ(a, b, ctx=self._context)
class BaseSMTTranslator(): __metaclass__ = MetaTranslator def __init__(self, type_model): self.types = type_model self.fresh = 0 self.defs = [] # current defined-ness conditions self.nops = [] # current non-poison conditions self.qvars = [] self.attrs = collections.defaultdict(dict) # term -> attr -> var def eval(self, term): '''smt.eval(term) -> Z3 expression Translate the term (and subterms), adding its definedness conditons, nonpoison conditions, and quantifier variables to the state. ''' logger.debug('eval %s', term) return eval(term, self) def __call__(self, term): '''smt(term) -> Z3 expression, def conds, nonpoison conds, qvars Clear the current state, translate the term (and subterms), and return the translation, definedness conditions, nonpoison conditions, and quantified variables. Quantified variables are guaranteed to be unique between different calls to the same SMTTranslator object. ''' logger.debug('call %s', term) self.defs = [] self.nops = [] self.qvars = [] v = eval(term, self) return v, self.defs, self.nops, self.qvars def add_defs(self, *defs): self.defs += defs def add_nops(self, *nops): self.nops += nops def add_qvar(self, *qvars): self.qvars += qvars def type(self, term): return self.types[term] def fresh_bool(self): self.fresh += 1 return z3.Bool('ana_' + str(self.fresh)) def fresh_var(self, ty, prefix='undef_'): self.fresh += 1 return z3.Const(prefix + str(self.fresh), _ty_sort(ty)) def _conditional_value(self, conds, v, name=''): raise NotImplementedError def _conditional_conv_value(self, conds, v, name=''): raise NotImplementedError def _binary_operator(self, term, op, defined, poisons): x = self.eval(term.x) y = self.eval(term.y) if defined: self.add_defs(*defined(x, y)) if poisons: for f in poisons: if f in self.attrs[term]: self.add_nops( z3.Implies(self.attrs[term][f], poisons[f](x, y))) elif f in term.flags: self.add_nops(poisons[f](x, y)) return op(x, y) def _float_binary_operator(self, term, op): logger.debug('_fbo: %s\n%s', term, self.attrs[term]) x = self.eval(term.x) y = self.eval(term.y) z = op(x, y) conds = [] if 'nnan' in self.attrs[term]: df = z3.And(z3.Not(z3.fpIsNaN(x)), z3.Not(z3.fpIsNaN(y)), z3.Not(z3.fpIsNaN(z))) conds.append(z3.Implies(self.attrs[term]['nnan'], df)) elif 'nnan' in term.flags: conds += [ z3.Not(z3.fpIsNaN(x)), z3.Not(z3.fpIsNaN(y)), z3.Not(z3.fpIsNaN(z)) ] if 'ninf' in self.attrs[term]: df = z3.And(z3.Not(z3.fpIsInf(x)), z3.Not(z3.fpIsInf(y)), z3.Not(z3.fpIsInf(z))) conds.append(z3.Implies(self.attrs[term]['ninf'], df)) elif 'ninf' in term.flags: conds += [ z3.Not(z3.fpIsInf(x)), z3.Not(z3.fpIsInf(y)), z3.Not(z3.fpIsInf(z)) ] if 'nsz' in self.attrs[term] or 'nsz' in term.flags: # NOTE: this will return a different qvar for each (in)direct reference # to this term. Is this desirable? b = self.fresh_bool() self.add_qvar(b) z = op(x, y) c = z3.fpIsZero(z) if 'nsz' in self.attrs[term]: c = z3.And(self.attrs[term]['nsz'], c) s = _ty_sort(self.type(term)) z = z3.If(c, z3.If(b, 0, z3.fpMinusZero(s)), z) if isinstance(term, FDivInst): c = [z3.Not(z3.fpIsZero(x)), z3.fpIsZero(y)] if 'nsz' in self.attrs[term]: c.append(self.attrs[term]['nsz']) z = z3.If( z3.And(c), z3.If(b, z3.fpPlusInfinity(s), z3.fpMinusInfinity(s)), z) return self._conditional_value(conds, z, term.name) _icmp_ops = { 'eq': operator.eq, 'ne': operator.ne, 'ugt': z3.UGT, 'uge': z3.UGE, 'ult': z3.ULT, 'ule': z3.ULE, 'sgt': operator.gt, 'sge': operator.ge, 'slt': operator.lt, 'sle': operator.le, } _fcmp_ops = { 'false': lambda x, y: z3.BoolVal(False), 'oeq': z3.fpEQ, 'ogt': z3.fpGT, 'oge': z3.fpGEQ, 'olt': z3.fpLT, 'ole': z3.fpLEQ, 'one': lambda x, y: z3.Not(fpUEQ(x, y)), 'ord': lambda x, y: z3.Not(z3.Or(z3.fpIsNaN(x), z3.fpIsNaN(y))), 'ueq': fpUEQ, 'ugt': lambda x, y: z3.Not(z3.fpLEQ(x, y)), 'uge': lambda x, y: z3.Not(z3.fpLT(x, y)), 'ult': lambda x, y: z3.Not(z3.fpGEQ(x, y)), 'ule': lambda x, y: z3.Not(z3.fpGT(x, y)), 'une': z3.fpNEQ, 'uno': lambda x, y: z3.Or(z3.fpIsNaN(x), z3.fpIsNaN(y)), 'true': lambda x, y: z3.BoolVal(True), } def _must_analysis(self, term, op): args = (self.eval(a) for a in term._args) if all(isinstance(a, Constant) for a in term._args): return op(*args) c = self.fresh_bool() self.add_defs(z3.Implies(c, op(*args))) return c def _has_attr(self, attr, term): if attr in term.flags: return z3.BoolVal(True) return self._get_attr_var(attr, term) def _get_attr_var(self, attr, term): if attr in self.attrs[term]: return self.attrs[term][attr] # TODO: pick name better b = self.fresh_bool() logger.debug('Creating attr var %s for %s:%s', b, term, attr) self.attrs[term][attr] = b return b
class BaseSMTEncoder(): __metaclass__ = MetaEncoder def __init__(self, type_model): self.types = type_model self.fresh = 0 self.reset() self._analysis = collections.defaultdict(dict) # name -> key -> var def reset(self): self.defs = [] # current defined-ness conditions self.nops = [] # current non-poison conditions self._safe = [] # current safety conditions self._aux = [] # current auxiliary conditions self.qvars = [] def eval(self, term): """Return the SMT translation of the term. Any side conditions are added to the translator context. """ logger.debug('eval %s', term) return eval(term, self) def __call__(self, term): """Interpret the term in a fresh translator context. Quantified variables are guaranteed to be unique between different calls to the same SMTTranslator object. """ # WARNING: calling eval after __call__ will modify the Interp # returned by __call__. Maybe better to clear everything after calling # eval? Perhaps best to clear both, but that wastes effort. logger.debug('call %s', term) self.reset() v = eval(term, self) return Interp( value = v, defined = self.defs, nonpoison = self.nops, safe = self._safe, aux = self._aux, qvars = self.qvars, ) def _conjunction(self, clauses): context = [] for c in clauses: with self.local_safe() as s: p = self.eval(c) self.add_safe(*mk_implies(context, s)) context.append(p) return context def conjunction(self, clauses): """Interpret a list of predicates in a fresh context. The Interp.value returned will be a list of SMT expressions. """ self.reset() ps = self._conjunction(clauses) return Interp( value = ps, defined = self.defs, nonpoison = self.nops, safe = self._safe, aux = self._aux, qvars = self.qvars, ) def add_defs(self, *defs): """Add well-defined conditions to the current translator context. """ self.defs += defs @contextmanager def local_defined(self): """Create a context with well-defined conditions independent from any prior conditions. Returns the list of well-defined conditions associated with the operations in the context. Usage: with smt.local_defined() as s: <operations> """ old = self.defs try: new = [] self.defs = new yield new finally: self.defs = old def add_nonpoison(self, *nonpoisons): """Add non-poison conditions to current translator context. """ self.nops += nonpoisons add_nops = add_nonpoison # TODO: deprecate @contextmanager def local_nonpoison(self): """Create a context with nonpoison conditions independent from any prior conditions. Returns the list of nonpoison conditions associated with the operations in the context. Usage: with smt.local_nonpoison() as s: <operations> """ old = self.nops try: new = [] self.nops = new yield new finally: self.nops = old def add_safe(self, *safe): """Add safety conditions to the current translator context. """ self._safe += safe @contextmanager def local_safe(self): """Create a context with safety conditions independent from any prior conditions. Returns the list of safety conditions associated with the operations in the context. Usage: with smt.local_safe() as s: <operations> """ old = self._safe try: new = [] self._safe = new yield new finally: self._safe = old def add_aux(self, *auxs): """Add auxiliary conditions to the current translator context. """ self._aux += auxs def add_qvar(self, *qvars): """Add quantified variables to the current translator context. """ self.qvars += qvars def type(self, term): return self.types[term] def fresh_bool(self): self.fresh += 1 return z3.Bool('ana_' + str(self.fresh)) def fresh_var(self, ty, prefix='undef_'): self.fresh += 1 return z3.Const(prefix + str(self.fresh), _ty_sort(ty)) def has_analysis(self, name, key): return name in self._analysis and key in self._analysis[name] def get_analysis(self, name, key): return self._analysis[name][key] def new_analysis(self, name, key, type=None): if key in self._analysis[name]: raise ValueError('Attempt to recreate analysis {} for {}'.format( name, key)) self.fresh += 1 r = z3.Const( 'ana_{}_{}'.format(name, self.fresh), z3.BoolSort() if type is None else _ty_sort(type)) self._analysis[name][key] = r return r def _conditional_value(self, conds, v, name=''): raise NotImplementedError('{} does not support floating-point'.format( type(self).__name__.lower())) def _conditional_conv_value(self, conds, v, name=''): raise NotImplementedError( '{} does not support floating-point conversion'.format( type(self).__name__.lower())) def _binary_operator(self, term, op, defined, nonpoison, poisons): x = self.eval(term.x) y = self.eval(term.y) if defined: self.add_defs(*defined(x,y)) if nonpoison: self.add_nonpoison(*nonpoison(x,y)) if poisons: for f in poisons: try: b = self.get_analysis(f, term) self.add_nonpoison(z3.Implies(b, poisons[f](x, y))) except KeyError: if f in term.flags: self.add_nonpoison(poisons[f](x, y)) return op(x,y) def _float_binary_operator(self, term, op): x = self.eval(term.x) y = self.eval(term.y) z = op(x,y) conds = [] if self.has_analysis('nnan', term): df = z3.And(z3.Not(z3.fpIsNaN(x)), z3.Not(z3.fpIsNaN(y)), z3.Not(z3.fpIsNaN(z))) conds.append(z3.Implies(self.get_analysis('nnan', term), df)) elif 'nnan' in term.flags: conds += [z3.Not(z3.fpIsNaN(x)), z3.Not(z3.fpIsNaN(y)), z3.Not(z3.fpIsNaN(z))] if self.has_analysis('ninf', term): df = z3.And(z3.Not(z3.fpIsInf(x)), z3.Not(z3.fpIsInf(y)), z3.Not(z3.fpIsInf(z))) conds.append(z3.Implies(self.get_analysis('ninf', term), df)) elif 'ninf' in term.flags: conds += [z3.Not(z3.fpIsInf(x)), z3.Not(z3.fpIsInf(y)), z3.Not(z3.fpIsInf(z))] if self.has_analysis('nsz', term) or 'nsz' in term.flags: # NOTE: this will return a different qvar for each (in)direct reference # to this term. Is this desirable? b = self.fresh_bool() self.add_qvar(b) z = op(x,y) c = z3.fpIsZero(z) if self.has_analysis('nsz', term): c = z3.And(self.get_analysis('nsz', term), c) s = _ty_sort(self.type(term)) z = z3.If(c, z3.If(b, 0, z3.fpMinusZero(s)), z) if isinstance(term, FDivInst): c = [z3.Not(z3.fpIsZero(x)), z3.fpIsZero(y)] if self.has_analysis('nsz', term): c.append(self.get_analysis('nsw', term)) z = z3.If(z3.And(c), z3.If(b, z3.fpPlusInfinity(s), z3.fpMinusInfinity(s)), z) return self._conditional_value(conds, z, term.name) _icmp_ops = { 'eq': operator.eq, 'ne': operator.ne, 'ugt': z3.UGT, 'uge': z3.UGE, 'ult': z3.ULT, 'ule': z3.ULE, 'sgt': operator.gt, 'sge': operator.ge, 'slt': operator.lt, 'sle': operator.le, } _fcmp_ops = { 'false': lambda x,y: z3.BoolVal(False), 'oeq': z3.fpEQ, 'ogt': z3.fpGT, 'oge': z3.fpGEQ, 'olt': z3.fpLT, 'ole': z3.fpLEQ, 'one': lambda x,y: z3.Not(fpUEQ(x,y)), 'ord': lambda x,y: z3.Not(z3.Or(z3.fpIsNaN(x), z3.fpIsNaN(y))), 'ueq': fpUEQ, 'ugt': lambda x,y: z3.Not(z3.fpLEQ(x,y)), 'uge': lambda x,y: z3.Not(z3.fpLT(x,y)), 'ult': lambda x,y: z3.Not(z3.fpGEQ(x,y)), 'ule': lambda x,y: z3.Not(z3.fpGT(x,y)), 'une': z3.fpNEQ, 'uno': lambda x,y: z3.Or(z3.fpIsNaN(x), z3.fpIsNaN(y)), 'true': lambda x,y: z3.BoolVal(True), }