@@ -30,6 +30,7 @@ class TermToZ3APIConverter
3030
3131 val sortCache = mutable.HashMap [Sort , Z3Sort ]()
3232 val funcDeclCache = mutable.HashMap [(String , Seq [Sort ], Sort ), Z3FuncDecl ]()
33+ val smtFuncDeclCache = mutable.HashMap [(String , Seq [Sort ]), (Z3FuncDecl , Seq [Z3Expr ])]()
3334 val termCache = mutable.HashMap [Term , Z3Expr ]()
3435
3536 def convert (s : Sort ): Z3Sort = convertSort(s)
@@ -471,19 +472,37 @@ class TermToZ3APIConverter
471472 // workaround: since we cannot create a function application with just the name, we let Z3 parse
472473 // a string that uses the function, take the AST, and get the func decl from there, so that we can
473474 // programmatically create a func app.
474- val decls = args.zipWithIndex.map{case (a, i) => s " (declare-const workaround ${i} ${smtlibConverter.convert(a.sort)}) " }.mkString(" " )
475- val funcAppString = s " ( ${functionName} ${(0 until args.length).map(i => " workaround" + i).mkString(" " )}) "
476- val assertion = decls + s " (assert (= ${funcAppString} ${funcAppString})) "
477- val workaround = ctx.parseSMTLIB2String(assertion, null , null , null , null )
478- val app = workaround(0 ).getArgs()(0 )
479- val decl = app.getFuncDecl
480- val actualArgs = if (decl.getArity > args.length){
481- // the function name we got wasn't just a function name but also contained a first argument.
482- // this happens with float operations where functionName contains a rounding mode.
483- app.getArgs.toSeq.slice(0 , decl.getArity - args.length) ++ args.map(convertTerm(_))
484- }else {
485- args.map(convertTerm(_))
475+
476+ val cacheKey = (functionName, args.map(_.sort))
477+ val (decl, additionalArgs : Seq [Z3Expr ]) = if (smtFuncDeclCache.contains(cacheKey)) {
478+ smtFuncDeclCache(cacheKey)
479+ } else {
480+ val declPreamble = " (define-sort $Perm () Real) " // ME: Re-declare the Perm sort.
481+ // ME: The parsing happens in a fresh context that doesn't know any of our current declarations.
482+ // In principle, it might be necessary to re-declare all sorts we're using anywhere. However, I don't see how there
483+ // could be any Z3 internal functions that exist for those custom sorts. For the Real (i.e., Perm) sort, however,
484+ // such functions exist. So we re-declare *only* this sort.
485+ val decls = declPreamble + args.zipWithIndex.map { case (a, i) => s " (declare-const workaround ${i} ${smtlibConverter.convert(a.sort)}) " }.mkString(" " )
486+ val funcAppString = if (args.nonEmpty)
487+ s " ( ${functionName} ${(0 until args.length).map(i => " workaround" + i).mkString(" " )}) "
488+ else
489+ functionName
490+ val assertion = decls + s " (assert (= ${funcAppString} ${funcAppString})) "
491+ val workaround = ctx.parseSMTLIB2String(assertion, null , null , null , null )
492+ val app = workaround(0 ).getArgs()(0 )
493+ val decl = app.getFuncDecl
494+ val additionalArgs = if (decl.getArity > args.length) {
495+ // the function name we got wasn't just a function name but also contained a first argument.
496+ // this happens with float operations where functionName contains a rounding mode.
497+ app.getArgs.toSeq.slice(0 , decl.getArity - args.length)
498+ } else {
499+ Seq ()
500+ }
501+ smtFuncDeclCache.put(cacheKey, (decl, additionalArgs))
502+ (decl, additionalArgs)
486503 }
504+
505+ val actualArgs = additionalArgs ++ args.map(convertTerm(_))
487506 ctx.mkApp(decl, actualArgs.toArray : _* )
488507 }
489508
@@ -522,6 +541,7 @@ class TermToZ3APIConverter
522541 sanitizedNamesCache.clear()
523542 macros.clear()
524543 funcDeclCache.clear()
544+ smtFuncDeclCache.clear()
525545 sortCache.clear()
526546 termCache.clear()
527547 unitConstructor = null
0 commit comments