<?xml-model href='http://www.tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng' schematypens='http://relaxng.org/ns/structure/1.0'?><TEI xmlns="http://www.tei-c.org/ns/1.0">
	<teiHeader>
		<fileDesc>
			<titleStmt><title level='a'>Better Predicate Pruning and Heuristics for Commutativity Synthesis</title></titleStmt>
			<publicationStmt>
				<publisher></publisher>
				<date>2023</date>
			</publicationStmt>
			<sourceDesc>
				<bibl> 
					<idno type="par_id">10463895</idno>
					<idno type="doi"></idno>
					<title level='j'>International Symposium on Automated Technology for Verification and Analysis</title>
<idno></idno>
<biblScope unit="volume"></biblScope>
<biblScope unit="issue"></biblScope>					

					<author>Adam Chen</author><author>Tegan Brennan</author><author>Parisa Fathololumi</author><author>Eric Koskinen</author><author>Mihai Nicola</author><author>Jared Pincus</author>
				</bibl>
			</sourceDesc>
		</fileDesc>
		<profileDesc>
			<abstract><ab><![CDATA[Code commutativity has increasingly many applications including proof methodologies for concurrency, reductions, automated parallelization, distributed systems and blockchain smart contracts. While there has been some work on automatically generating commutativity conditions through abstraction refinement, the performance of such refinement algorithms critically depends on (i) the universe of predicates and (ii) the choice of the next predicate during search, and thus far this has not been examined in detail. In this paper, we improve commutativity synthesis by addressing these under-explored requirements. We prune the universe of predicates through a combination of better predicate generation, new a priori syntactic filtering, and through dynamic reduction of the search space. We also present new predicate selection heuristics: one based on look-ahead, and one that utilizes model counting to greedily cover the search space. Our work is embodied in the new commutativity synthesis tool Servois2, a generational improvement over the state-of-the-art tool Servois. Ser-vois2 is implemented in a faster language and has support for CVC5 and Z3. We contribute new, non-trivial commutativity benchmarks. All of the new features in Servois2 are shown to either increase performance (geomean 3.58⇥ speedup) or simplify the conditions generated, when compared against Servois. We also show that our look-ahead heuristic leads to better scaling with respect to the number of predicates.]]></ab></abstract>
		</profileDesc>
	</teiHeader>
	<text><body xmlns="http://www.tei-c.org/ns/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink">
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="1">Introduction</head><p>Commutativity of data structure methods and program code applies to a wide variety of contexts, ranging from proof methodologies for concurrency (e.g. Siever <ref type="bibr">[1]</ref>, CIVL <ref type="bibr">[2]</ref>, Anchor <ref type="bibr">[3]</ref>) to exploiting multicore (e.g. parallelizing compilers <ref type="bibr">[4]</ref>, transactional memory <ref type="bibr">[5]</ref> declarative programming <ref type="bibr">[6,</ref><ref type="bibr">7]</ref>, scalable systems <ref type="bibr">[8]</ref>) to distributed systems (e.g. CRDTs <ref type="bibr">[9]</ref> and blockchain <ref type="bibr">[10,</ref><ref type="bibr">11]</ref>).</p><p>Accordingly, there have been a variety of techniques and tools for reasoning about commutativity, including program analysis <ref type="bibr">[4]</ref>, sampling <ref type="bibr">[12]</ref>, random Contributions. Our work improves the state-of-the-art in the following ways:</p><p>1. Predicate semantics. (Sec. 3) In the state of the art, predicates must be built by manually writing terms, and are then mildly filtered and used without any information as to how one predicate relates to another. Refinement is exponential in the number of predicates so it is important to focus on important predicates. To that end, we improve the treatment of predicates by both syntactically and then semantically filtering redundant predicates. We next show how the information from filtering can be used to construct a lattice of predicates, ordered by implication, and use this lattice to better filter predicates and prune the state space during search. We also automatically extract terms from the input problem's pre/post relations.</p><p>2. Search heuristics. (Sec. 4) A key step in the algorithm is choosing the next predicate to divide the search space. We implement two new heuristics:</p><p>-poke2: A new predicate selection heuristic which avoids redundant SMT work, while also using the information obtained more directly. Consequently, it performs at most half as many SMT queries, if not fewer, than Servois's original implementation. -mcMax: A heuristic that employs model counting to more quickly cover the search space. Model counting is the problem of computing the number of models (i.e. distinct assignments to variables) that satisfy a given predicate <ref type="bibr">[16]</ref>. As many predicates have infinitely many solutions, model-counting constraint solvers return the number of solutions for a given predicate within a given bound <ref type="bibr">[17,</ref><ref type="bibr">18]</ref>. The mcMax heuristic takes a quantitative approach to predicate selection by leveraging model-counting to greedily pick predicates based on the largest covering of the state space, making choices based on approximate finite-domain information, yet maintaining soundness of the overall infinite-domain algorithm.</p><p>3. New implementation and pragmatic concerns. (Sec. 5) We implemented Servois2 in OCaml, exploiting the expected performance benefits of OCaml over Python. Our implementation is parametric on SMT solver, now supporting CVC4, CVC5 and Z3. We therefore inherit the expanded theory support, expanding the domains in which commute conditions can be synthesized. Ser-vois2 can now, for example, synthesize commute conditions for Strings operations like hasChar and concat. We also support interruption, emitting a sound but incomplete condition, allowing Servois2 to be used in a larger variety of new settings. Finally, Servois2 has a more well-defined API (as an OCaml type), allowing one to use it as a library. Servois2 is publicly available at: github.com/veracity-lang/servois2. The artifact, which contains a copy of the code, is available at: <ref type="url">https://www.doi.org/10.5281/zenodo.7935263</ref>.</p><p>4. Evaluation. (Sec. 6) In order to show that our approaches improve performance in practice, we introduced new, non-trivial benchmarks that Bansal et al. <ref type="bibr">[14]</ref>'s tool Servois struggles to solve. We evaluated all of Servois2's new approaches, including the poke2 and mcMax heuristics, in comparison to a faithful re-implementation of the poke heuristic in our new OCaml implementation on both the original benchmarks and our new ones. We also compared the performance improvements between heuristics with additional options for tuning the synthesis (see Sec. 6). Our experiments demonstrate that our approaches do give a substantial speedup-3.58&#8677; (geometric mean) faster. In cases that involve theories where model counting can be done e ciently (strings, linear integer arithmetic, integer arrays), mcMax is often able to o&#8629;er better performance. Furthermore, poke2 scales approximately linearly with the number of state variables, while the other heuristics (including all those in the prior work) diverge after only a few variables. Finally, given this wide variety of options, we used a portfolio approach, running each case with all options (solvers, heuristics, approaches to terms, etc.) and reporting back the first one to finish.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="2">Background: Commutativity synthesis</head><p>We begin with a brief review of abstraction-refinement commutativity condition synthesis, emphasizing key steps.</p><p>Suppose we have an abstract data type (ADT) method call m(&#257;)/r m , with method name m, taking argument vector &#257; and returning value r m . Similarly, consider a second method call n( b)/r n . We say these method calls commute from an initial ADT state , provided that when methods are applied in either order, they lead to the same final ADT state, and will have observed the same return values along the way. We notate this m ./ ,&#257;, b,rm,rn n, with subscripts omitted when the context is clear. A commutativity condition is a logical formula ' n m ( , &#257;, b) describing the conditions on the initial ADT state (and parameters &#257; and b) under which m and n always commute. (A non-commutativity condition ' describes conditions when they always do not commute.) As an example, a  Set ADT with methods insert(x) and remove(y), a su cient commutativity condition would be ' remove insert &#8984; x 6 = y. We synthesize a commutativity condition ' via the Refine algorithm <ref type="bibr">[14]</ref>, which takes as input, an ADT specification, with methods' pre/post conditions written in SMTLIB. The algorithm uses the binary operator ./, which is defined as ./ on a lifted (total) version of the ADT; we omit the full details as they are not relevant to our improvements on the work. When run on a given pair of methods m and n, the output of the algorithm is a pair (' n m , 'n m ) of commutativity/noncommutativity conditions. Consider as an example input, an ADT for a hashtable that has three variables representing the state: a size integer, a Set over sort E of keys, and a finite array H mapping elements of sort E to sort F . Then, for each method, e.g., put(k, v), the input ADT specification includes a pre-condition (in this case true) and a post-condition relating the pre-state with input vector (size, keys, H, k, v) to a tuple of new values with return value (in this case, true or false representing success) (size new, keys new, H new, r). The Refine algorithm as output synthesizes commutativity conditions for the input method pair. In the case of the hashtable example, the solution for the commutativity synthesis of two calls of the same method put(k 1 , v 1 ) and put(k 2 , v 2 ) generated by the algorithm is '</p><p>The Refine algorithm is presented in Fig. <ref type="figure">1</ref>. The algorithm recursively partitions the logical space along conjunctions of predicates, which are selected from a set of predicates P. When the algorithm finds a region of the state space H that is a su cient condition for commutativity (or mutatis mutandis non-commutativity), it adds it to an accumulated DNF logical commutativity condition. Otherwise, the recursive calls use counterexamples to select a predicate p that di&#8629;erentiates the two counterexamples c and nc . This predicate is conjunctively added to H and used in the children recursive calls, and similarly for its negation. Fig. <ref type="figure">2</ref> illustrates this process of partitioning the logical space through the use of di&#8629;erentiating counterexamples. This process continues until a necessary and su cient commutativity condition is found, or all combinations of predicates are exhausted. Typically, exhaustion of predicates is unlikely as there are exponentially many combinations of them. Furthermore, the algorithm can theoretically be interrupted (e.g. after a timeout) to yield a sound commutativity condition.</p><p>While the Refine algorithm is a somewhat straightforward form of abstraction-refinement, the e&#8629;ectiveness of the technique and implementation thereof critically depends on how predicates are handled, selected, pruned, etc. We now discuss these details and how Servois2 improves on each of them.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="3">Semantic Treatment of Predicates</head><p>Refine has worst-case exponential runtime in the number of predicates. While a Choose function that picks good predicates helps, it is still important to generate a small set P of relevant predicates and be selective during each recursive call. At the very least, reducing the number of predicates gives linear improvements on runtime, as SMT solvers, as well as Choose, must handle every predicate in P. In this section, we describe better methods of reducing the size of this set P.</p><p>(a) Improved predicate filtering. In Servois, after the initial list of predicates was built from manually provided terms, the SMT solver was queried twice for each predicate, and any predicate that was tautologically true or false was discarded. We retain this functionality, but first perform an additional syntactic layer of filtering by dropping any predicate that is:</p><p>1. A reflexive operation on two identical terms, 2. An operation between two constants, or 3. A symmetric case of another predicate already included. Since all of these filters are done purely syntactically, we save SMT work.</p><p>(b) Pruning by exploiting implication. We next determine which predicates imply other predicates. This can be done via syntactic implication rules such as x &gt; y ) x + n &gt; y + n. As a benefit, we are able to compute the closure of the logical implication relation, and are able to sort predicates into equivalence classes. Thus by removing redundant predicates, the size of the set of predicates can be reduced. Given logical implication relations, we can build a lattice out of the partially ordered set of predicates, ordered by the ) relation. This lattice information can be used to dynamically prune predicates that become redundant during runtime due to selection of other, related predicates. For example, consider the following LIA benchmark multiVarA ./ multiVarB. (Technically Servois/Servois2 inputs are given as ADT pre/post specifications, but we write this example as code illustration purposes.) Here, the lattice identifies implication chains such as 0 &gt; (2x + y)</p><p>During search, we are able to use these chains to e ciently pruning the predicate lattice, e&#8629;ectively reducing the height of the lattice and thus width of the search tree.</p><p>Fig. <ref type="figure">3</ref> illustrates our modifications to the original algorithm in Fig. <ref type="figure">1</ref>. Starting from main, we perform automated predicate generation PredGen, which we will discuss below. From this set P, we construct the lattice L with MkLat. Within Refine, we parameterize Choose by L, allowing the Choose heuristics discussed in Sec. 4 to make choices based on L. Finally, we prune the search space by using RmUpper to remove all predicates that are weaker, i.e. higher in the lattice, than the selected predicate pair (p, &#172;p). We may do similarly with the predicates stronger than the negation (RmLower). As a result, recursive calls will not have to consider any predicates that are already entailed by H.</p><p>Constructing the lattice can be costly as the size of the relation is quadratic in the number of predicates. Furthermore, syntactic rules cannot discover all implications, so an SMT solver must be invoked if more precision is desired. As we will see, it is not always worth this overhead. We have thus kept our lattice treatment as an optional feature. When disabled, the lattice simply behaves as a set of predicates (i.e. any predicate is only related to itself), &#224; la Fig. <ref type="figure">1</ref>, and assume that the set of predicates is closed under negation &#167; .</p><p>(c) Automatic predicate generation via term extraction. Servois requires the programmer to manually provide terms with each method in the ADT specification, which can be error-prone and tedious. These terms are then used to build predicates by using boolean relations such as =, &gt;, etc. We are able to automatically generate the terms for synthesizing the predicates by traversing the method specification (state variables, method arguments, pre/post-condition), and extracting basic expressions (categorized by type). The expressions are then combined with predefined operations for each type (e.g. in-/equality for Integers, membership/subset/etc. for Sets, contains/prefix/etc. for Strings, . . . ) to generate the predicates. With this approach, we generate enough predicates to establish a su ciently granular search space across all of our benchmarks and &#167; To satisfy closure, we include negations of all predicates. This comes at no performance loss, as such additions can be skipped over by Choose. This is valid because Refine recurses upon the negation of the chosen predicate. we are not limited in how exhaustively terms are provided. Manual-vs-automatic term extraction leads to di&#8629;erent sets and quantities of predicates, which may a&#8629;ect how conditions are expressed. In Sec. 6 we discuss the performance impact.</p><p>(d) Syntax-based generation of predicates. Once predicates are automatically generated, it is natural to consider whether more complex predicates can be generated. While the original tool only considered predicates on two given terms, we found that often, compound terms that may not be provided or directly in the specification's syntax would be present in commutativity conditions. We added the expansion of terms with known and provided functions to allow for the automated generation of compound terms and predicates. Thus more complex commutativity conditions could be expressed, and the user does not have to already have specific predicates in mind when listing terms. Due to the exponential nature of syntax expansion, we get a greatly increased number of predicates. We found that this increase was too detrimental to performance to be practical-two or more iterations often times out. However there were still some test cases that benefited from performing one or two iterations, and the approach would likely be beneficial with improved pruning.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4">Search Heuristics</head><p>At each step, the Refine algorithm must Choose a predicate that di&#8629;erentiates the commutative and non-commutative examples. While any implementation of Choose maintains the soundness of Refine, due to the exponential nature of the number of subsets of predicates, choosing a "good" predicate is important both to e ciency and quality of the form of the emitted condition. We refer to a Choose strategy as a "heuristic". Bansal et al. <ref type="bibr">[14]</ref> describe a heuristic-referred to as poke-which performs a greedy one-step look-ahead.</p><p>(1) Choosepoke2(H, P, c, nc){ <ref type="bibr">(2)</ref> let P 0 = Di ngPreds(P, c, nc)in</p><p>return 0; <ref type="bibr">(7)</ref> else if valid(H^!p 0 ) m \ ./ n) then <ref type="bibr">(8)</ref> return 0; </p><p>return Length(</p><p>Di ngPreds(P 0 , 0 c , 0 nc )) <ref type="bibr">(14)</ref> in list min(weight, P 0 ) (15) } (16) Di ngPreds(P, c, nc){ <ref type="bibr">(17)</ref> return filter((fun p !</p><p>Fig. <ref type="figure">4</ref>: Pseudocode for our poke2 heuristic for choosing which predicate to recurse upon. Here, P may be obtained from L by taking the underlying set of predicates. list min(f, P) returns the element of P that minimizes f . In this section we introduce two new predicate selection heuristics called Choose poke2 and Choose mcMax (or simply, poke2 and mcMax) that, as we show in Sec. 6, perform better than the Choose poke of Bansal et al. <ref type="bibr">[14]</ref>, with trade-o&#8629;s to consider between the two of them.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.1">The poke2 heuristic</head><p>We begin by formalizing the poke2 heuristic, and compare it to the previous poke heuristic. When the SMT solver is invoked with the "valid()" queries in Refine, we obtain two satisfying counterexamples: c for commutativity and nc for non-commutativity. poke and poke2 share the common behavior to then proceed with two steps: (1) Test each predicate to see which hold in which counterexample (if either); this can be done in the same SMT query that was used for valid. This testing lets one find predicates that di&#8629;er between the commutative counterexample and the non-commutative counterexample. This is summarized in Di ngPreds; the pseudocode for this subroutine is given at the bottom of Fig. <ref type="figure">4</ref>. (2) Next, perform a partial look-ahead on each of these predicates-however, the way this is done di&#8629;ers between the heuristics.</p><p>-The poke2 heuristic. The full pseudocode for poke2 is given in Fig. <ref type="figure">4</ref>.</p><p>The partial look-ahead is encapsulated in the weight function. If a predicate was true in the commutative case then we can tentatively conjoin it with the commutativity condition and its inverse with the non-commutativity condition (mutatis mutandis for false-keeping track of which case is done on Line 4), then query the solver (Lines 5 and 7) to see how many predicates still</p><p>cover(&#172;p) = 1 cover(p) in (5) let P 00 = list max(cover, P 0 ) in <ref type="bibr">(6)</ref> return first(P 00 )</p><p>cover(&#172;p) = 1 cover(p) in <ref type="bibr">(5)</ref> let P 00 = list max(cover, P 0 ) in <ref type="bibr">(6)</ref> return list min(weight, P 00 ) (7) } (b) mcMax-poke2 Fig. <ref type="figure">5</ref>: Pseudocode for mcMax heuristics. As before, P is obtained by taking the underlying set of L.</p><p>di&#8629;erentiate the two cases. We define the number of remaining di&#8629;erentiating predicates to be the weight of the predicate (Lines 9-11). Finally we pick the di&#8629;erentiating predicate that results in the fewest remaining di&#8629;erentiating predicates in the look-ahead (Line 12). In the case that two predicates have the same number of new di&#8629;erentiating predicates, we prefer the simpler (measured in number of atoms) one (not shown).</p><p>-The poke heuristic. By contrast, in poke the predicate and its inverse were tested with both the commutative case and the non-commutative case, irrespective of whether it was true or false in the commutative case. This resulted in many degenerate return values, which not only increased SMT time, but also could pick less beneficial predicates.</p><p>There is no need to prove correctness of poke2, as the Refine algorithm is correct for any implementation of Choose that picks a di&#8629;erentiating predicate. Our evaluation of poke2 is thus based on runtime (more detail in Section 6.1).</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="4.2">The mcMax heuristic</head><p>For theories where model counting is e ciently supported by existing tools, we introduce an additional heuristic called mcMax. mcMax uses model counting to determine the number of satisfying solutions for each constraint on the state space. It then uses this count to quantify how well each predicate covers the state space and picks the predicate with the best coverage. Model counting requires a finite domain, so we treat state variables as finite on a bounded domain, e.g., treating integers as fixed-length bit vectors. Recall that any implementation of Choose is sound, so bounding the domain (temporarily as a heuristic hint) does not threaten soundness. For such fixed-length bit representations, we ideally require a bit width bound that is large enough to properly di&#8629;erentiate between coverage ratios. Experimentally, we found that a bound as low as 4-bit representation of integers was su cient for LIA constraints with relatively small coe cients and a length of 4 was su cient for string constraints.</p><p>We now describe how mcMax proceeds using the pseudocode given in Fig. <ref type="figure">5</ref>. The mcMax heuristic starts o&#8629; on Line 2 in a similar manner as poke heuristic by constructing the subset of di&#8629;erentiating predicates P 0 from the two satisfying counterexamples. In the next step (Lines 3-4), we calculate the coverage ratio for both p and its complement &#172;p as the fraction of their corresponding models' count. Finally, the predicate found to represent the largest state region is chosen (Line 5). Recall that Refine traverses both the given predicate and its negation (shown in the recursive calls in Fig. <ref type="figure">1</ref>). In the case that execution is interrupted, we observe the first recursive call may be explored, while the second is not. In these cases mcMax often leads to a better (higher coverage ratio) predicate compared to a non-model-counting heuristic, since we greedily pick the larger conjuncts.</p><p>To overcome the arbitrary first choice among equally covering predicates at line 6 in Fig. <ref type="figure">5a</ref>, the variant mcMax-poke2 equips mcMax with the weight-based ranking of predicates from poke2. Whenever the list of maximal coverage predicates returned by mcMax has at least two candidates, the predicate selection is turned over to poke2 applied to the candidate list.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="5">Implementation</head><p>Servois2 is implemented in OCaml and is publicly released under the MIT License &#182; . The tool has an underlying representation for SMT expressions, and parses input YAML files and the SMTLIB2 expressions within them. Examples of the Servois/Servois2 input format are available in the repository. The output commutativity condition is also an SMTLIB2 expression, but may be further constrained: since it is always in disjunctive normal form, and we add one conjunct at a time, we may model disjunctive normal form as a list of conjuncts, which are in turn lists of atoms. The lattice is implemented as a module parameterized by any module exposing an ordering relation, and is encoded as a graph with vertices stored in a map and two edge sets: that of covering elements and that of elements covered by it.</p><p>Model counting. For counting the solutions satisfied by each predicate, we use the state-of-the-art model-counting constraint solver ABC <ref type="bibr">[18]</ref> that, among other strengths, allows for passing the specific domain bound along with the model-counting query. ABC supports precise solving of model-counting queries over strings, booleans, and linear integer arithmetic. We memorize the counting results in an association list to reuse them in subsequent calls of Choose mcMax .</p><p>Model counting for integer arrays. We expand the applicability of mcMax heuristics to predicates over array terms by adopting a method similar to the state-of-the-art model counter for bounded array constraints MCBAT <ref type="bibr">[19]</ref>. This approach involves applying a sequence of model-count preserving reductions from the theory of arrays to the theory of uninterpreted functions and linear integer arithmetic before dispatching the query to ABC. While MCBAT focuses on formulas that are universally quantified over index variables, our procedure below addresses quantifier-free array constraints with terms a[i] representing the value stored in the array a at index i. Consider for example, the problem of counting the solutions hx, i, ji satisfying &#182; <ref type="url">https://github.com/veracity-lang/servois2</ref> the predicate (x[i] x[j] 1), where i, j are integer variables and x represents arrays of size 4. We accomplish the task in three stages. First, we translate the predicate into a list of linear integer arithmetic constraints that are conjoined into a formula. Then we count the number of satisfying solutions hx i , x j , i, ji by running ABC on this query:</p><p>Finally, we obtain the total model count by multiplying the translated query result with the value domain size twice, once for each of the unaccounted and implicitly unconstrained array values.</p><p>The reductions below summarize the steps of our model counting procedure for formulas with integer array constraints:</p><p>1. Replace all compound array index expressions e with fresh variables i and add corresponding constraints of the form e = i. Perform the replacement from the outermost expression inwardly. Consider, for example, the term</p><p>x[k + j 2] &gt; 3 occuring in the query. We first replace the access term k + j 2 by a fresh variable i, and then introduce an additional constraint i = k + j 2 which captures this replacement. 2. Add array bounds constraints for each array index variable i.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="3.">Perform Ackermann's reduction:</head><p>-Replace all occurrences of array index terms a[i] with fresh variables a i , keeping track of the replaced mappings for each array variable a. -Add functional consistency constraints for each array variable and each pair of array index terms occurring in the query, i.e. (i = j) ) (a i = a j ). 4. Dispatch the set of constraints to ABC and obtain the model-count #mc tr .</p><p>Thus far, there are only minimal di&#8629;erences to the approach in <ref type="bibr">[19]</ref>. 5. Identify the unaccounted mappings for each array variable and compute the partial model-count by considering their summation and the unconstrained value domain: #mc unacc = |Z| unacc . 6. Obtain final model count as #mc = (#mc tr &#8676; #mc unacc ).</p><p>Additional solvers &amp; theories. Servois was hardcoded to work with CVC4 <ref type="bibr">[20]</ref>. We have parameterized Servois2 by SMT solver via OCaml modules and extended support for CVC5 <ref type="bibr">[21]</ref> and for Z3 <ref type="bibr">[22]</ref>. While mostly an implementation detail, this does allow us to leverage the additional strength of the other solvers. For example, CVC4 (as of version 1.8) did not have good support for modulus and division. Both CVC5 and Z3 are able to support such operations, and Servois2 is able to generate commutativity/non-commutativity conditions for modular arithmetic examples.</p><p>With expanded solver support, Servois2 can tackle more theories, including ones for which specialized solvers are useful. Neither bit-vectors nor strings were supported in the original release of Servois, but Servois2 can synthesize commutativity conditions for both theories. As an example, we showed that Servois2 is capable of inferring that bit-vector negation always commutes with itself. We also benchmarked a few string examples, such as substr ./ hasChar, as they also demonstrate the usefulness of model counting.</p><p>Early termination. The following theorem is presented in Bansal et al. <ref type="bibr">[14]</ref>:</p><p>For each Refine m n iteration: ' ) m ./ n, and ' ) m \ ./ n.</p><p>Thus, if updates to ' and ' are atomic, then terminating the algorithm at any point will yield valid conditions. We take advantage of this in Servois2 by allowing timeouts: the algorithm gracefully terminates after a designated time by outputting the incomplete (yet valid) conditions ', '. This proves useful in practice, as not all commutativity conditions may be expressible in terms of the predicates available; a necessary and su cient condition for synthesis of a complete commutativity condition via the Refine algorithm is given in Bansal et al. <ref type="bibr">[14]</ref>. In such cases, the algorithm must finish its exponential run-time, only to determine that no complete commutativity condition is expressible. Even if the algorithm does terminate, after a certain point, the commutativity condition may be more complex than is useful. Thus it is usually more useful to cut the execution short and report only the most important few disjuncts of the commutativity conditions. In Sec. 6.2 we describe an instance of both a case where the algorithm does not terminate and a case where the algorithm terminates, but we still may obtain a reasonable condition by limiting the execution time.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6">Evaluation</head><p>We evaluated whether Servois2 improved over the state-of-the-art Servois in terms of performance (speed) and expressivity. All experiments below were run on a machine with an AMD EPYC 7452 32-Core CPU, 128GB RAM, Ubuntu 20.04, and OCaml 4.14.0.</p><p>Benchmarks. Our suite of 68 benchmarks begins with those used to evaluate Servois in the prior work <ref type="bibr">[14]</ref>. Since the core goal of our work is to improve performance, we have pruned down this set, removing those benchmarks for which all tested heuristics can synthesize a condition after zero or one iteration(s). For example, we omitted the counter and accumulator examples because the conditions generated were either true/false or a single atom. We also removed all similar method pairs with simple commutativity conditions from the remaining data structures: sets, hashtables (HT), and stacks (Sta).</p><p>In addition to these benchmarks, we contribute new benchmarks for strings (Str) and linear integer arithmetic calculations (LIA), and a benchmark based on rigid motions on hexagons (DiH, for "dihedral"). These serve to show the application of model counting, which works best on these domains. The model counter is not applicable to the other data sets due to presence of custom data declarations. It could also be run on the counter and accumulator benchmarks, but we do not expect that to be illustrative due to triviality. Moreover, we used Veracity project <ref type="bibr">[6]</ref> benchmarks as additional nontrivial benchmarks. There are 26 reported benchmarks in Veracity that use commutativity synthesis. We have also implemented some new benchmarks, e.g. Solidity examples translated to Veracity, to demonstrate various aspects of our improvements in addition to more speedup. We elaborate on the usage of Veracity benchmarks in Sec. 6.2.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.1">Performance results</head><p>We would ideally compare the performance of Servois2 versus Servois, but since Servois2 is written in OCaml, and Servois is written in Python, there is an obvious speedup from compilation, and indeed we found Servois2 to be at least twice as fast even using the same heuristics and on the same inputs. (As an example: the Hashtable put/put example was the slowest running benchmarkit took 5.31s with Servois using poke, and 2.61s with Servois2 using the same heuristic.) However, our work is not aimed at comparing Python vs OCaml, so we instead benchmark across our new heuristics (poke2 and mcMax) and features in comparison to a faithful re-implementation of Servois's poke in OCaml. The re-implementation was created by manually translating the source code of Servois.</p><p>Comparison to poke baseline. To test the variety of features we have added, we ran each benchmark with all combinations of features:</p><p>-The heuristics poke, poke2, and mcMax/mcMax-poke2 (when applicable).</p><p>-With each of the CVC4, CVC5, and Z3 solvers.</p><p>-With and without automatic term extraction (Sec. 3).</p><p>We report the configuration with the best performance in Table <ref type="table">1</ref> * * , using poke with CVC4 and no lattice, no term extraction as a reference point for comparison. The heuristic and solver is given, then whether term extraction was performed (notated TG). The geometric mean of the speedup ratio of the best configuration over poke was 3.58&#8677;. Note that this speedup is conservative, as the several benchmarks that timed out with poke (and did not with Servois2) are excluded. We also report the change in the complexity of the synthesized commutativity condition in A, indicating the change in the number of atoms in the synthesized condition. The full generated conditions are omitted; note that if synthesis terminates with a complete condition, the generated conditions will be logically equivalent, but sometimes the order of the terms changed. ( &#8224; ) indicates the cases where the tool terminated with an incomplete condition. We terminated the benchmarks at 120s, and indicate the ones that still did not finish within this time with T. A few benchmarks could not be run under CVC4, and those are marked with j. All benchmarks whose poke baseline took less than 1 second to execute were omitted from the table due to triviality. T indicates time out (set at 120s). j indicates cannot be run.</p><p>The mcMax heuristic only applies to ADTs with theories supported by the model counter ABC <ref type="bibr">[23]</ref> extended with our procedure in Sec. 5, hence our results using that heuristic are limited to the String, LIA, and Dihedral ADTs, as well as the Veracity benchmarks. Our extension for integer arrays allowed for the use of mcMax on the majority of the Veracity benchmarks. In some cases, mcMax provides a significant speedup over poke and even poke2. For example, in the hasChar ./ concat benchmark, mcMax is 2.89&#8677; as fast as poke2 (not shown) and over 5.41&#8677; as fast as poke, with the same configuration aside from heuristic. In other cases, such as in the sum ./ multiVarSum analysis, mcMax underperforms compared to poke2 and poke. The performance of mcMax seems to depend on the methods considered, but there are cases where it can significantly improve run time. In future work, we hope to explore additional model-counting heuristics such as bisecting the search space rather than greedily covering it.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Extraction of terms.</head><p>The original Servois tool required users to provide terms, sacrificing some degree of automation which is inconvenient and errorprone. As described in Sec. 3, Servois2 now can automatically extract terms from the method specifications. As shown in Table <ref type="table">1</ref>, denoted by TG, the automated term extraction can even outperform manually provided terms.</p><p>In addition, by automatically extracting terms, our approach is another step closer to a fully automated commutativity synthesizer-the user does not have to do the manual work of providing terms. We believe that in conjunction with comparable performance, this makes automated term extraction preferable.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Predicate lattice.</head><p>We also evaluated the performance using the predicate lattice approach outlined in Sec. 3. In practice, we found that the overhead of lattice construction using SMT queries was typically too high, and it did not substantially improve synthesis time in most cases. When using syntactic rules (using a preliminary set of inference rules and axioms), we did not discover Fig. <ref type="figure">6</ref>: Experimental results on the scalability of general-purpose heuristics enough implications to be useful in any cases, with still substantial, albeit greatly reduced overhead. However, we did find that some LIA examples were improved by using the SMT implication lattice. sum ./ multiVarSum in particular saw a 2.27&#8677; speedup from 8.36s to 3.69s, which is a substantial speedup even accounting for the lattice construction time of 1.87s. In multiVarA ./ multiVarB, the discovery of logically equivalent predicates filtered more than half of the initial list of predicates-from 280 (including negations of predicates) to 106. For the complete results, see Table <ref type="table">2</ref>. While it remains unclear whether the predicate lattice can be used for performance gains in most cases, the preliminary results suggest that further work may yield larger gains in di cult cases.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head>Scalability.</head><p>Consider the toy example below, where we have a possibly ordered set described by x variables and we want to compute another element a that can potentially be added to the set after applying a marginal decrement. In this (somewhat artificial) example, the number of predicates increases (Fig. <ref type="figure">6b</ref>) with the number of variables, while the commutativity problem has a straightforward solution:</p><p>(1) int a, b, x 1 , x 2 , ..., x n ;</p><p>(2) bool sum(){</p><p>a := (a -b); return true; } (4) bool multiVarSum(){ <ref type="bibr">(5)</ref> if (a&gt;0 &amp;&amp; x 1 &lt;x 2 &amp;&amp; x 2 &lt;x 3 &amp;&amp; ... &amp;&amp; x n 1 &lt;x n ){ <ref type="bibr">(6)</ref> a := (x n + a); return true; <ref type="bibr">(7)</ref> } else { <ref type="bibr">(8)</ref> a := (x n -a); return true; }} Although mcMax shows promising results, due to reduced applicability to cases where e cient model counting is supported, we did not consider it for this particular experiment. Our focus here is on general-purpose heuristics. Fig. <ref type="figure">6a</ref> reports the results of our experiments running the heuristics simple (presented in <ref type="bibr">[14]</ref>), poke, and poke2 on the above example, with increasingly many variables (up to 30) and, consequently, increasingly many predicates. For the precise ADT specification, refer to the "lia scale var template" file in the artifact or Github provided in Sec. 1. We observe an impressive performance benefit from the poke2 heuristic. Firstly, observe that starting from only a small number of predicates, poke2 proved to be one order of magnitude faster than poke which timed out early in our experiment. Secondly, the increase in the number of state variables</p><p>x is roughly linear with the increase of poke2 synthesis time. And lastly, the poke2 heuristic led to synthesizing the utmost simple condition, namely the one humanly inferred.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="6.2">Case study: commute blocks in Veracity</head><p>To show Servois2's applicability, we present the case study of its use in the Veracity &#8224; &#8224; project <ref type="bibr">[6]</ref>, recalled below. The original Servois lacked features (e.g. solvers, theories, early termination) and performance to be used in such a setting. Despite the use of Servois2 in Veracity, the improvements described in the current paper are orthogonal.</p><p>Veracity is a parallelizing compiler for a language in which programmers directly express conditions under which sequential blocks of code commute <ref type="bibr">[6]</ref>. Expanding programs with such commutativity annotations enables parallelization of sequential code that has dataflow dependencies, which previously could not be parallelized. We omit the finer details as it is outside the scope of commutativity synthesis. Consider for example, the following Veracity benchmark even-odd includes a commute statement, with a blank commutativity condition to be synthesized (or provided by the user):</p><p>Veracity needs Servois2 in order to synthesize the following commutativity condition for these program fragments labeled (1) and ( <ref type="formula">2</ref>): y = 0 _ (y 6 = 0 ^x%2 =</p><p>x + y). In more detail, Servois2 is used by first having the Veracity compiler translate the program code into methods, say block 1 () and block 2 () on an ADT whose state are the program variables x and y. Then, the synthesized commutativity conditions are translated back and inserted in place of the " " in the Veracity commute block. Unfortunately the original Servois's limited support for solvers/theories (as well as limited performance) prevents it from synthesizing a commutativity condition for this benchmark. The divergent behavior of Servois on some benchmarks was a further impediment to its use in Veracity.</p><p>A few selected benchmarks are shown in Table <ref type="table">3</ref>. These benchmarks are illustrative of the di&#8629;erent kinds of typical output from Servois2. Most cases  were similar to the dict example, terminating in a few seconds with a sensible result. The ht-simple case takes more time. The condition is complete, but due to the longer time, it may be worth terminating the algorithm early and only receiving one or two of the disjuncts, especially if they cover the most common cases. Finally, loop-amt is a case that is not amenable to commutativity inference and it would be better to terminate sooner and allow the user to attempt a di&#8629;erent approach. Unlike direct ADT benchmarks, those derived from Veracity programs involve the composition of numerous e&#8629;ects and thus involve complex commutativity conditions. Consequently, most of the Veracity benchmarks make substantially more complex queries to Servois2 than the handwritten ADT specifications. We thus used all of the Veracity benchmarks to test the di&#8629;erent configurations, as mentioned before and shown in Table <ref type="table">1</ref>.</p><p>New non-trivial benchmarks were manually translated into the Veracity programming language. These were various combinations of functions from the SmartContract/Auction, Solidity/StandardToken, and Solidity/PullPayment source codes. Most of these new benchmarks perform better on the new heuristic poke2 compared to the previously presented approach poke. Also, for several of them, poke did not terminate, so we had to use the early termination feature to synthesize the commutativity condition within a specific time frame. For StandardToken, for example, after executing TransferForm ./ Approve with using poke and 120s timeout, we get an incomplete condition; however, with poke2, we can get a complete condition in about 10 seconds with a reasonable number of atoms.</p></div>
<div xmlns="http://www.tei-c.org/ns/1.0"><head n="7">Conclusion and Future Work</head><p>We have shown a more mature and performant method of automatically synthesizing commutativity conditions in the Servois2 synthesizer. Our results confirm what one might expect: that more advanced heuristics and better treatment of predicates leads to overall performance improvement. Furthermore, we have released a far more usable tool that has already been used in recent work <ref type="bibr">[6]</ref> and is ready to be integrated into other commutativity settings such as proof methodologies <ref type="bibr">[1,</ref><ref type="bibr">3,</ref><ref type="bibr">2]</ref> or distributed systems <ref type="bibr">[9,</ref><ref type="bibr">11]</ref>. There are several directions for future work in this space, discussed below. Algorithmic improvements. We saw great improvements in the performance of the heuristic in keeping track of which predicates aligned with the commutative (resp. non-commutative) case. The algorithm is currently agnostic to which condition is being pursued, and it may be possible to tag such information in the recursive calls, leading to similar improvements in performance. Furthermore, the disjunctive nature of the algorithm may be amenable to parallelization. However, it is unclear whether the actual reasoning is amenable to parallelization or if it is not worth the overhead.</p><p>Extended use of model counting. mcMax uses the model-counting solver ABC <ref type="bibr">[23]</ref>, which targets string, LIA, and boolean constraints, but we could also use other model counters with support for other theories. Approximate model counters <ref type="bibr">[24]</ref> are a promising avenue for handling model-counting queries across additional theories, and the integration of such a model counter might lead to further applicability of mcMax.</p><p>The mcMax heuristic provides one model counting heuristic to inform predicate selection, but we hypothesize that additional heuristics might provide advantages on di&#8629;erent benchmarks, for example, by maximizing partitioning rather than covering. Given the promising results of mcMax, we plan to pursue a more extensive evaluation of model-counting heuristics.</p><p>Model counting might find an additional use in cases where our commutativity analysis terminates early. Using model counting, we can determine what portion of the input domain is covered by the resulting commutativity and noncommutativity conditions, augmenting our analysis with additional reliability information in cases of early termination. It also may be possible to use this information to determine when to terminate.</p><p>Improving the use of the predicate lattice. Our experiments indicate that the overhead of lattice construction is significant. Thus for the lattice to be practical, one would need to both increase its performance benefit and decrease the overhead from construction. Although Refine prunes predicates based on the lattice, none of the current heuristics use information about implication chains, and there may be even more gains to be had by using lattices. There are also more sophisticated approaches to building the lattice data structure of logical implications, such as by using the framework GreenTrie <ref type="bibr">[25]</ref>. The number of queries can be greatly reduced through semantic reasoning and caching of subformulas. This could greatly reduce the overhead of lattice construction, thus making their use more appealing.</p></div><note xmlns="http://www.tei-c.org/ns/1.0" place="foot" xml:id="foot_0"><p>&#8224; &#8224; http://www.veracity-lang.org</p></note>
		</body>
		</text>
</TEI>
