Synthesizing Control-Flow Graph Generators from Operational Semantics

ion and Projection and Machines. Our first insight is to transform the operational semantics into another style of semantics, the abstract machine, via a new algorithm. Evaluating a program under these semantics generates an infinite-state transition system with recognizable control-flow. Typically at this point, an analysis-designer would manually specify some kind of abstract semantics which collapses this system into a finite-state one. Our approach does this automatically by interpreting the concrete semantics differently, using an obscure technique called abstract rewriting. From this reduced transition system, a familiar structure emerges: we have obtained our control-flow graphs! Now all three variants of control-flow graph given in this section follow from different abstractions of the abstract machine, followed by an optional projection, or merging of nodes. With this approach, we can finally give a formal, proven correspondence between the operational semantics and all such variants of a control-flow graph. Mandate: A CFG-Generator Generator. We have implemented our approach inMandate, the first control-flow-graph generator generator.Mandate takes as input the operational semantics for a language, expressed in an embedded Haskell DSL that can express a large class of systems, along with a choice of abstraction and projection functions. It then outputs a control-flow-graph generator for that language. By varying the abstraction and projection functions, the user can generate any of a large number of CFG-generators. Mandate has two modes. In the interpreted mode, Mandate abstractly executes a program with its language’s semantics to produce a CFG. In cases where the control-flow of a node is independent , Vol. 1, No. 1, Article . Publication date: November 2019. Synthesizing Control-Flow Graph Generators from Operational Semantics 3 SOS PAM AM Abstracted Program CFG (Interpreted mode) Program Source Per program Per language Graph patterns Syntax-directed CFG Generator Per language (sometimes) CFG (Compiled mode) Fig. 3. Dataflow of our approach from its context (e.g.: including variants (1) and (2) but not (3)). In compiled mode,Mandate outputs a CFG-generator as a short program, similar to what a human would write. We have evaluated Mandate on several small languages, as well as two larger ones. The first is Tiger, an Algol-family language used in many university compilers courses, and made famous by a textbook of Appel [1998]. The second, BaliScript1, is a JavaScript-like language with objects and higher-order functions used in an undergraduate JIT-compilers course. In summary, our work makes the following contributions: (1) An elegant new algorithm for converting small-step structural operational semantics into abstract machines. (2) A new approach using abstract rewriting to derive other artifacts from language semantics. (3) A method of deriving many types of control-flow graph generator from an abstract machine, determined by choice of abstraction and projection functions, including standalone generators which execute without reference to the semantics (“compiled mode"). (4) A formal and proven correspondence between the operational semantics and many common variations of control-flow graphs (5) The first CFG-generator generator, Mandate, able to automatically derive many types of control-flow-graph generator for a language from an operational semantics for that language, and successfully used on two realistic languages. 2 CONTROL-FLOW GRAPHS FOR IMP We shall walk through a simple example of generating a control-flow graph generator, using a simple imperative language called IMP. IMP features conditionals, loops, mutable state, and two binary operators. The syntax is in Fig. 4. In this syntax, we explicitly split terms into values and non-values to make the rules easier to write. We will do this more systematically in §3.2. Variables x ,y, . ∈ Var Expr. Values v ::= n ∈ Int | true | false Expressions e ::= v | x | e + e | e < e Stmt. Values w ::= skip Statements s ::= w | s; s | while e do s | x := e | if e then s else s Fig. 4. Syntax of IMP The approach proceeds in three phases, corresponding roughly to the large boxes in Fig. 3. In the first phase (left box / §2.1), we transform the semantics of IMP into a form that reveals the control flow. In the second phase (right box / §2.2), we show how to interpret these semantics with abstract rewriting [Bert et al. 1993] to obtain CFGs. In the finale (middle box / §2.3), we show how to use abstract rewriting to discover facts about all IMP programs, resulting in human-readable code for a CFG-generator. 2.1 Getting Control of the Semantics Semantics. The language has standard semantics, so we only show a few of the rules, given as structural operational semantics (SOS). Each rule relates an old term and environment (t , μ) to a 1This name has been changed for double-blinding reasons , Vol. 1, No. 1, Article . Publication date: November 2019. 4 James Koppel, Jackson Kearl, and Armando Solar-Lezama new one (t ′, μ ′) following one step of execution. We focus on the rules for assignment, as they modify environments; we will later consider while-loops (which imply back-edges). (e, μ) { (e ′, μ ′) (x := e, μ) { (x := e ′, μ ′) AssnConд (x := v, μ) { (skip, μ[x → v]) AssnEval These rules give the basic mechanism to evaluate a program, but don’t have the form of stepping from one subterm to another, as a control-flow graph would. So, from these rules, our algorithm will automatically generate an abstract machine. The abstract machine acts on states 〈 (t , μ) K〉. K is the context or continuation, and represents what the rest of the program plans to do with the result of evaluating t . K is composed of a stack of frames. While the general notion of frames is slightly more complicated (§3.3), in most cases, a frame can be written as e.g.: [ (x := □t ,□μ ) ] , which is a frame indicating that, once the preceding computation has produced a term and environment (t ′, μ ′), the next step will be to evaluate (x := t ′, μ ′). Our algorithm generates the following rules. These match the textbook treatment of abstract machines [Felleisen et al. 2009]. 〈 (x := e, μ) k〉 → 〈(e, μ) k ◦ [(x := □t ,□μ )]〉 〈 (v, μ) k ◦ [(x := □t ,□μ )]〉 → 〈(skip, μ[x → v]) k〉 The first abstract machine rule, on seeing an assignment x := e , will focus on e . Other rules, not shown, will then reduce e to a value. The second then takes this value and evaluates the assignment. While these rules have been previously hand-created, this work is the first to derive them automatically from structural operational semantics. These two SOS rules become two abstractmachine rules. A naive interpretation is that the first SOS rule corresponds to the first abstractmachine rule, and likewise for the second. After all, the left-hand sides of the first rules match, as do the right-hand sides of the second. But notice how the right-hand sides of the firsts do not match, nor do the left-hand sides of the seconds. The actual story is more complicated. This diagram gives the real correspondence: (e, μ) (e′, μ′) (x := e, μ) (x := e′, μ′) 1 (x := v, μ) (skip, μ[x → v])