<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>Visualization of Execution Traces in the Colibri 2 SMT Solver</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Christophe Junke</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>François Bobot</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Université Paris-Saclay, CEA, List</institution>
          ,
          <addr-line>F-91120, Palaiseau</addr-line>
          ,
          <country country="FR">France</country>
        </aff>
      </contrib-group>
      <pub-date>
        <year>2025</year>
      </pub-date>
      <fpage>126</fpage>
      <lpage>135</lpage>
      <abstract>
        <p>SMT solvers are complex tools that can be hard to debug. We think that there is a need to add a layer of observability to gain a better understanding of how they work. We present our recent addition to Colibri2 to add structured traces in the TEF format, and its associated Vite/ReactJS vizualization front-end. We present the motivation behind this, the current design and some preliminary results. PCWrEooUrckResehdoinpgs ISSNc1e6u1r-3w-0s0.o7r3g</p>
      </abstract>
      <kwd-group>
        <kwd>eol&gt;SMT</kwd>
        <kwd>debugging</kwd>
        <kwd>observability</kwd>
        <kwd>user interface</kwd>
        <kwd>vizualization</kwd>
        <kwd>logging</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>
        Unlike verification tools, SMT solvers are usually not designed to be interactive. Instead, they are used
as backend tools to which proofs can be ofloaded (Z3, Alt-Ergo as engines, versus Why3, Frama-C,
Spark environments). As such, SMT solvers may ofer a programmatic interface (such as Smt-Switch [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ])
or a command-line interface, where typically inputs and outputs are expressed in the SMT-LIB language
(in a terminal, this ofers a simple REPL behavior).
      </p>
      <p>From the point of view of both users and developers, it is important to build and maintain an
understanding of how a solver works. This may be challenging due to the complex nature of this kind
of software.</p>
      <p>Colibri21 is an SMT solver written in OCaml. For debugging purposes it can emit log messages
depending on the flags given to it in its options. For example it is possible to observe only the messages
coming from a specific theory. This and the usual text filtering tools available to programmers is the
main way we can analyze the behavior of the solver. Colibri2 also keep tracks of some internal metrics,
emitted at the end of its execution.</p>
      <p>Using logging for debugging and profiling has its limitations: it is hard to isolate how a particular
node evolves in the system, given that it may be tied to various identifiers over time (union-find merge
operations), and it is hard to vizualize some dynamic aspects of the solver, like the time spent in various
parts of the system (e.g. to produce flamegraphs).</p>
      <p>
        Complex systems like distributed large-scale web servers rely on continuous observation of their
components. We develop a prototype user-interface for Colibri2 which relies on the structured tracing
format TEF (Trace Event Format) from Google’s Catapult Trace Viewer [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ]. The program emits events
that make up a trace file in JSON. This file is a summary of the program execution. It is intended to
record all the important data from the program for a later analysis. For this reason it is also a useful
artefact for users who want to report bugs.
      </p>
      <p>Compared to a graphical interface that would be integrated in the solver, this approach is also less
intrusive: any part of the solver where we need to keep track of some internal data can do it by emitting
trace events. In return, this requires downstream tools to be able to interpret some events. The TEF
format is such that that all events can be displayed in a generic way, but there is a reconstruction step
that is necessary, for example, to take care of domain updates of nodes to build a graph of changes over
time.</p>
      <sec id="sec-1-1">
        <title>1.1. Related Work</title>
        <p>
          There are existing eforts to add graphical user interfaces to SMT solvers. For example, AltGr-Ergo [
          <xref ref-type="bibr" rid="ref3">3</xref>
          ]
is a GUI that displays internal information about Alt-Ergo (unsat cores, time spent in various
computations, etc.) and ofers more control, interactively, than the SMT-LIB format (sessions, selection
mechanisms). The intent is to provide feedback about the internal state of the solver.
        </p>
        <p>The Axiom Profiler [ 4] is an interface that analyzes log outputs from Z3 to focus on debugging
quantifier instantiations (in particular, matching loops). Z3Hydrant [ 5] is a tool that converts detailed
log files from Z3 to sound, by relying on the human ability to pick out patterns. These tools rely on
the tracing format of Z3, which can be emitted in an extension of SMT-LIB2. Likewise, CVC5 emits
diagnostic outputs in SMT-LIB2 and statistics in a separate format [6].</p>
      </sec>
      <sec id="sec-1-2">
        <title>1.2. Outline</title>
      </sec>
    </sec>
    <sec id="sec-2">
      <title>2. Tracing</title>
      <p>The rest of the paper is organized around two main parts. The first one focuses on the trace generation
side, and in particular how we encode specific information from our tool using a general-purpose trace
format. The second part in section 3 presents the general architecture of the graphical user-interface
and the motivation behind it.</p>
      <p>For the purpose of understanding microservices and distributed systems, several tools have been
developed in the last years around the concept of observability to help collect and analyze traces from
systems [7]. We use the ocaml-trace2 library to generate traces in the Trace Event Format, abbreviated
TEF, from Google’s Catapult Trace Viewer tool.</p>
      <p>This approach requires to modify the existing code to emit traces at various points of the system,
which is relatively easy to do. In return, the use of a publicly available format allows us to directly
open the trace in existing tools, for example the Perfetto UI3, as shown in figure 1. The TEF format
can describe concepts such as objects or events spanning multiple processes and threads in distributed
systems. We do not need the full range of events available in the language for our tool, and instead
limit ourselves to a subset consisting of counter events, instant events and duration events4.</p>
      <sec id="sec-2-1">
        <title>2https://github.com/c-cube/ocaml-trace</title>
        <p>3https://ui.perfetto.dev
4duration events, or spans, are described in our tool by a pair of begin/end events for the same key; by construction they nests
according to function calls in our code, so that children spans are always contained in parent spans.</p>
        <p>We emit messages that help us keep track of various changes inside our solver, which means that our
events carry a meaning that is not directly interpretable by generic tracing tools.</p>
        <sec id="sec-2-1-1">
          <title>2.1. Union-find node identifiers</title>
          <p>We use a union-find datastructure [ 8, 9] to maintain a compact representation of terms in our solver.
Each node in the graph is associated with an identifier, and merge operations can make two nodes
equivalents. We generate trace events each time a new node is registered in the system, in order to
track the identifier and its associated term. Likewise, node merges and domain updates of nodes are
also traced.</p>
          <p>When we need to print a node in a trace event, we print instead its identifier. In our user interface,
we want to extract all the identifiers referenced in a trace payload to be able to show more information
about each node (e.g. its current domain, or the last time it was modified, etc.)</p>
          <p>Identifiers are prefixed by an at character: @1, @2, etc. The patterns of identifiers is however not
regular, because some nodes use other names that give more information when debugging, like identifiers
for numerical and logical constants: @0R, @⊥, etc. Moreover, these identifiers may be pretty-printed
in contexts where they can be ambiguous to parse back, like in domain values, which are currently
only represented as text and not structured values. Unless developers are really careful about it, a
pretty-printer may introduce characters that render the parsing ambiguous. For example, given an
arbitrary domain value for which the printed representation is @0R, we cannot tell if the string represents
identifier 0 followed by R or identifier 0R.</p>
          <p>In order to extract these identifiers from events, we changed our pretty-printer for identifiers to add
semantic tags [10] around them, and we change our formatters in the context of a trace event so that
identifiers are surrounded by the non-printable characters STX and ETX (resp. ASCII codes 2 and 3).</p>
          <p>This is the current workaround to be able to correctly parse node identifiers from messages that
rely on our already existing pretty-printers. An alternative approach would be to ensure that each
datastructure we want to trace in our solver has an associated JSON representation. This is currently
not the case and if we do so we will also have to encode these JSON representations inside our events.
In that case it may be interesting to rely on object events as defined by TEF (ie. memory addresses for
which we store snapshots at various points of time): contrary to instant messages which only allow
primitive values, snapshots of objects may be arbitrary JSON values.</p>
        </sec>
        <sec id="sec-2-1-2">
          <title>2.2. Scopes</title>
          <p>Our SMT solver undergoes diferent phases of computation over the course of its execution. Some
are captured with with_span constructs from ocaml-trace, which encapsulate a function call by
emitting a duration event trace. Other scopes are handled explicitly, because they do not directly fit the
extent of a function call. For example, multiple node merge operations can be executed in the same
phase, delimited by start_merge and finalize_merge events.</p>
        </sec>
        <sec id="sec-2-1-3">
          <title>2.3. Breadcrumbs</title>
          <p>The Colibri2 solver is organized around a central scheduler that manages multiple queues of events.
Each subsystem can register callbacks to be executed when some future internal event is fired (e.g.
domain change of a given node). Between the time that this subsystem decides to delay an action and
the time that action is executed, the scheduler may have interleaved a number of other tasks.</p>
          <p>For debugging purposes we need to keep track of the context of the delayed action. There is some
information that each module may decide to add in its traces, but we would like to have a generic
mechanism to explain at minimum when the task being currently run was originally delayed.</p>
          <p>The approach we implemented consists in emitting a couple of events when an event enters a queue
and when it leaves it. They are respectively named breadcrumbs_push and breadcrumb_pop, and
requires two parameters: a unique identifier for the current breadcrumb, and the name of the queue.
SMT 2025: Satisfiability Modulo Theories 126–135</p>
          <p>For example, in the following simplified trace, two nodes are merged, and the corresponding code
delays two actions in the merge_dom queue. This is represented by the two breadcumb_push events
that follow start_merge:
{"name":"start_merge","args": {"other":"@42","repr":"@0"}},
{"name":"breadcrumb_push","args": {"id":1,"queue":"merge_dom"}},
{"name":"breadcrumb_push","args": {"id":2,"queue":"merge_dom"}},</p>
          <p>Later, we can see that there is a breadcrumb_pop operation that retrieves the action whose id is 1
from the same queue. This event is followed by the set_dom event:
{"name":"breadcrumb_pop","args": {"id":1,"queue":"merge_dom"}},
{"name":"set_dom","args": {"dom":"Ground.tys","node":"@0","val":"Prop,"}},</p>
          <p>This sequence of events is enough to visually tie set_dom to start_merge in the interface by
adding bidirectional links between their rows.</p>
        </sec>
        <sec id="sec-2-1-4">
          <title>2.4. Counters</title>
          <p>We maintain a set of counters in the solver, for which every change of value is traced. Notable counters
are the step counter, which marks the increasing tick at which the scheduler is being run. We also
keep track of the internal time spent in various sections of the code. Currently we make no use of these
counters in our interface but they are already visible in the Perfetto UI.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>3. Graphical interface</title>
      <p>The trace viewer is a web interface based on ReactJS5 and the Vite build tool6. It displays a trace as a
table, where each row corresponds to an event.</p>
      <sec id="sec-3-1">
        <title>3.1. Design goals</title>
        <p>One of our objectives with the interface is to be robust to changes in the trace format in face of updates
to the software. The reason for this is that there might be traces generated by older or newer versions
of the solver, which are not necessarily tied to the version of the interface (e.g. between developers or
between users and developers). For example, by default we want to be able to display any trace event in
a generic way, without entering an error state.</p>
        <p>The second main objective is to be modular enough that adding support for particular type of events
can be done by adding one specific module that attaches itself to various parts of the interface. We
currently support adding independant side-panels from such modules.</p>
      </sec>
      <sec id="sec-3-2">
        <title>3.2. Architecture</title>
        <p>There are three main phases that occur when loading a trace file. The first one is a preprocessing step
where all events are filtered, transformed and aggregated into an in-memory graph. The second phase
is our rendering step, where the previous graph is used to assemble UI elements and event handlers
in structures called properties. Finally, the third step is the React rendering step, where properties
are turned into reactive HTML DOM7 elements: typically, events generated through user interaction
eventually reach the event handlers generated at step 2, which compute a modified set of properties that
will be used to update the interface in-place. For example, hovering over a node identifier creates a new
popup window: all the necessary information for the popup was computed once during the filtering
pass, but during step 2 we generate components that react on mouse events to create or destroy popup
elements.</p>
        <p>The remaining of this section first looks at the rendering step and how row properties are represented.
Then we go backward to the preprocessing step to describe how the properties are generated by using
a pipeline of filters. We describe two general-purpose filters and finally present an example of how a
particular type of trace event is transformed and rendered in this architecture.
3.2.1. React properties for rows
In order to build and update DOM elements, the functional API of React expect the programmer to
define and instantiate Components, which are functions that transform properties, regular Javascript
objects, into React objects that represents DOM elements (in our cases, using JSX). By using React hooks,
the input properties can be updated in a way that React can observe and act upon, in order to update
the generated DOM elements.</p>
        <p>We focus here on properties called RowProps, which are properties that describe one row of the trace
view. Figure 2b shows the basic structure of one RowProps, which acts like a blueprint for the final
rendering of a table row. There are various fields like the central axis where we display either the
current step or the current timestamp, the main content field, and extensible areas on the left and the
right of the axis. It contains also various properties like a list of CSS classes attached to the row, possible
DOM event callbacks, etc.</p>
        <p>We rely on the data processing step to produce a sequence of RowProps values by successively
transforming events through filters, and finally rendering all the final events as RowProps. Figure 2a
describes this processing: initially the sequence of events is extracted from a stream of JSON entries
to produce a sequence of events. Later, the rendering phase produces, for each Event, the RowProps
instance required to render a Row component in React.</p>
        <p>From a performance perspective, we are careful about not re-rendering the whole table of rows. For
example, when the current row selection changes, the last row is unselected and the new one selected.
A naive approach would recompute the set of all row properties for the other rows.</p>
        <sec id="sec-3-2-1">
          <title>7Document Object Model</title>
          <p>(a) Transformation pipeline from JSON to</p>
          <p>Events through filters during data
processing, and rendering of events as
rowProps: each rowProp structure is first
initialized by all filters, then processed
by the event’s rendering method. The
ifnal structure is used to render a React
Component for this Row.</p>
          <p>(b) A RowProps structure represents the combination of
different parts of the final layout: all rows are aligned on a
central axis cell, a main cell which is a cell of one or more
components, and two extensible margins on the left and
right sides of the axis. Each filter can register a new index
in a margin, so that events that are emitted by this filter
can render some extra information there, without having
to coordinate with other filters.
3.2.2. Data preprocessing step
Events
Events are regular Javascript objects that are initially decoded from JSON as either CounterEvent or
GenericEvent objects. During processing, they may be mutated or transformed into more specific
classes, but the rest of the API only expects them to have a render() method that produces a RowProps
value. The processing of trace events is architectured around the parsing of events through a series of
iflters executed in sequence.</p>
          <p>Filters
Filters are Javascript objects built from a constructor that accepts only one input, the shared environment
object. This object is where they can accumulate and share global data. We rely on the immutable.js
library to use purely-functional datastructures, because events need to have access to diferent versions
of this state over time. We also define a basic registration mechanism to document and check how
resources are shared through filters. Filters also contribute to the rendering of row properties and
possibly other parts of the interface. For example, the environment allows a filter to add a custom panel
on the right-side of the screen, making filters the main extension point of the architecture.</p>
          <p>Filters are analogous to POSIX pipes: each filter may produce zero, one or more events for the next
iflter in the pipeline using its step function, and may update its internal state while doing so. This
is done only for events that match a test predicate (otherwise the event bypasses the current filter
unmodified).</p>
          <p>For example, message events in JSON are transformed into objects of type GenericEvent. Below
we will see that counter events named step are transformed in instances of a StepEvent class. In
object-oriented fashion, specialized objects have dedicated methods to be rendered as UI components,
whereas GenericEvent objects only render themselves as a table of key/value pairs.</p>
          <p>Each filter has also the opportunity to initially produce a set of events from nothing ( init method).
Conversely, when there is no more inputs, a filter can also purge its internal state by emitting a last
sequence of events (done method): for example, it is possible to sort all events inside a filter, by
accumulating all incoming events internally and emitting them in a diferent order.</p>
          <p>Finally, to accomodate for the fact that filters may have an internal state to manage, they may also
define reset and exit methods, which are only used for side-efects at the beginning and end of the
processing.</p>
          <p>During the rendering phase, filters contribute to each row by initializing the current row, using
initRowProps. By default it is the identity function, but in some cases the filter can decorate rows
with custom values.</p>
          <p>Filters need not define all the above functions, all of them are optional because we first normalize
iflters to give them default behaviors:
return {
...filter,
env : filter.env || env,
test : filter.test || ((t) =&gt; true),
init : filter.init || (( ) =&gt; []),
reset : filter.reset || (( ) =&gt; undefined),
exit : filter.exit || (( ) =&gt; undefined),
step : filter.step || ((t) =&gt; [t]),
done : filter.done || (( ) =&gt; []),
initRowProps : filter.initRowProps || ((l) =&gt; l)
}</p>
          <p>The main pipeline of our tool consists currently of 14 filters, which focus on distinct types of events
or processing. By doing manual tests we try to deactivate or reorder them to identify how much they
depend on each other, and if so, we try to reduce this coupling when possible.
3.2.3. Composite filters
We define two high-level filters that work an an arbitrary array of filters, namely
Chain and Select.</p>
          <p>Chain The chain filter enforces the lifecycle described above when a series of filters are applied in
sequence. For example, the init method of the Chain filter iterates over all child filters: each filter 
may produce events from its init method, and they must be first given to the test and step functions
of the next filters +1, +2, etc. Only once these events a processed, we may call the init method of
+1. Another detail is that exit methods are called in the reverse order of filters, to correctly undo
previous calls to reset with respect to resources shared among filters.</p>
          <p>Select The selection filter redirects events to exactly one filter among an array of filters: its constructor
takes as a parameter a selector function which is called on Configuration events. When such an event
is observed, the selection filter may deactivate the current filter and activate a diferent one. This is
used to allow the definition of separate branches of filters based on the trace being read. The intended
usage of this filter is to make it possible to handle diferent versions of the trace format, or to support
diferent type of traces (generated by diferent tools). This filter takes care of properly cleaning the filter
that is being deactivated (by calling done and exit methods), and reinitializes the new active filter (by
calling reset and init).
3.2.4. StepEvent example</p>
          <p>The code in figure 4 is the definition of the Stepper constructor function, which produces a filter
that observes the step counter emitted by Colibri2 to produce intermediate StepEvent objects in
the trace.
/* Transform rowProps to display event */
function render(event, rowProps) {
return {
/* inherit other fields */
...rowProps,
/* add a CSS class to this row */
classes: rowProps.classes.concat(["step"]),
/* override the generic Axis component (timestamp) and show the step */
axis: &lt;span&gt;{`step ${event.step}`}&lt;/span&gt;
}
/* list of rendering functions applied like
a fold; we override the generic event
renderer by using the function above */
render: [render],
}
};
step: step,
origin: origin
(a) Constructor for StepEvent events and the
associated rendering function. Rendering is done
by filling a structure representing a row that is
iself the basis from which a Row component is
rendered in React.
(b) Rendering of a test trace where step counter
events have been transformed into StepEvent
objects.</p>
          <p>The filter’s step function is only called for events that match the test condition, namely counter
events for which the associated name its step.</p>
          <p>In that case, it produces two events: (i) the original event e (which may be useful for downstream
iflters), and (ii) a StepEvent object8. These events are used to visually distinguish each scheduler step
over time, as shown in figure 3b, but also keep tracks of the last known step value. The shared
environment env defines a mechanism to register and request access to shared state variables between
iflters: this establishes a dependency between filters, forcing filters that depends on StepperState to
be executed after the one depicted here.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>4. Conclusion</title>
      <p>We presented a prototype implementation of a user-interface organized around interpreting traces of
events emitted from our SMT solver Colibri2. The architecture of this interface can be adapted to
accomodate other types of traces. The addition of event traces to a system feels less intrusive and costly
than adding a graphical user-interface to an existing system and can help understand the behavior of
a complex system. The chosen trace format was selected for its ability to be already understood by
existing tools.</p>
      <p>The filtering mechanism of the user-interface is designed to be modular and robust to changes. It can
8the origin field is used to remember the source JSON object that is responsible for this event.
import {and, isTagged, keyMatch} from '@/traces/FilterUtils';
import CounterEvent from '@events/CounterEvent.tsx';
import StepEvent from '@events/StepEvent.tsx';
export default function Filter (env) {
var state
return {
description: "Inject step-changed events",
test: and(isTagged("counter"), keyMatch("name", "step")),
init: () =&gt; [ CounterEvent("step", 0), StepEvent(null, 0) ],
reset: () =&gt; {
state = env.register("StepperState")
state.set("lastStep", 0)
},
exit: () =&gt; state.unregister(),
step: (e) =&gt; {
state.set("lastStep", e.value)
return [e, StepEvent(e.origin, e.value)];
}
}</p>
      <p>}
be used to analyse and process complex streams of events. For example, a current limitation is that the
user interface renders the whole trace file, which can be impractical for large traces. We may however
add a tool that splits traces into smaller traces, where each one starts with a header of events that build
the required contextual state for each trace (e.g. reset node variable to their last known domains).</p>
      <p>We currently use event traces to perform an ofline analysis of the solver behavior, but this format
does leave open the possibility of having a direct communication between our solver and a user-interface.
To close the interaction loop we would however need to handle external events from our scheduler.</p>
      <p>The use of web technology like React for the user-interface feels also beneficial in the context of this
tool, as it allows for complex dynamic interactions with the data that is being analyzed while relying on
a software platform that is now ubiquitous. A possible perspective in our case would be for example to
rely on the generated trace to display the union-find graph over time, the evolution of variable domains
using plots, or some internal state like simplex boundaries.</p>
    </sec>
    <sec id="sec-5">
      <title>Declaration on Generative AI</title>
      <sec id="sec-5-1">
        <title>The author(s) have not employed any Generative AI tools.</title>
        <p>for Theorem Provers, UITP 2016, Coimbra, Portugal, 2nd July 2016, volume 239 of EPTCS, 2016, pp.
1–13. URL: https://doi.org/10.4204/EPTCS.239.1. doi:10.4204/EPTCS.239.1.
[4] N. Becker, P. Müller, A. J. Summers, The axiom profiler: Understanding and debugging smt
quantifier instantiations, in: T. Vojnar, L. Zhang (Eds.), Tools and Algorithms for the Construction
and Analysis of Systems, Springer International Publishing, Cham, 2019, pp. 99–116.
[5] F. Hackett, I. Beschastnikh, Listening to the firehose: Sonifying z3’s behavior (????).
[6] A. Reynolds, Interfaces for understanding cvc5, 2024. URL: https://cvc5.github.io/blog/2024/04/15/
interfaces-for-understanding-cvc5.html.
[7] A. Janes, X. Li, V. Lenarduzzi, Open tracing tools: Overview and critical comparison, Journal of
Systems and Software 204 (2023) 111793. URL: https://www.sciencedirect.com/science/article/pii/
S0164121223001887. doi:https://doi.org/10.1016/j.jss.2023.111793.
[8] C. G. Nelson, Techniques for program verification, Ph.D. thesis, Stanford, CA, USA, 1980.</p>
        <p>AAI8011683.
[9] L. de Moura, N. Bjørner, Eficient e-matching for smt solvers, in: F. Pfenning (Ed.), Automated</p>
        <p>Deduction – CADE-21, Springer Berlin Heidelberg, Berlin, Heidelberg, 2007, pp. 183–198.
[10] R. Bonichon, P. Weis, Format Unraveled, in: 28ièmes Journées Francophones des Langages
Applicatifs, Gourette, France, 2017. URL: https://hal.science/hal-01503081.</p>
      </sec>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>M.</given-names>
            <surname>Mann</surname>
          </string-name>
          ,
          <string-name>
            <surname>A</surname>
          </string-name>
          . Wilson,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Zohar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>L.</given-names>
            <surname>Stuntz</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Irfan</surname>
          </string-name>
          ,
          <string-name>
            <given-names>K.</given-names>
            <surname>Brown</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Donovick</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Guman</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Tinelli</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Barrett</surname>
          </string-name>
          ,
          <article-title>Smt-switch: A solver-agnostic C++ API for SMT solving</article-title>
          , in: C.
          <string-name>
            <surname>-M. Li</surname>
            ,
            <given-names>F.</given-names>
          </string-name>
          <string-name>
            <surname>Manyà</surname>
          </string-name>
          (Eds.),
          <source>Proceedings of the 24ℎ International Conference on Theory and Applications of Satisfiability Testing (SAT '21)</source>
          , volume
          <volume>12831</volume>
          of Lecture Notes in Computer Science, Springer,
          <year>2021</year>
          , pp.
          <fpage>377</fpage>
          -
          <lpage>386</lpage>
          . URL: http://theory.stanford.edu/~barrett/pubs/MWZ+21.pdf. doi:
          <volume>10</volume>
          .1007/978-3-
          <fpage>030</fpage>
          -80223-3_
          <fpage>26</fpage>
          , barcelona, Spain.
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <surname>Google</surname>
          </string-name>
          , Catapult trace viewer,
          <year>2015</year>
          . URL: https://chromium.googlesource.com/catapult/+/HEAD/ tracing/README.md.
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>S.</given-names>
            <surname>Conchon</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Iguernelala</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Mebsout</surname>
          </string-name>
          ,
          <article-title>Altgr-ergo, a graphical user interface for the SMT solver alt-ergo</article-title>
          , in: S. Autexier, P. Quaresma (Eds.),
          <source>Proceedings of the 12th Workshop on User Interfaces</source>
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>