## Aquarium: Cassiopea and Alewife Languages

(Prerelease Version of 20220108)

David A. Holland Harvard University dholland@eecs.harvard.edu

Eric Lu Harvard University ericlu01@g.harvard.edu

Jingmei Hu Harvard University jingmei\_hu@g.harvard.edu

Ming Kawaguchi Harvard University ming@seas.harvard.edu

Stephen Chong Harvard University chong@seas.harvard.edu

## Abstract

This technical report describes two of the domain specific languages used in the Aquarium kernel code synthesis project. It presents the language cores in terms of abstract syntax. Cassiopea is a machine description language for describing the semantics of processor instruction sets. Alewife is a specification language that can be used to write machineindependent specifications for assembly-level instruction blocks. An Alewife specification can be used to verify and synthesize code for any machine described in Cassiopea, given a machine-specific translation for abstractions used in the specification. This article does not include an introduction to either the Aquarium system or the use of the languages. In addition to this version of the article being a draft, the Aquarium project and the languages are work in progress. This article cannot currently be considered either final or complete.

## 1 Introduction

The goal of the Aquarium project is to synthesize the machine-dependent parts of an operating system. This has the potential to greatly reduce the amount of work needed to port an operating system to a new machine architecture. It also potentially reduces the depth of knowledge needed to do that work - currently an OS port requires deep expert knowledge of both the OS and the new machine architecture - and with luck the overall amount of time involved as well.

This document does not discuss either code synthesis or OS porting in any detail. It is intended as a supplement to other project publications, which should in general be read first. While this document includes discussions of and rationales for language features (as well as absence of language features) these discussions assume familiarity with the surrounding context.

The two languages described in this document are Cassiopea 1 , which is a register-transfer-list-style machine description language used for writing down the semantics of

1 Cassiopea is named for a jelly(fish) that features symbiotic photosynthetic algae, which is for some reason spelled without the customary 'i'.

Margo I. Seltzer University of British Columbia mseltzer@cs.ubc.ca processor instruction sets, and Alewife, which is a specification or modeling language used for writing down machineindependent specifications for assembly language code blocks to be synthesized.

Cassiopea also includes material for writing machinedependent specifications (this is the target for the Alewife translator), a concept of mapping modules used by the Alewife translator, and a simple representation of basic blocks in assembly language. These are described in this document.

This document does not, however, describe any of the other languages or file formats used in the Aquarium project or any of the Aquarium tools.

The document is structured with six parts:

- · The Cassiopea language and abstract syntax (Sec. 3)
- · Cassiopea types (Section 4)
- · Cassiopea semantics (Section 5)
- · The Alewife language and abstract syntax (Section 6)
- · Alewife types and semantics (Section 7)
- · Alewife translation to Cassiopea (Section 8)

## 2 Notation

In the abstract syntax, type judgments, and semantics judgments we use italics for metavariables (e.g., 𝑣 ) and for words corresponding to types in the abstract syntax (e.g., declaration ). We use typewriter font for words that correspond to language keywords. The notation 𝛼 𝑖 means 'a sequence of one or more 𝛼 , each to be referred to elsewhere as 𝛼 𝑖 '. If there are no references outside the overbar, the 𝑖 subscript may be left off. Epsilon ( 𝜖 ) appearing in syntax represents an empty production. The notation " . . . " represents a string literal with arbitrary contents.

Bitvectors (machine integers) may be any width greater than zero. Bitvector constants are represented as 0b C , which can be thought of as an explicit sequence of zeros and ones. Thenumberofbits in a bitvector constant (that is, the number of digits) gives its type. Thus, 0b 00 and 0b 0000 are different. In the concrete syntax, bitvector constants whose size is a multiple of 4 can also be written in the form 0x C . These are desugared in the parser and not shown further in this document.

The Cassiopea and Alewife syntax should be considered disjoint. Some elements are the same in each, but these are specified separately regardless. They use the same metavariables as well, which should not be mixed; any language construct in a judgment should be all Cassiopea or all Alewife. In a few places mixing is needed, in which case the translation defined in Section 8 is applied to allow inserting Alewife fragments into Cassiopea terms. The Alewife rules in Section 7 do use the same environments as the Cassiopea rules. These should be construed as holding only Cassiopea elements. Further details can be found in Section 7.

## 3 Cassiopea Overview

This section covers the abstract syntax for Cassiopea.

Cassiopea is a register-transfer-list style language: it models instructions as non-Turing-complete procedures that update a machine state. Its executable semantics are covered in Section 5.

We model the machine from the assembly-language programmer's perspective. In particular, we do not treat memory as a huge block of address space but instead treat it in small chunks passed in from somewhere else. We model both control registers and general-purpose registers as well as other machine state such as whether interrupts are enabled.

Furthermore we must allow assembler labels, which have addresses, but those addresses are not resolved until after programs are compiled and linked and must be treated as abstract.

Notation. We use the following metavariables:

x , y , z Program variables (binders)

r

Registers (abstract)

C

Integer constants (written in decimal)

0b C Bitvector constants (written in binary)

𝜏 Types

v Values

e Expressions

S Statements

i , j Rule-level integers

(Other constructions are referred to with longer names.)

A number of constructions are lists written out in longhand (with a null case and a cons case) - these are written out in longhand so that typing and semantic judgments can be applied explicitly to each case, in order to, for example, propaate environment updates correctly.

Identifiers and Variables. Identifiers are divided syntactically into seven categories:

- · xmem are identifiers bound to memory regions, which are second-class.
- · xfunc are identifiers bound to functions, which are second-class.
- · xproc are identifiers bound to procedures, which are second-class.
- · xop are identifiers bound to instructions ('operations'), which are akin to procedures but distinguished from them.
- · x 𝜏 are identifiers for type aliases, which are bound to base types in declarations.
- · xmodule are the names of 'modules', which are used to select among many possible groups of lowering elements.
- · Other identifiers x are used for other things, and should be assumed to not range over the above elements.

Note that all identifiers live in the same namespace, and rebinding or shadowing them is not allowed. All these identifiers can be thought of as variables, in the sense that they are names that stand for other things. All of them are immutable once defined, including the ordinary variables x that contain plain values.

Types. Types are divided syntactically into base types (integers, booleans, strings, bitvectors, etc.) and others (memory regions and functions). User functions may handle only base types. Furthermore, memory regions and functions are second-class for reasons discussed below and are excluded in various places in the syntax and the typing rules.

We use index typing to capture the bit width of values.

Registers. Registers are represented in the specification with the metavariable 𝑟 , which stands for the underlying abstract identity of a register. Declaring a register, e.g., with letstate x : C reg as shown in Figure 2, allocates a fresh register 𝑟 and binds the variable x to it. A subsequent declaration of the form let y : C reg = x creates another variable y that refers to the same underlying register. One might think of registers as numbered internally. We use the form letstate control x : C reg to declare specific control registers, which are treated differently by the framing rules. The additional keyword dontgate inhibits state gating for the register; this should be used for flags registers and anything similar that is implicitly used by all ordinary code.

Some registers have associated with them a text form, which is declared separately and is the form an assembler expects to parse. The Capstan uses this to extract an asssembly program from aquarium's internal representation. It is referred to by attaching the suffix . txt to the/a register variable. As some registers are not directly addressable by the assembler (e.g., they might be subfields of some larger addressable unit or nonaddressable internal state), not all registers have a text form. This is not readily checked statically, at least not without introducing specific additional machinery, so invalid . txt references fail at assembly code extraction time.

The type of a register is C reg , which is a register that holds a C -bit bitvector. The bitvector value in question can be updated by assigning a new value; this is a statement ( e 1 : = e 2) and can only happen in places where statements are allowed. The construction ∗ e reads a register.

Figure 1. Cassiopea types, values, operators, and expressions.

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-8a72a930-db35-4e98-994e-9f5b28987e82.jpg)

The reader will note that the semantics rules for machines and declarations do not provide initial values for registers. Instead, executions are defined in terms of some initial register state (and also some memory state), which is required to have the right registers to match the machine definition. This allows reasoning about the execution of programs and program fragments in terms of many or all possible initial states. These issues are discussed further below.

regions are a property of programs (and thus are declared in specifications) and not a property of the machine as a whole.

Memory. A memory region has the type C 1 bit C 2 len C 3 ref , shown in Figure 1. This refers to a memory region that has C 2 cells, each of which stores a bitvector of width C 1. This memory region is addressed with pointers of width C 3. Note that we assume byte-addressed machines, and for the purposes of both this specification and our implementation, we assume bytes are 8 bits wide. (This restriction could be relaxed if we wanted to model various historic machines.) Thus a memory region of type 32 bit 4 len 32 ref has 4 32-bit values in it, which can be addressed at byte offsets 0, 4, 8, and 12. These values can of course be changed, like values in registers.

Memory regions are named with identifiers. These names, and memory regions themselves, are not first class; variables are not allowed to range over them. Also note that memory

Pointers. A pointer literal has the form ( xmem , C ) , in which xmem is the region name and C is the offset, shown in Figure 1. Because memory regions are second-class, xmem must be specifically one of the available declared memory regions. Pointer literals exist in the abstract syntax, but are not allowed in the concrete syntax except in specifications. The only way to get a pointer value is to look up a label (discussed immediately below) or have it provided in a register as part of the initial machine state.

Apointer literal is treated as a bitvector of the same width, so one can appear in a register or in memory. However, we enforce a restriction (not captured in the semantics rules so far) that no value in the initial machine state, whether in a register or in memory, is a pointer unless required to be so by the precondition part of the specification. All other values are restricted to be plain bitvector values.

Addition and subtraction are allowed on pointers and they change the offset, but other bitvector operations (e.g., multiply) are disallowed and fail. Similarly, attempting to fetch from or store to a plain bitvector that is not a pointer fails. Note however that we do not statically distinguish

pointers and plain bitvectors. (We could have used flowsensitive typing to reason about when registers and memory cells contain pointers and when they do not; but this adds substantial complexity and for our problem domain does not provide significant value.) Instead, we step to failure at runtime. This can be seen in the semantics rules.

Fetching from a pointer takes the form fetch ( e C , ). Storing to a pointer takes the form store ( e 1 , 𝐶 ) ← e 2. The extra constant C specifies the width of the cell pointed to. (This is not an offset.) Because we do not check pointers statically, we do not know the memory region being pointed to and cannot look up its cell size; thus we need the width explicitly for typing. It is checked at runtime.

Labels. As mentioned above, the term 'label' means an assembler label or linker symbol. These have addresses (or depending on how one looks at them, they are addresses) and those addresses are constants, but the constants are not known at assembly time, so we must model them abstractly.

When one declares a memory region, one may attach a label to it, which is an additional identifier. This identifier is created as a variable of type C label in Figure 1. The value is a pointer to the first entry in the region, and a single type subsumption rule allows this value to be accessed and placed in an ordinary register or variable of suitable bitvector type. The intended mechanism is that for each machine the preferred instruction on that machine for loading assembler symbols into a register can be defined to take an operand of type C label , and then its value can be assigned to the destination register. This type restriction on the operand is sufficient to synthesize programs that use labels correctly.

Code Positions and Branching. Cassiopea code blocks may contain branches, either forward within the block (branching backwards is forbidden) or to a single external assembler label outside the block. This model is sufficient for our block-oriented synthesis approach; more complex control flow can be handled with multiple blocks.

Consequently, a branch may either go to the external assembler label (which terminates execution of the current block) or skip zero or more instructions. We model branch targets as an 8-bit skip count. In Figure 2, The statement BRANCH 0b ( C ) skips 0b C instructions; BRANCH 0xff ( ) jumps to the external label. This statement may be used to define both conditional and unconditional branch instructions. Such instructions should be defined to take an operand of type 8 bit to choose the branch destination. This magic number should then be printed to the output assembly text using the built-in function textlabel , which replaces it with a valid assembler label, either the selected external label string or a scratch label attached to the proper destination instruction.

Specifications do not need to be directly concerned with internal branches, which occur or not as needed. However, external branches are part of a block's specification; typically the purpose of a block with an external branch is to test some condition and then either branch away or fall through to the next block. It is thus necessary to be able both to name the external label to use and to specify the conditions when it should be reached. For this purpose a predicate branchto is provided. It may appear in the postcondition and governs the exit path from the block: if forced to true, the block branches to the external assembler label, and if false, the block falls through from its last instruction. The concrete syntax is branchto(dest) which also sets the assembler label used to dest . It is not valid to name more than one such assembler label.

Note that the assembler labels used in branching are, though also assembler labels, a separate mechanism unrelated to the labels attached to memory regions; they are code labels rather than data labels and inherently work differently.

Also note that the implementation is slightly more complicated than the description given above and the rules given in the semantics section. In the implementation, the magic number that triggers an external branch is not 255 but the number 𝑙 such that 𝑙 added to the instruction number of the branch adds to 99. This detail affects only intermediate synthesis results in the internal format; the extracted assembly code is the same.

Register Sets. Register sets are second-class elements intended to exist only as literals and only as the result of lowering machine-independent specifications that cannot directly talk about specific registers. Currently they do not exist in the implementation. Register sets are not allowed to be operands to instructions to avoid state explosions when synthesizing. This restriction is currently not captured in the abstract syntax or typing rules.

Functions and Procedures. Functions, defined with def in Figure 2, are pure functions whose bodies are expressions. They produce values. They can access registers and memory, and can fail, but cannot update anything. Procedures, defined with proc , are on the other hand impure and their bodies are statements. They do not produce values, but they may update the machine state. They are otherwise similar, and are intended to be used to abstract out common chunks of functionality shared among multiple instructions in machine descriptions. Functions can also be used for state hiding in specifications.

Functions and procedures are second-class; they may be called only by their own name and may not be bound to variables or passed around. Furthermore, they are only allowed to handle base types: higher-order functions are explicitly not supported.

Operations. Operations (defined with defop in Figure 2) are essentially instructions, and we refer to these interchangeably. An operation takes zero or more operands and transforms the machine state as defined by one or more statements. Operands are currently defined as expressions, but are restricted as follows:

Figure 2. Cassiopea statements and declarations.

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-d6c2981a-fc29-4eed-ad3e-baecec37039d.jpg)

- · They may be values, but not string values, and not fail .
- · They may be variables of register type.
- · They may be variables of label type.

This restriction affects what the synthesizer tries to generate; a broader set of expressions may be accepted for verification or concrete execution and simply evaluated in place.

There is an important distinction between 'operations' and 'instructions'. Operations are the units in which Cassiopea reasons about machine operations and the units in which Cassiopea generates programs and code fragments, but they do not necessarily need to be single instructions. The text output to the assembler is arbitrary and can be computed on the fly based on the operand value. On some platforms the assembler defines so-called 'synthetic instructions' that are potentially multiple real instructions. This facility takes that a step further by allowing the writer of the machine description to define their own synthetics.

A (single) lowering is a collection of declarations used to instantiate elements in Alewife translations, shown in Figure 3. These are placed into a module , with multiple modules per file, so that the lowerings associated with multiple related Alewife specifications can be kept together. The import xmodule directive enables sharing common elements in one module xmodule across multiple specifications. The modules used to lower a specification are selected using the lower with -declaration in Alewife.

Other Constructs. e [ C ] and e [ C 1 , C 2 ] in Figure 1 extract a single bit and a slice, respectively, from a bitvector. The offsets are constants; a shift can be used beforehand if variable offsets are needed. The width of the slice must be constant for static typing.

## Machines, Lowering, Specifications, and Programs.

A machine is the full description of a machine architecture; it includes declarations (including types, constants, registers, functions and procedures) and also instructions, shown in Figure 3. This is typically a standalone file but may be a set of files referenced via include .

A spec (specification) is a precondition and postcondition, which are boolean expressions, along with optional permission to destroy additional registers (the frame ), shown in Figure 3. Cassiopea specifications are produced by compiling Alewife specifications. Note that a module can also contain frame declarations; these are added to any provided in the Alewife specification. A code block is permitted to destroy any register that is either explicitly listed in the frame declarations or mentioned in the postcondition, while it may read any register mentioned in the precondition and any control register. This restriction is currently not adequately captured in the semantics rules.

A program is a sequence of instruction invocations, shown in Figure 3.

Built-in Functions. Here is a partial list of the built-in functions in Cassiopea.

- · empty ( int C → C reg set ) produces an empty register set of the requested bit size.
- · hex ( C bit → string ) prints numbers in hexadecimal.
- · bin ( C bit → string ) prints numbers in binary.

Figure 3. Cassiopea machines, lowerings, programs, and specifications.

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-88c6be29-6937-4d7c-9bcd-a37ddecd0bc6.jpg)

- · dec ( C bit → string ) prints numbers in decimal.
- · lbl ( C label → string ) prints labels (it returns the label identifier as a string). This is for data labels attached to memory locations.
- · textlabel (8 bit → string ) prints branch offsets as assembler labels. This is for code labels, as described above.
- · format ( string → string . . . → string ) formats strings. The first argument is a format string; the remainder of the arguments are substituted into the format string where a dollar sign appears followed by the argument number (1-based). (A literal dollar sign can be inserted by using $$ .) The number of additional arguments expected is deduced from the contents of the format string.
- · bv\_to\_len ( int C 1 → C 2 bit → C 1 bit ) returns a new bitvector of size C 1 with the same value up to the ability of the new size to represent that value.
- · bv\_to\_uint ( C 1 bit → int ) converts a bitvector to unsigned int.
- · uint\_to\_bv\_l int ( C 1 → int C 2 → C 1 bit ) converts an unsigned int C 2 into a bitvector of size C 1.
- · isptr ( C bit → bool ) tests at runtime if a bitvector value is a pointer or not.

Note that some of these functions have their own typing rules, some of which are polymorphic in bitvector size. We have not complicated the typing rules presented by including all of these as special cases.

Concrete Syntax. We do not describe the concrete syntax in detail here; however, it does not stray very far from the abstract syntax. The operator precedence and most of the operator spellings are taken from C but most of the rest of the concrete syntax is ML-style. There are also a few things desugared in the parser and not shown in the abstract syntax. As already mentioned, bitvector constants whose size is a multiple of 4 can also be written in the form 0x C . Syntax of the form 𝑒. hex , 𝑒. bin , and 𝑒. dec is converted to the builtin functions hex , bin , dec respectively. These print either integers or bitvectors as strings in hexadecimal, binary, or decimal respectively. The syntax x . lbl is similarly converted to the built-in function lbl . This produces the label (that is, the identifier naming the label) as a string. Further the concrete syntax supports include files via an include directive, which is useful for sharing common elements.

## 4 Cassiopea Static Typing

This section describes the Cassiopea type system.

Environments. The type system uses two environments: Δ maps type alias names to the types they represent, and Γ maps variables to the types assigned to them. Recall from the syntax that only base types may have alias names, so alias names can be treated as base types.

Well-Formedness. Since types include alias names, we need to check that a proposed alias name is actually a type name. At the same time we insist that the widths of bitvectors be greater than zero. The judgment for this has the form Δ ⊢ wf 𝜏 , shown in Figure 4. There is an intended invariant that only well-formed types may be entered into the variable typing environment Γ , so that types taken out of it do not need to be checked for well-formedness again.

In a typing environment comprised of Δ mapping userdefined type names (type aliases) to types and Γ mapping program binders (variables) to types, we say that a type is well formed when all type names are well-formed and all indices are of type int and nonzero.

Expressions. Expressions produce values that have types. Because types appear explicitly in some expressions (e.g., let ), we need both environments, so the form of an expression typing judgment is Δ Γ , ⊢ e : 𝜏 , shown in Figure 5. This means that we conclude e has type 𝜏 . Note that the . txt form is restricted to registers; it is specifically for extracting the assembly text form of a register. We have not written out a

Figure 4. Cassiopea type well-formedness.

| (Type Well-Formedness)          | (Type Well-Formedness)          | (Type Well-Formedness)   | (Type Well-Formedness)     |
|---------------------------------|---------------------------------|--------------------------|----------------------------|
| Δ ⊢ wf ()                       | Δ ⊢ wf int                      | Δ ⊢ wf bool              | Δ ⊢ wf string              |
| C > 0                           | C > 0                           | C > 0                    | C > 0                      |
| Δ ⊢ wf C bit                    | Δ ⊢ wf C reg                    | Δ ⊢ wf C label           | Δ ⊢ wf C reg set           |
| C 1 > 0 , C 2 > 0 , C 3 > 0 Δ ( | C 1 > 0 , C 2 > 0 , C 3 > 0 Δ ( | x ) = 𝜏 Δ ⊢ wf 𝜏         | ∀ 𝑖, Δ ⊢ wf 𝜏 𝑖 Δ ⊢ wf 𝜏 𝑟 |
| Δ ⊢ wf C 1 bit C 2 len C 3 ref  | Δ ⊢ wf C 1 bit C 2 len C 3 ref  | Δ ⊢ wf x                 | Δ ⊢ wf 𝜏 𝑖 → 𝜏 𝑟           |

## (Expression Typing)

Δ Γ

⊢

"

. . .

Δ

,

"

:

string

Δ Γ

,

⊢

true

⊢

wf

Δ Γ

𝜏

⊢

GLYPH&lt;1&gt;

:

C

⊢

=

Δ Γ

,

C

,

Δ Γ

⊢

xfunc

Δ Γ

,

⊢

,

Δ Γ

∀ ∈ (

i

⊢

:

{

int

,

0 1

0b

}

k

C

:

GLYPH&lt;0&gt;

𝜏

𝑖

k

base

,

bit

→

Δ Γ

,

Δ Γ

e

:

⊢

𝜏

base

⊢

xfunc

e

C

2

bit

,

Δ Γ

⊢

,

Δ Γ

1

. . .

,

Δ Γ

⊢ {

⊢

k

)

,

x

1

, . . . ,

e

e

[

C

1

1

(

:

𝜏

base

e

𝑖

1

0

≤

]

:

1

,

Δ Γ

:

Δ Γ

int

)

1

:

Γ

fail

∀

:

,

i

Δ Γ

,

𝜏

base

,

⊢

e

2

,

𝜏

⊢

e

:

(

𝑖

:

𝜏

Δ Γ

⊢

,

𝜏

base

e

Δ Γ

x

Δ Γ

&lt;

:

C

bit

⊢

e

1

,

Δ Γ

⊢

C

2

𝜏

base

x

)

=

𝜏

,

Δ Γ

𝑖

base

1

⊨

1

binop e

⊢

∉

,

Δ Γ

b

⊢

let

:

Δ

⊢

x

:

𝜏

,

Δ Γ

⊢

binop

2

bool

⊢

wf

𝜏

e

:

:

e

:

C

3

,

Δ Γ

x

:

𝜏

base

𝜏

base

2

bit

0

≤

C

1

&lt;

C

2

≤

C

3

Δ Γ , ⊢ e [ C 1 , C 2 ] : k bit

,

=

Δ Γ

[

x

↦

→

𝜏

base

]

⊢

e

2

:

𝜏

2

e

1

in

e

2

:

𝜏

2

bool

Δ Γ

,

⊢

e

1

:

𝜏

Δ Γ

,

Δ Γ , ⊢ if b then e 1 else e 2 : 𝜏

,

Δ Γ

⊢

xmem

:

\_

bit

\_

len

C

ref

Δ Γ

Δ Γ , ⊢ ( xmem , e ) : C bit

,

,

Δ Γ

⊢

e

:

C

reg

Δ Γ

,

⊢

x

:

⊢

,

Δ Γ

⊢

𝑘

x

}

∗

e

:

C

bit

𝑖

x

:

C

:

C

reg

Δ Γ

,

,

Δ Γ

⊢

⊢

x

e

:

C

C

label

:

C

bit reg set

Δ Γ

,

,

Δ Γ

,

,

Δ Γ

⊢

e

⊢

2

:

𝜏

e

:

\_

bit

⊢

fetch

⊢

(

e C

,

branchto

⊢

:

e

1

:

C

reg reg set

Δ Γ

,

⊢ ∥

e

∥

:

int

Δ Γ

,

⊢

Figure 5. Cassiopea typing rules for expressions.

separate rule for each unary and binary operator. The types of operators are as shown in Figure 6. Note that the bitvector operators are polymorphic in bit size.

Arguably the right hand argument of the shift operators should be allowed to be a different width. There is one rule for pointer literals that covers both the expression and the value form. There is no rule (either in the typing or in the semantics) that allows taking a subrange of a memory region as a new smaller region. We have not needed this for our use cases, and keeping the set of possible regions fixed simplifies a number of things.

Statements. Statements do not produce values. We still need both environments, though, so the form of a typing

)

:

bool

,

Δ Γ

e

⊢

1

∈

e

2

:

𝜏

:

𝜏

Δ Γ

⊢

false

,

,

Δ Γ

,

Δ Γ

base

1

,

Δ Γ

base

⊢

e

:

⊢

e

.

txt

⊨

unop

⊢

1

→

𝜏

unop e

:

:

:

:

bool

𝜏

reg string

𝜏

base

1

→

𝜏

base

1

→

𝜏

base

2

base

2

k

C

C

=

C

&gt;

𝜏

base

2

2

-

C

1

0

bit

e

2

:

C

bool

:

,

reg set

Figure 6. Cassiopea typing rules for unop and binop .

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-9b3738ae-dc24-4796-a85d-a7634dcabb74.jpg)

| - b - ¬ bnot = + < && >> b + b < bs < ∪ ⊆   | ≠ - ∗ < = | | >> 𝑆 bor b - b b < = b bs < = bs ∩ \   | > ⊕ << bxor ∗ > >   | int → int ∀ C , C bit → C bit bool → bool ∀ C , C bit → C bit ∀ 𝜏 base , 𝜏 base → 𝜏 base → bool int → int → int int → int → bool   |
|---------------------------------------------|------------------------------------------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------|
| band                                        |                                                      |                     | bool → bool → bool ∀ C , C bit → C bit → C bit ∀ C , C bit → C bit → C bit ∀ C , C bit → C bit → C bit ∀ C , C bit → C bit → bool  |
|                                             |                                                      |                     | ∀ C , C bit → C bit → bool reg set → C reg set → C reg set                                                                         |
|                                             |                                                      |                     | , C                                                                                                                                |
|                                             |                                                      |                     | ∀ C , C reg set → C reg set → bool                                                                                                 |

↦

| (Statement Typing)                | (Statement Typing)                | (Statement Typing)                | (Statement Typing)                     | (Statement Typing)                     | (Statement Typing)                     |
|-----------------------------------|-----------------------------------|-----------------------------------|----------------------------------------|----------------------------------------|----------------------------------------|
| Δ , Γ ⊢ S 1 Δ , Γ                 | ⊢ S 2 Δ , Γ ⊢ x                   | proc : GLYPH<0> 𝜏 base 𝑖          | →() GLYPH<1>                           | ∀ i ,                                  | Δ , Γ ⊢ e 𝑖 : 𝜏 base 𝑖                 |
| Δ , Γ ⊢ S 1 ; S 2                 | Δ , Γ ⊢ S 1 ; S 2                 | Δ , Γ ⊢ x proc ( e 𝑖 )            | Δ , Γ ⊢ x proc ( e 𝑖 )                 | Δ , Γ ⊢ x proc ( e 𝑖 )                 | Δ , Γ ⊢ x proc ( e 𝑖 )                 |
| Δ , Γ ⊢ e : 𝜏 base                | x ∉ Δ , Γ Δ , Γ [ x               | → 𝜏 base ] ⊢ S x                  |                                        | ∉ Δ , Γ                                | Δ , Γ [ x → int ] ⊢ S                  |
| Δ , Γ ⊢ let x : 𝜏 base = e in S   | Δ , Γ ⊢ let x : 𝜏 base = e in S   | Δ , Γ ⊢ let x : 𝜏 base = e in S   | Δ , Γ ⊢ for x ∈ ( C 1 . . . C 2 ) do S | Δ , Γ ⊢ for x ∈ ( C 1 . . . C 2 ) do S | Δ , Γ ⊢ for x ∈ ( C 1 . . . C 2 ) do S |
| Δ , Γ ⊢ e : bool Δ ,              | Γ ⊢ S 1 Δ , Γ                     | ⊢ S 2 Δ , Γ ⊢ e 1                 | : C                                    | reg                                    | Δ , Γ ⊢ e 2 : C bit                    |
| Δ , Γ ⊢ if e then S 1 else S 2    | Δ , Γ ⊢ if e then S 1 else S 2    | Δ , Γ ⊢ if e then S 1 else S 2    | Δ , Γ ⊢ e 1 : = e 2                    | Δ , Γ ⊢ e 1 : = e 2                    | Δ , Γ ⊢ e 1 : = e 2                    |
| Δ , Γ ⊢ e 1 : C 1 bit             | Δ , Γ ⊢ e 2 : C 2 bit             | Δ , Γ ⊢ e : 8                     | bit                                    |                                        | Δ , Γ ⊢ e : bool                       |
| Δ , Γ ⊢ store ( e 1 , C 2 ) ← e 2 | Δ , Γ ⊢ store ( e 1 , C 2 ) ← e 2 | Δ , Γ ⊢ store ( e 1 , C 2 ) ← e 2 | Δ , Γ ⊢ BRANCH ( e ) Δ ,               | Δ , Γ ⊢ BRANCH ( e ) Δ ,               | Γ ⊢ assert ( e )                       |
|                                   | ,                                 | , ⊢                               |                                        |                                        |                                        |
| Δ Γ ⊢                             |                                   | skip Δ                            | Γ                                      |                                        |                                        |
|                                   |                                   |                                   | crash                                  |                                        |                                        |

↦

Figure 7. Cassiopea typing rules for statements.

judgment for a statement is Δ Γ , ⊢ S , shown in Figure 7. This means that S is well typed.

Declarations. Declarations update the environment. The form of a typing judgment for a declaration is Δ Γ , ⊢ decl ▷ Δ ′ , Γ ′ , and a judgment for a list of declarations has the same form, shown in Figure 8. This means that the declaration (or list) is well typed and produces the new environment on the right. One can think of the ⊢ ▷ as the tail and head of an arrow with the element transforming the environments labeling the body.

any references need to be quantified. (That in turn is not allowed to avoid needing to send these quantifiers to the SMT solver.)

We impose an additional syntactic restriction on declarations found in a machine description (as opposed to the additional declarations that may appear in a specification): they may not use the expression forms that refer to machine state (registers or memory), because when defining the machine, there is no specific machine state to refer to;

Machines. A machine is some declarations followed by some defops, so the typing rule is just sequencing, shown in Figure 9, but there is a wrinkle: the initial environment for the machine is not an input. Δ builtin is the (fixed) environment describing the built-in type aliases. (Currently there are none.) Γ builtin is the environment describing the types of built-in variables. This notionally includes the built-in functions. (But as mentioned earlier some of them actually have their own typing rules.) The form of a typing judgment for a machine is ⊢ machine ▷ Δ Γ , . This means that the machine description is well typed and provides the environment on

↦

↦

Figure 8. Cassiopea typing rules for declarations.

the right for use of other constructs that depend on the machine. (Specs and programs are only valid relative to a given machine.)

Specifications. For specifications we need two helper rules, shown in Figure 9: one that applies an additional list of declarations to a machine, which has the same form as the judgment on a machine; and one that says that a frame (modifies list) is well typed, which has the form Δ Γ , ⊢ frames . This lets us write the real rule, which has the form machine ⊢ spec and means that the specification is well typed under the machine description.

↦

↦

↦

↦

↦

↦

↦

↦

↦

↦

↦

↦

## (Machine Typing)

$$\frac { \Delta _ { b u i l t i n }, \Gamma _ { b u i l t i n } \vdash \text{decls} \rhd \Delta, \Gamma \quad \Delta, \Gamma \vdash \text{defops} \rhd \Delta ^ { \prime }, \Gamma ^ { \prime } } { \vdash \text{decls} ; \text{defops} \rhd \Delta ^ { \prime }, \Gamma ^ { \prime } }$$

## (Specification Typing)

$$\frac { \vdash \ m a c h i n e \rhd \Delta, \Gamma \quad \Delta, \Gamma \vdash \text{decls} \rhd \Delta ^ { \prime }, \Gamma ^ { \prime } } { \vdash \ m a c h i n e ; \text{decls} \rhd \Delta ^ { \prime }, \Gamma ^ { \prime } }$$

$$\frac { \forall i, \, \Delta, \Gamma \vdash x _ { r e g _ { i } } \colon C _ { i } \, r e g } { \Delta, \Gamma \vdash r e g \text{-modify} \, \colon \overline { x _ { r e g _ { i } } } } \ \frac { \forall i, \, \Delta, \Gamma \vdash e _ { i } \colon \text{int} \quad \forall i, \, \Delta, \Gamma \vdash x _ { m e m _ { i } } \colon \tau _ { m e m } } { \Delta, \Gamma \vdash \text{mem-modify} \, \colon \overline { ( x _ { m e m _ { i } }, \, e _ { i } ) } }$$

$$\frac { } { \Delta, \Gamma \vdash \epsilon } \ \frac { \Delta, \Gamma \vdash \text{frame} \quad \Delta, \Gamma \vdash \text{frames} } { \Delta, \Gamma \vdash \text{frame} \text{frames} }$$

$$\frac { \vdash \ m a c h i n e ; \, \text{decls} \rhd \Delta, \Gamma \quad \Delta, \Gamma \vdash \text{frame} \quad \Delta, \Gamma \vdash \text{pre} \, \colon \text{bool} \quad \Delta, \Gamma \vdash \text{post} \, \colon \text{bool} } { \ m a c h i n e \vdash \text{decls} ; \, \text{frames} ; \, \text{pre} ; \, \text{post} }$$

## (Program Typing)

$$\frac { \Delta, \Gamma \vdash x _ { o p } \colon ( ( ) \to ( ) ) } { \Delta, \Gamma \vdash x _ { o p } } \ \frac { \Delta, \Gamma \vdash x _ { o p } \colon \left ( \overline { \tau _ { b a s e _ { i } } } \to ( ) \right ) \quad \forall i, \ \Delta, \Gamma \vdash e _ { i } \colon \tau _ { b a s e _ { i } } } { \Delta, \Gamma \vdash x _ { o p } \ \overline { e _ { i } } }$$

,

Δ Γ

⊢

inst

Δ Γ

,

⊢

insts

⊢

machine

▷

Δ Γ

,

Δ Γ

,

⊢

program

,

Δ Γ

⊢

𝜖

Δ Γ

,

⊢

inst

;

insts machine

⊢

program

Figure 9. Cassiopea typing rules for machine, specification, and program.

Programs. A program is a sequence of calls to instructions. We need judgments of the form Δ Γ , ⊢ inst for a single instruction and also Δ Γ , ⊢ insts for the sequence, shown in Figure 9. There are two cases for a single instruction because of a minor glitch in formulation: because the overbar notation means 'one or more', there are two cases in the syntax for instructions, one for zero operands and one for one or more operands; we need typing rules for both cases. Meanwhile the type entered into Γ for a zero-operand instruction is unit to unit, not 𝜖 to unit, to avoid needing an additional form for types just for this case. (Notice that a one-operand instruction may not have type unit to unit because unit is not allowed as an instruction operand, so the type is not ambiguous.) These rules let us write a judgment for a program, which has the form machine ⊢ program and means that the program is well typed relative to the machine.

Soundness. Note that even though we do not check certain things statically, the type system remains sound: we include the necessary checks and failure states in the semantics so that evaluation does not get stuck.

## 5 Cassiopea Semantics

This section defines the semantics of Cassiopea.

Environment. The execution environment Λ maps Cassiopea variables x to values v . For labels on memory regions, each label maps to a pointer that points to the base (offset 0) of the region associated with the label. However, we take advantage of the polymorphism and dynamic typing of paper rules to also store the following in the same environment:

- · xfunc (function names) map to pairs { x 𝑖 , e } , which give the list of argument names and the body for functions.
- · xproc (procedure names) map to pairs { x 𝑖 , S } , which give the list of argument names and the body for procedures.
- · xop (operation/instruction names) map to triples { x 𝑖 , e S , } , which give the list of argument names, the expression for the text form, and the body for operations.
- · r . txt (the form for the text version of a register) maps to a value.
- · The word EXTBRANCH maps to a branch state, which must be either ext or · . This reports whether, after executing a program, it branched to the external label or not.

Since identifiers are not allowed to overlap in well-typed programs, and register identities are not strings at all, and EXTBRANCH is reserved, this usage creates no conflicts.

Note that xmem , x 𝜏 , and xmodule do not appear in Λ as these require no translation/lookup at runtime.

## (Expression Semantics)

↦

↦

| Λ ( x ) = v                                        | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ r Λ ( r .                        | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ r Λ ( r .                                   | txt ) = v Λ ⊢ (                                               | 𝜌, 𝜎, e ) ⇓ r                                                 | r . txt ∉ Λ                                                   |
|----------------------------------------------------|----------------------------------------------------|---------------------------------------------------------------|---------------------------------------------------------------|---------------------------------------------------------------|---------------------------------------------------------------|
| Λ ⊢ ( 𝜌, 𝜎, x ) ⇓ v Λ ⊢ ( 𝜌, 𝜎, e . txt ) ⇓ v      | Λ ⊢ ( 𝜌, 𝜎, x ) ⇓ v Λ ⊢ ( 𝜌, 𝜎, e . txt ) ⇓ v      | Λ ⊢ ( 𝜌, 𝜎, x ) ⇓ v Λ ⊢ ( 𝜌, 𝜎, e . txt ) ⇓ v                 | Λ ⊢ ( 𝜌, 𝜎, x ) ⇓ v Λ ⊢ ( 𝜌, 𝜎, e . txt ) ⇓ v                 | Λ ⊢ ( 𝜌, 𝜎, e . txt ) ⇓ fail                                  | Λ ⊢ ( 𝜌, 𝜎, e . txt ) ⇓ fail                                  |
| ∀ i , Λ ⊢ ( 𝜌, 𝜎, e 𝑖 ) ⇓ v 𝑖                      | ∀ i , Λ ⊢ ( 𝜌, 𝜎, e 𝑖 ) ⇓ v 𝑖                      | Λ ( x func ) = { x 𝑖 , e } [∀ ,                               | Λ ( x func ) = { x 𝑖 , e } [∀ ,                               | Λ i x 𝑖 → v 𝑖 ] ⊢ ( 𝜌, 𝜎, e ) ⇓ ˜ v                           | Λ i x 𝑖 → v 𝑖 ] ⊢ ( 𝜌, 𝜎, e ) ⇓ ˜ v                           |
| Λ ⊢ ( 𝜌, 𝜎, x func ( e 𝑖 )) ⇓ ˜ v                  | Λ ⊢ ( 𝜌, 𝜎, x func ( e 𝑖 )) ⇓ ˜ v                  | Λ ⊢ ( 𝜌, 𝜎, x func ( e 𝑖 )) ⇓ ˜ v                             | Λ ⊢ ( 𝜌, 𝜎, x func ( e 𝑖 )) ⇓ ˜ v                             | Λ ⊢ ( 𝜌, 𝜎, x func ( e 𝑖 )) ⇓ ˜ v                             | Λ ⊢ ( 𝜌, 𝜎, x func ( e 𝑖 )) ⇓ ˜ v                             |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ v 1                              | ˜ v 2 = unop v 1                                   | Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ v 1                                       | Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ v 1                                       | Λ ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ v 2 ˜ v                                   | 3 = v 1 binop v 2                                             |
| Λ ⊢ ( 𝜌, 𝜎, unop e ) ⇓ ˜ v 2                       | Λ ⊢ ( 𝜌, 𝜎, unop e ) ⇓ ˜ v 2                       |                                                               |                                                               | Λ ⊢ ( 𝜌, 𝜎, e 1 binop e 2 ) ⇓ ˜ v 3                           | Λ ⊢ ( 𝜌, 𝜎, e 1 binop e 2 ) ⇓ ˜ v 3                           |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0b C                             | C = b 0 . . . b C 𝑖 . . . b n                      | C = b 0 . . . b C 𝑖 . . . b n                                 | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0b C                                        | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0b C                                        | C = b 0 . . . b C 𝑖 . . . b C 𝑗 . . . b n                     |
| Λ ⊢ ( 𝜌, 𝜎, e [ C 𝑖 ]) ⇓ b C 𝑖                     | Λ ⊢ ( 𝜌, 𝜎, e [ C 𝑖 ]) ⇓ b C 𝑖                     |                                                               |                                                               | Λ ⊢ ( 𝜌, 𝜎, e [ C 𝑖 , C 𝑗 ]) ⇓ b C 𝑖 . . . b C 𝑗              | Λ ⊢ ( 𝜌, 𝜎, e [ C 𝑖 , C 𝑗 ]) ⇓ b C 𝑖 . . . b C 𝑗              |
|                                                    | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                    | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                               | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                               | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                               |                                                               |
|                                                    | Λ ⊢ ( 𝜌, 𝜎, e [ _ ]) ⇓ fail                        | Λ ⊢ ( 𝜌, 𝜎, e [ _ ]) ⇓ fail                                   | Λ ⊢ ( 𝜌, 𝜎, e [ _ , _ ]) ⇓ fail                               | Λ ⊢ ( 𝜌, 𝜎, e [ _ , _ ]) ⇓ fail                               |                                                               |
|                                                    | Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ v 1                            | Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ v 1                                       | Λ [ x → v 1 ] ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ ˜ v 2                         | Λ [ x → v 1 ] ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ ˜ v 2                         |                                                               |
|                                                    | Λ ⊢ ( 𝜌, 𝜎, let x : 𝜏 base = e 1 in e 2 ) ⇓ ˜ v 2  | Λ ⊢ ( 𝜌, 𝜎, let x : 𝜏 base = e 1 in e 2 ) ⇓ ˜ v 2             | Λ ⊢ ( 𝜌, 𝜎, let x : 𝜏 base = e 1 in e 2 ) ⇓ ˜ v 2             | Λ ⊢ ( 𝜌, 𝜎, let x : 𝜏 base = e 1 in e 2 ) ⇓ ˜ v 2             | Λ ⊢ ( 𝜌, 𝜎, let x : 𝜏 base = e 1 in e 2 ) ⇓ ˜ v 2             |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ true Λ ⊢ ( 𝜌, 𝜎, e 𝑡 )           | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ true Λ ⊢ ( 𝜌, 𝜎, e 𝑡 )           | ⇓ ˜ v 𝑡 Λ ⊢ ( 𝜌, 𝜎, e                                         | ⇓ ˜ v 𝑡 Λ ⊢ ( 𝜌, 𝜎, e                                         | ) ⇓ false Λ ⊢ (                                               | 𝜌, 𝜎, e 𝑓 ) ⇓ ˜ v 𝑓                                           |
| Λ ⊢ ( 𝜌, 𝜎, if e then e 𝑡 else _ ) ⇓ ˜ v           | Λ ⊢ ( 𝜌, 𝜎, if e then e 𝑡 else _ ) ⇓ ˜ v           | 𝑡 Λ                                                           | 𝑡 Λ                                                           | ⊢ ( 𝜌, 𝜎, if e then _ else e 𝑓 ) ⇓ ˜ v 𝑓                      | ⊢ ( 𝜌, 𝜎, if e then _ else e 𝑓 ) ⇓ ˜ v 𝑓                      |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ r 𝜌 ( r ) = v                    | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ r 𝜌 ( r ) = v                    | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C ) 𝜎 ( x mem , C ) = ( v , C 𝑙 ) | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C ) 𝜎 ( x mem , C ) = ( v , C 𝑙 ) | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C ) 𝜎 ( x mem , C ) = ( v , C 𝑙 ) | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C ) 𝜎 ( x mem , C ) = ( v , C 𝑙 ) |
| Λ ⊢ ( 𝜌, 𝜎, ∗ e ) ⇓ v                              | Λ ⊢ ( 𝜌, 𝜎, ∗ e ) ⇓ v                              | Λ ⊢ ( 𝜌,                                                      | Λ ⊢ ( 𝜌,                                                      | 𝜎, fetch ( e , C 𝑙 )) ⇓ v                                     | 𝜎, fetch ( e , C 𝑙 )) ⇓ v                                     |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                    | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                    | 𝜎 ( x mem , C                                                 | 𝜎 ( x mem , C                                                 | ) = ( _ , C 𝑚 ) C 𝑚 ≠ C 𝑙                                     | ) = ( _ , C 𝑚 ) C 𝑚 ≠ C 𝑙                                     |
| Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail              | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail              | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                    | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ ( x mem , C )                    | ( x mem , C ) ∉ 𝜎                                             | ( x mem , C ) ∉ 𝜎                                             | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0b C                                        | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0b C                                        |
| Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail              | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail              | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         | Λ ⊢ ( 𝜌, 𝜎, fetch ( e , C 𝑙 )) ⇓ fail                         |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ C                                | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ C                                | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ C                                           | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ C                                           | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ C                                           | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ C                                           |
|                                                    | Λ ⊢ ( 𝜌, 𝜎, ( x mem , e )) ⇓ ( x mem , C )         | Λ ⊢ ( 𝜌, 𝜎, ( x mem , e )) ⇓ ( x mem , C )                    | Λ ⊢ ( 𝜌, 𝜎, ( x mem , e )) ⇓ ( x mem , C )                    | Λ ⊢ ( 𝜌, 𝜎, ( x mem , e )) ⇓ ( x mem , C )                    | Λ ⊢ ( 𝜌, 𝜎, ( x mem , e )) ⇓ ( x mem , C )                    |
| Λ ( EXTBRANCH ) = ext                              | Λ ( EXTBRANCH ) = ext                              | Λ ( EXTBRANCH ) = ext                                         | Λ ( EXTBRANCH ) = ·                                           | Λ ( EXTBRANCH ) = ·                                           | Λ ( EXTBRANCH ) = ·                                           |
| Λ ⊢ ( 𝜌, 𝜎, branchto ) ⇓ true                      | Λ ⊢ ( 𝜌, 𝜎, branchto ) ⇓ true                      | Λ ⊢ ( 𝜌, 𝜎, branchto ) ⇓ true                                 | ⊢ ( 𝜌, 𝜎, branchto ) ⇓ false                                  | ⊢ ( 𝜌, 𝜎, branchto ) ⇓ false                                  | ⊢ ( 𝜌, 𝜎, branchto ) ⇓ false                                  |
| ∀ i ∈ ( 1 . . . k ) , Λ ( x 𝑖 ) = r 𝑖              | ∀ i ∈ ( 1 . . . k ) , Λ ( x 𝑖 ) = r 𝑖              | ∀ i ∈ ( 1 . . . k ) , Λ ( x 𝑖 ) = r 𝑖                         | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ { r 1 , . . . , r C }                       | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ { r 1 , . . . , r C }                       | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ { r 1 , . . . , r C }                       |
| Λ ⊢ ( 𝜌, 𝜎, { x 1 , . . . , x 𝑘 }) ⇓ { r 1 , . . . | Λ ⊢ ( 𝜌, 𝜎, { x 1 , . . . , x 𝑘 }) ⇓ { r 1 , . . . | Λ ⊢ ( 𝜌, 𝜎, { x 1 , . . . , x 𝑘 }) ⇓ { r 1 , . . .            | Λ ⊢ ( 𝜌, 𝜎, ∥ e ∥) ⇓ C                                        | Λ ⊢ ( 𝜌, 𝜎, ∥ e ∥) ⇓ C                                        | Λ ⊢ ( 𝜌, 𝜎, ∥ e ∥) ⇓ C                                        |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ r                                | Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ r                                | Λ ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ { r 1 , . . . ,                           | Λ ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ { r 1 , . . . ,                           | ∃ i ∈ ( 1 . . . k ) , r i = r                                 | ∃ i ∈ ( 1 . . . k ) , r i = r                                 |
| Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ true                     | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ true                     | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ true                                | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ true                                | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ true                                | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ true                                |
| Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ r Λ ⊢ ( 𝜌,                     | Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ r Λ ⊢ ( 𝜌,                     | 𝜎, e 2 ) ⇓ { r 1 , . . . ,                                    | 𝜎, e 2 ) ⇓ { r 1 , . . . ,                                    | ∀ i ∈ ( 1 . . . k ) , r i ≠ r                                 | ∀ i ∈ ( 1 . . . k ) , r i ≠ r                                 |
| Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ false                    | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ false                    | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ false                               | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ false                               | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ false                               | Λ ⊢ ( 𝜌, 𝜎, e 1 ∈ e 2 ) ⇓ false                               |

Figure 10. Cassiopea semantics for expressions.

Machine State. In addition to the execution environment, we also need a representation of machine state. We define two stores, one for registers and one for memory. The register store 𝜌 maps registers r to values v . The memory store 𝜎 is more complicated: it maps pairs ( xmem , C ) (that is, pointer literals) to pairs ( v C , 𝑙 ) , where v is the value stored at that location and C 𝑙 is the bit width. The bit widths of memory regions are invariant, both across the region when they are declared and also over time. They are used to check the access widths appearing in fetch and store operations. Also note that new entries cannot be created in either the register store or the memory store, as real hardware does not permit such actions. The values stored in registers and memory regions are restricted by the typing rules to bitvectors (whether constants or pointers) of the appropriate width.

Notice that stepping through the declarations does not initialize the machine state. We want to reason about executions over ranges of possible starting machine states; so instead we provide a judgment that uses the typing environments to restricts the stores to forms consistent with the declarations. This is discussed further below.

Expressions. We describe expressions with a large-step operational semantics, shown in Figure 10. The form of an expression semantic judgment is: Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ v , which means that given the environment Λ and the machine state 𝜌, 𝜎 , the expression e evaluates to the value v . Expressions mayrefer to the machine state, but not modify it. Expressions can fail; in addition to the explicit failure cases seen, some of the operators and built-in functions can fail. For example, as mentioned earlier, attempting bitvector arithmetic other than addition and subtraction on pointers will fail. Furthermore, division by zero fails.

Note that we currently do not statically check (in the typing rules) that the . txt form is present for every register or that it is defined for registers on which it is used. Thus we have an explicit failure rule for when no matching declaration has been seen. We also have failure rules for bad fetch operations: if the length annotation is wrong, if the pointer is not in the machine state (this covers both unaligned accesses and out of bounds accesses), or if the value used is not a pointer. Similarly, we have failure rules for when bit indexing/slicing a pointer. We do not, conversely, need explicit failure checks or rules for the bit indexes in the bit extraction/slicing constructs as they are statically checked.

Also note that we include in the semantics the obvious failure propagation rules for when subexpressions fail. We do not show these explicitly as they are not particularly interesting or informative; however, note that the &amp;&amp; and | | logical operators short-cut left to right.

Statements. Unlike expressions, statements can change machine state. Thus, the form of a machine state semantics judgment (also large step) is Λ ⊢ ( 𝜌, 𝜎, S ) ⇓ ( 𝜌 , 𝜎 ′ ′ , S ′ , 𝜉 ) , shown in Figure 11. This means that the statement S evaluates to the irreducible statement S ' (which must be either skip or crash ) and in the course of doing so changes the machine state from 𝜌, 𝜎 to 𝜌 , 𝜎 ′ ′ , and produces a branching state 𝜉 , which can be either an 8-bit bitvector, the reserved value ext , or a dot ( ). As with expressions, statements can fail. Ex-· plicit failure rules are shown for bad stores (corresponding to the cases for bad fetches) and for a failed assertions. We also similarly include, but do not show, the obvious failure propagation rules for cases where sub-statements, or expressions within statements, fail.

Declarations. The semantics for declarations have judgments of the form Λ , ( 𝜌, 𝜎 ) ⊢ decl ▷ Λ ′ , shown in Figure 12. This means that the given declaration updates Λ as shown. As stated above, we do not initialize the machine state while handling declarations; this instead allows us to work with arbitrary (or universally quantified) machine states afterwards. However, because the let-binding declaration evaluates an expression, it potentially needs access to a machine state. Consequently we write the rules so they accept a machine state as input, but do not update it. In the case of machine descriptions, where there is no machine state, we pass empty environments; let declarations in machine descriptions are not allowed to reference machine state. In the case of the additional declarations that accompany a specification, we pass in the initial machine state; this allows values from the initial machine state to be globally bound so they can be referred to in the postcondition.

We give first the rules for a list of declarations, then the rules for the various declarations, then the rules for a list of operation definitions and a rule for a single operation definition. Note that several of the declarations do not update Λ , and nothing is placed in Λ for memory regions. For registers, only the mapping of the identifier to its underlying register r is entered; nothing for r is inserted.

Machines. As with the typing rules, the semantics rule for a whole machine description integrates the initial environment and gives a judgment of the form ⊢ machine ▷ Λ ′ , shown in Figure 13. We also include a comparable form that includes additional declarations, as it will be used below.

Programs. Instructions update the machine state, and we chose to represent programs as lists of instructions rather than having dummy instruction forms for skip and sequence. Consequently the form of the judgments is slightly different, and there are several of them, shown in Figure 14.

First, we can execute an individual instruction using the form Λ ⊢ ( 𝜌, 𝜎, inst ) → ( 𝜌 , 𝜎 ′ ′ , 𝜉 ) , meaning that the instruction executes and updates the machine state 𝜌, 𝜎 to 𝜌 , 𝜎 ′ ′ , producing the branching state 𝜉 . Then, a list of instructions executes using the form Λ ⊢ ( 𝜌, 𝜎, insts , 𝜉 ) → ( 𝜌 , 𝜎 ′ ′ , insts ′ , 𝜉 ′ ) , which means that the list steps to a new list and updates both the machine state and the branching state. When the instruction list runs out, these reduce to a shorter form via a judgment of the form Λ ⊢ ( 𝜌, 𝜎, insts , 𝜉 ) → ( 𝜌 , 𝜎 ′ ′ ) , which discards the instructions and branching state and produces

↦

↦

↦

↦

Figure 11. Cassiopea semantics for statements.

| (Statement Semantics)                                                                                                                                                                                                     |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Λ ⊢ ( 𝜌, 𝜎, S 1 ) ⇓ ( 𝜌 ′ , 𝜎 ′ , skip , ·) Λ ⊢ ( 𝜌 ′ , 𝜎 ′ , S 2 ) ⇓ ( 𝜌 ′′ , 𝜎 ′′ , S ′ 2 , ·) ′′ ′′ ′                                                                                                                  |
| Λ ⊢ ( 𝜌, 𝜎, S 1 ; S 2 ) ⇓ ( 𝜌 , 𝜎 , S 2 , ·)                                                                                                                                                                              |
| ∀ i , Λ ⊢ ( 𝜌, 𝜎, e 𝑖 ) ⇓ v 𝑖 Λ ( x proc ) = { x 𝑖 , S } Λ [∀ i , x 𝑖 → v 𝑖 ] ⊢ ( 𝜌, 𝜎, S ) ⇓ ( 𝜌 ′ , 𝜎 ′ , S ′ , ·)                                                                                                      |
| Λ ⊢ ( 𝜌, 𝜎, x proc ( e 𝑖 )) ⇓ ( 𝜌 ′ , 𝜎 ′ , S ′ , ·)                                                                                                                                                                      |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ v Λ [ x → v ] ⊢ ( 𝜌, 𝜎, S ) ⇓ ( 𝜌 ′ , 𝜎 ′ , S ′ , ·) Λ ⊢ ( 𝜌, 𝜎, let x : 𝜏 base = e in S ) ⇓ ( 𝜌 ′ , 𝜎 ′ , S ′ , ·)                                                                                     |
| ∀ i ∈ ( C 1 , C 1 + 1 , . . . , C 2 ) , Λ [ x → i ] ⊢ ( 𝜌 𝑖 , 𝜎 𝑖 , S ) ⇓ ( 𝜌 𝑖 + 1 , 𝜎 𝑖 + 1 , skip , ·)                                                                                                                 |
| Λ ⊢ ( 𝜌 𝐶 1 , 𝜎 C 1 , for x ∈ ( C 1 . . . C 2 ) do S ) ⇓ ( 𝜌 C 2 + 1 , 𝜎 C 2 + 1 , skip , ·)                                                                                                                              |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ true Λ ⊢ ( 𝜌, 𝜎, S ) ⇓ ( 𝜌 𝑡 , 𝜎 𝑡 , S 𝑡 , ·)                                                                                                                                                           |
| Λ ⊢ ( 𝜌, 𝜎, if e then S else _ ) ⇓ ( 𝜌 𝑡 , 𝜎 𝑡 , S 𝑡 , ·)                                                                                                                                                                 |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ false Λ ⊢ ( 𝜌, 𝜎, S ) ⇓ ( 𝜌 𝑓 , 𝜎 𝑓 , S 𝑓 , ·)                                                                                                                                                          |
| Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ r r ∈ 𝜌 Λ ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ v 𝜌 ′ = 𝜌 [ r → v ] ′                                                                                                                                                     |
| Λ ⊢ ( 𝜌, 𝜎, e 1 : = e 2 ) ⇓ ( 𝜌 , 𝜎, skip , ·)                                                                                                                                                                            |
| ( x mem , C ) 𝜎 ( x mem , C ) = ( _ , C 𝑙 ) Λ ⊢ ( 𝜌, 𝜎, e 2 ) ⇓ v 𝜎 ′ = 𝜎 [( x mem , C ) →( v , C 𝑙 )] Λ ⊢ ( 𝜌, 𝜎, store ( e 1 , C 𝑙 ) ← e 2 ) ⇓ ( 𝜌, 𝜎 ′ , skip , ·)                                                     |
| Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ ( x mem , C ) 𝜎 ( x mem , C ) = ( _ , C 𝑚 ) C 𝑚 ≠ C 𝑙 Λ ⊢ ( 𝜌, 𝜎, store ( e 1 , C 𝑙 ) ← e 2 ) ⇓ ( 𝜌, 𝜎, crash , ·)                                                                                    |
| 𝜌, 𝜎, e 1 ) ⇓ ( x mem , C ) ( x mem , C ) ∉ 𝜎 𝜎, store ( e 1 , C 𝑙 ) ← e 2 ) ⇓ ( 𝜌, 𝜎, crash , ·) Λ ⊢ ( 𝜌, 𝜎, e 1 ) ⇓ 0b C Λ ⊢ ( 𝜌, 𝜎, store ( e 1 , C 𝑙 ) ← e 2 ) ⇓ ( 𝜌, 𝜎, crash ⊢ ( 𝜌, 𝜎, ) ⇓ true ⊢ ( 𝜌, 𝜎, ) ⇓ false |
| Λ e Λ e                                                                                                                                                                                                                   |
| 𝜎, assert ( )) ⇓ ( 𝜌, 𝜎, skip , ·) ⊢ ( 𝜌, 𝜎, assert ( )) ⇓ ( 𝜌, 𝜎, crash , ·)                                                                                                                                             |
| ( 𝜌, e Λ e                                                                                                                                                                                                                |
| Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0 Λ ⊢ ( 𝜌, 𝜎, e ) ⇓ 0b C 0x00 < 0b C < 0xff                                                                                                                                                             |
| 𝜎, BRANCH ( e )) ⇓ ( 𝜌, 𝜎, skip , ·) Λ ⊢ ( 𝜌, 𝜎, BRANCH ( e )) ⇓ ( 𝜌, 𝜎, skip , 0b C ) ⊢ ( 𝜌, 𝜎, ) ⇓                                                                                                                      |
| Λ e 0xff                                                                                                                                                                                                                  |
| ⊢ ( 𝜌, 𝜎, BRANCH ( )) ⇓ ( 𝜌, 𝜎, skip , ext )                                                                                                                                                                              |
| Λ e                                                                                                                                                                                                                       |

an output machine state. That in turn allows us to draw conclusions of the form machine ⊢ ( 𝜌, 𝜎, program ) → ( 𝜌 , 𝜎 ′ ′ ) , which means that a machine with the initial state 𝜌, 𝜎 executes the program to produce the new machine state ( 𝜌 , 𝜎 ′ ′ ) .

Note that there are two versions of the judgment for instructions, one specialized for no arguments/operands. Instructions with no operands are declared as taking unit, but invoked with empty operands (not with unit) to correspond to the way assembly languages normally work.

We include a final judgment of the form machine ⊢ ( 𝜌, 𝜎, program ) → ( 𝜌 , 𝜎 ′ ′ ) that puts the machine on the left-hand side of the turnstile. It means that under a given machine the program maps 𝜌, 𝜎 to 𝜌 , 𝜎 ′ ′ . There is a limitation in the way we have formulated programs and the rules for programs, which is that there is no easy way to represent failure. (Failure in this might represent triggering an exception and stopping execution, which we do not model, or invoking 'unpredictable' or 'undefined' behavior in the processor and transitioning to an arbitrary unknown machine state.)

,

Λ

′

𝑖

Λ , ( 𝜌, 𝜎 ) ⊢ 𝜖 ▷ Λ

(

𝜌, 𝜎

) ⊢

type

x

𝜏

=

Λ

,

Λ

## (Declaration Semantics)

$$\frac { } { \epsilon \rhd \Lambda } \ \frac { \Lambda, ( \rho, \sigma ) \vdash d e c l \rhd \Lambda ^ { \prime } \quad \Lambda ^ { \prime }, ( \rho, \sigma ) \vdash d e c l s \rhd \Lambda ^ { \prime \prime } } { \Lambda, ( \rho, \sigma ) \vdash d e c l ; d e c l s \rhd \Lambda ^ { \prime \prime } }$$

Λ

⊢ (

𝜌, 𝜎,

e

) ⇓

v

𝜏

base

⊢ (

(

▷

𝜌, 𝜎,

𝜌, 𝜎

) ⊢

Λ

x

) ⇓

let

x

.

Λ

,

(

r

txt

Λ

=

Λ

[

xfunc

↦

→{

x

,

e

}]

Λ , ( 𝜌, 𝜎 ) ⊢ def xfunc x 𝑖 : 𝜏 base 𝑖 → 𝜏 base = e ▷ Λ ′

𝜌, 𝜎

Λ

=

𝑒

) ⊢

⊢ (

let

x

𝜌, 𝜎,

▷

Λ

:

𝜏

base

e

) ⇓

[

r

.

txt

=

e

v

↦

→ ]

v

Λ

=

Λ

[

xproc

↦

→{

x

′

𝑖

,

S

}]

Λ , ( 𝜌, 𝜎 ) ⊢ proc xproc x 𝑖 : 𝜏 base 𝑖 →() = S ▷ Λ ′

Λ

′

=

Λ

[

x

↦

→ ]

r

r

fresh

Λ

′

=

Λ

[

x

↦

→ ]

r

r

fresh

Λ , ( 𝜌, 𝜎 ) ⊢ letstate x : 𝜏 reg ▷ Λ ′ Λ , ( 𝜌, 𝜎 ) ⊢ letstate control x : 𝜏 reg ▷ Λ ′

Λ

′

=

Λ

[

x

↦

→ ]

r

r

fresh

Λ , ( 𝜌, 𝜎 ) ⊢ letstate control dontgate x : 𝜏 reg ▷ Λ ′

Λ

′

=

Λ

[

x

↦

→(

xmem

,

0

)]

Λ , ( 𝜌, 𝜎 ) ⊢ letstate xmem : 𝜏 mem ▷ Λ Λ , ( 𝜌, 𝜎 ) ⊢ letstate xmem : 𝜏 mem with x ▷ Λ ′

$$\frac { } { \Lambda, ( \rho, \sigma ) \vdash \epsilon \rhd \Lambda } \ \frac { \Lambda, ( \rho, \sigma ) \vdash d e f o p \rhd \Lambda ^ { \prime } } { \Lambda, ( \rho, \sigma ) \vdash d e f o p ; \ d e f o p s \rhd \Lambda ^ { \prime } }$$

Λ

′

=

Λ

[

xop

↦

→{[ ]

,

e

, 𝑆

}]

,

Λ

(

𝜌, 𝜎

) ⊢

defop xop

{

txt

=

e

,

sem

=

S

}

▷

Λ

′

Λ

=

Λ

[

xop

↦

→{

𝑥 ,

′

𝑖

e

, 𝑆

}]

Λ , ( 𝜌, 𝜎 ) ⊢ defop xop x 𝑖 : 𝜏 base 𝑖 { txt = e , sem = S } ▷ Λ ′

Figure 12. Cassiopea semantics for declarations.

## (Machine Semantics)

$$\left | \quad \frac { \Lambda _ { b u i l t i n }, ( \{ \}, \{ \} ) \vdash d e c l s \triangleright \Lambda \quad \Lambda \vdash d e f o p s \triangleright \Lambda ^ { \prime } } { \vdash d e c l s ; d e f o p s \triangleright \Lambda ^ { \prime } } \quad \right |$$

⊢ decls ; defops ▷ Λ

Figure 13. Cassiopea semantics for machines.

(they are properties on initial and final register and memory states), and the last one says what it means for a program to satisfy a specification. Note that the reg modify -and mem modify -rules as shown are slightly misleading, because the register and pointer list provided in the input specification is implicitly augmented with all registers and pointers mentioned in the postcondition before it gets to this point.

The intended behavior is that a program that fails during execution (that is, the body of one of its instructions steps to crash ) enters a state where no postcondition can evaluate to true . We have decided for the moment that working this explicitly into the formalism would result in a lot of complication and obscuration without providing any useful information.

Specifications. For specifications we need three judgments, shown in Figure 15: the first two state what the reg modify -and mem modify -clauses mean, respectively

Machine State Validity. As discussed above, we do not initialize the machine state while processing declarations. Instead we treat the starting machine state as an input (e.g., in the final judgment about programs) or quantify it universally as in the specification judgment. This requires that we have a predicate to reject machine states that do not match the machine description. The validity judgment has the form Δ Γ Λ , , ⊢ 𝜌 , shown in Figure 16, and correspondingly for 𝜎 (except without Λ ) and then for 𝜌, 𝜎 (both stores at once). This means that the given stores match the given environments.

▷

Λ

[

x

↦

→ ]

v

∀

,

i

Λ

∀ i , Λ ⊢ ( 𝜌, 𝜎, e 𝑖 ) ⇓ v 𝑖

## (Program Semantics)

$$\frac { \Lambda ( x _ { o p } ) = \{ [ \, ], _ { \, \rightharpoonup } S ) \} \quad \Lambda \vdash ( \rho, \sigma, S ) \, \Downarrow ( \rho ^ { \prime }, \sigma ^ { \prime }, \text{skip}, \xi ) } { \Lambda \vdash ( \rho, \sigma, x _ { o p } ) \to ( \rho ^ { \prime }, \sigma ^ { \prime }, \xi ) }$$

Λ

(

xop

)

=

{

x

′

𝑖

,

\_

, 𝑆

}

Λ

=

Λ

[∀

𝑖,

x

𝑖

↦

→

v

𝑖

]

Λ ′ ⊢ ( 𝜌, 𝜎, S ) ⇓ ( 𝜌 , 𝜎 ′ ′ , skip , 𝜉 )

Λ ⊢ ( 𝜌, 𝜎, xop e 𝑖 ) → ( 𝜌 , 𝜎 ′ ′ , 𝜉 )

Λ ⊢ ( 𝜌, 𝜎, inst ) → ( 𝜌 , 𝜎 ′ ′ , 𝜉 )

Λ ⊢ ( 𝜌, 𝜎, inst ; insts , ·) →( ′ 𝜌 , 𝜎 ′ , insts , 𝜉 ) Λ ⊢ ( 𝜌, 𝜎, inst ; insts , 0x01 ) → ( 𝜌, 𝜎, insts , ·)

0b C &gt; 0x01

Λ ⊢ ( 𝜌, 𝜎, inst ; insts , 0b C ) → ( 𝜌, 𝜎, insts , 0b C b -0x01 ) Λ

⊢ ( 𝜌, 𝜎, \_ , ext ) → ( 𝜌, 𝜎, 𝜖, ext )

⊢ machine ▷ Λ Λ ⊢ ( 𝜌, 𝜎, program , ·) → ∗ ( 𝜌 , 𝜎 ′ ′ , 𝜖, 𝜉 )

machine ⊢ ( 𝜌, 𝜎, program ) → ( 𝜌 , 𝜎 ′ ′ , 𝜉 )

Figure 14. Cassiopea semantics for programs.

## (Specification Semantics)

$$\frac { \forall i, \Lambda \vdash ( \rho, \sigma, x _ { r e g _ { i } } ) \, \Downarrow \, r _ { i } \quad \forall r \notin \{ \overline { r _ { i } } \}, \rho ( r ) = \rho ^ { \prime } ( r ) } { \Lambda, \rho, \sigma, \rho ^ { \prime }, \sigma ^ { \prime } \vdash r e g \text{-modif} y \, \colon \overline { x _ { r e g _ { i } } } }$$

xreg i xmemi

)) ⇓ (

,

C

𝑖

)

∀

xmem

,

C

,

(

xmem

,

C

)

∉

{(

xmemi

,

C

𝑖

)}

,

𝜎

((

xmem

Λ , 𝜌, 𝜎, 𝜌 ′ , 𝜎 ′ ⊢ mem modify -: ( xmemi , e 𝑖 )

$$\psi \, \sigma \, \omega ^ { \prime } \, \sigma ^ { \prime } \, \vdash \, f r a m e \quad \Lambda \, \omega \, \sigma \, \omega ^ { \prime } \, \sigma ^ { \prime } \, \vdash \, f r a$$

$$\frac { \Lambda, \rho, \sigma, \rho ^ { \prime }, \sigma ^ { \prime } \vdash \epsilon } { \Lambda, \rho, \sigma, \rho ^ { \prime }, \sigma ^ { \prime } \vdash \text{frame} \quad \Lambda, \rho, \sigma, \rho ^ { \prime }, \sigma ^ { \prime } \vdash \text{frames} } } { \Lambda, \rho, \sigma, \rho ^ { \prime }, \sigma ^ { \prime } \vdash \text{frame frames} }$$

↦

⊢ machine ; decls ▷ Δ Γ , ⊢ machine ▷ Λ ∀ 𝜌, 𝜎, ( Δ Γ Λ , , ⊢ 𝜌, 𝜎 ) = ⇒ Λ , ( 𝜌, 𝜎 ) ⊢ decls ▷ Λ ′ = ⇒ ′ [ Λ ′ ⊢ ( 𝜌, 𝜎, pre ) ⇓ true = ⇒ ∀ 𝜌 , 𝜎 ′ ′ , ( Λ ′ ⊢ ( 𝜌, 𝜎, program , ·) → ∗ ( 𝜌 , 𝜎 ′ ′ , 𝜖, 𝜉 )) = ⇒ ( Λ EXTBRANCH → ] ⊢ ( 𝜉 𝜌 , 𝜎 ′ ′ , post ) ⇓ true ∧ Λ ′ , 𝜌, 𝜎, 𝜌 ′ , 𝜎 ′ ⊢ frames )

machine , ( decls ; frame ; pre ; post ) ⊢ program

## Figure 15. Cassiopea semantics for specifications.

## (Machine State Validity)

(∀ x , Δ Γ , ⊢ x : C reg ∧ Λ ( x ) = r ) ⇔ (∃ v , 𝜌 ( r ) = v ∧ Δ Γ , ⊢ v : C bit )

Δ Γ Λ , , ⊢ 𝜌

∀ xmem , Δ Γ , ⊢ xmem : C 1 bit C 2 len C 3 ref ⇔ (∀ i ∈ { 0 , C 1 / 8 , . . . , ( C 2 -1 ) ∗ C 1 / 8 } , ∃ v , 𝜎 ( xmem , i ) = ( v C , 1 ) ∧ Δ Γ , ⊢ v : C 1 bit ) ∧ (∀ i ∉ { 0 , C 1 / 8 , . . . , ( C 2 -1 ) ∗ C 1 / 8 } , Λ ⊢ ( 𝜌, 𝜎, ( xmem , i )) ⇓ fail )

Δ Γ , ⊢ 𝜎

$$\frac { \Delta, \Gamma, \Lambda \vdash \rho \quad \Delta, \Gamma \vdash \sigma } { \Delta, \Gamma, \Lambda \vdash \rho, \sigma }$$

Figure 16. Cassiopea machine state validity.

⊢ (

𝜌, 𝜎,

(

xmemi

,

𝑖

e

,

C

))

=

𝜎

′

((

xmem

,

C

))

We use this with the typing environments that come from both the machine description and the additional declarations arising from a specification. In the case of registers we need access to Λ to handle the names of registers. We do not use the Λ generated from the additional declarations in a specification; this avoids circularity. This is acceptable, because specifications are not allowed to define new registers. For memory regions we need to enumerate the valid offsets for the region (note the literal 8 that hardwires 8-bit bytes) and check the cell width.

Branching. To handle branching, we allow statements to produce a branching state, which indicates the number of instructions to skip over. Normally this is · , which means none and has no effect; however, when an instruction body produces something else we use it to branch. A nonzero bitvector results in skipping the indicated number of instructions; the out-of-band additional value ext causes a branch to the external label. The magic number used to select the external label appears only in one of the statement rules; beyond that point we use ext explicitly.

The program rule in Figure 15 inserts the branch state produced by the program into Λ , where it provides a value for the branchto predicate found in the postcondition. This allows specifications to enforce the external branching behavior.

## 6 Alewife Overview

This section describes Alewife, our specification language for writing abstracted machine-independent specifications of low-level code.

Alewife specifications are abstractions of machine-level Cassiopea specifications; we say that Cassiopea constructs are lifted into Alewife and Alewife constructs are lowered into Cassiopea. Alewife is only for specifications, so there are no statements, no updates, and no notion of instructions or programs. We refer to the single synthesis problem in one Alewife specification as a block .

Notation. We use the following metavariables:

(Other constructions are referred to with longer names.) As noted previously, Alewife types and expressions should be considered distinct from Cassiopea ones (even where they correspond directly). We use the same letters in the hopes that this will cause less confusion (even in the definition of the translation) than picking an entirely different set of letters for Alewife.

| x , y , z   | Program variables (binders)             |
|-------------|-----------------------------------------|
| r           | Registers (abstract)                    |
| C           | Integer constants (written in decimal)  |
| 0b C        | Bitvector constants (written in binary) |
| N           | Symbolic integer constants              |
| 𝜏           | Types                                   |
| v           | Values                                  |
| e           | Expressions                             |
| i , j       | Rule-level integers                     |

Identifiers and Variables. In Alewife there are five syntactic categories of identifiers: As in Cassiopea, xmem name memoryregions. xfunc namefunctions, and x 𝜏 are type aliases. xmodule name Cassiopea lowering modules, which are used to instantiate the abstract elements and conduct AlewifeCassiopea translation for synthesis. Other x are ordinary variables that range over other things, and may be presumed to not range over the above reserved categories. All variables are immutable, in the sense that their values do not change once bound.

Symbolic Constants. In Alewife symbolic constants N are permitted to occur in some places where only integer constants are allowed in the corresponding Cassiopea constructions. In particular, the bit sizes associated with types (and the lengths of memory regions, which are functionally similar) may be given as symbolic values x instead of integer constants. These must be bound to integer constants either directly in the Alewife spec, in the Cassiopea lowering, or by the Cassiopea machine description. This allows the concrete sizes of bitvectors to vary depending on the machine architecture.

Types. As in Cassiopea, Alewife types are divided syntactically into base types and others, shown in Figure 17. The chief difference from Cassiopea is that bit widths (and the lengths of memory regions) can be symbolic constants.

Figure 17. Alewife symbolic constants, types, values, operators and expressions.

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-fe987475-9eaf-40db-a1d1-410e58ece20f.jpg)

However, an additional difference is that pointers ( ptr ) are distinguished from plain bitvectors ( vec ). This is reasonably possible in Alewife, because it need not reason about the progression of values through machine registers, only before and after states. Strings and unit are also absent, as they are not needed for specifications.

Values and Expressions. The values in Alewife correspond directly to the values in Cassiopea as do operators and most expressions, shown in Figure 17. Note that the width argument of fetch can be a symbolic size.

Declarations and Frames. Alewife declarations come in two forms: require and provide , shown in Figure 18. The second form declares elements in the ordinary way, while the first form declares an element that must be provided by the Cassiopea lowerings or the Cassiopea machine description. In this case, the type is given, but not the value. This functions as a form of import, and allows an Alewife file to be checked on its own separately from any particular machine description or Cassiopea lowerings. However, we do not currently define or implement such a check. Note that it is possible to require functions that implicitly depend on machine state or that depend on machine state on some machines and not others. Such functions can also depend on constants or other elements that are not visible in the Alewife specification at all. The lower with -declarations specify all lowering modules that are used to compile this Alewife specification into a Cassiopea specification. The module name xmodule is used to look up the Cassiopea lowering module to apply. The region declarations declare memory regions, like the memory-typed letstate declarations in Cassiopea. (These are implicitly always provide , because, for memory regions, the corresponding require declaration would be entirely equivalent, requiring duplication in the Cassiopea lowering.) Note that the parameters of the region can be symbolic constants if abstraction is needed.

Frame declarations in Alewife, annotated with reg modify -and mem modify -, are exactly the same as in Cassiopea. Because Alewife files are machine-independent, the registers mentioned must be abstract and concretized via the Cassiopea lowerings.

Block-lets. While Alewife expressions include letbindings, the scope of those let-bindings is conventional:

Figure 18. Alewife declarations, block-lets, and specifications.

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-71f3fa20-51af-47d6-ad42-7aeb07f9d087.jpg)

it lasts until the end of the expression. To refer to values taken from the initial state (that is, the machine state of which the precondition must be true), we need a way to bind these values so their scope extends to the postcondition. The block-lets serve this purpose in Alewife, shown in Figure 18, much like the additional declarations seen in Cassiopea specs can. These are found within a block (because a block corresponds to a synthesis problem, it is meaningful to associate before and after machine states with it), and the scope is the entire block.

Translation. The translation (lowering) from Alewife to Cassiopea, defined in the next section, appears crossrecursively in the rules in this section. Because Δ Γ , are Cassiopea environments, they map identifiers to Cassiopea types, not Alewife ones. This means Alewife types must be lowered on the fly to update them correctly.

Specifications. Afull specification starts with a preamble of declarations, shown in Figure 18. It also includes blocklets and the pre- and post-conditions for the block. Common declarations can be shared with include .

## 7 Alewife Typing and Semantics

Wedonotprovide (or implement) a full typechecking pass for Alewife. Instead, when we lower to Cassiopea, we allow the Cassiopea typechecker to reject invalid results, which might be caused by invalid Alewife input or by bad/mismatched Cassiopea lowering definitions. The rules provided here are for doing scans over the declarations sufficient to make the translation to Cassiopea work and no more.

Environments. We retain the Cassiopea typing environments, Δ Γ , . We add an additional environment Σ , which maps identifiers to integer constants. This is a projection of the Cassiopea execution environment Λ : it holds mappings only for variables defined as integer constants and excludes everything else. We include a separate set of rules for extracting these integer constants without doing a full Cassiopea execution. (Among other things, this avoids involving machine state or the machine state stores.)

Integer Constant Extraction. The integer constant extraction rules do a simple pass over Cassiopea declarations to extract the variables defined as integer constants, shown in Figure 19. These populate a substitution environment Σ that we use for lowering Alewife types containing symbolic constants. These rules are judgments of the form Σ ⊢ decl ▷ Σ ′ or Σ ⊢ decls ▷ Σ ′ , plus one of the form ⊢ machine ▷ Σ for a whole machine description.

Typing. The declaration typing rules are intended to accumulate types for all the declarations in an Alewife specification. They are applied concurrently with the Cassiopea declaration rules to the Alewife specification, the Cassiopea machine description, and the Cassiopea lowering. The declaration typing rules have judgments of the form Δ Γ Σ , , ⊢ decl ▷ Δ ′ , Γ ′ , Σ ′ and Δ Γ Σ , , ⊢ decls ▷ Δ ′ , Γ ′ , Σ ′ , shown in Figure 20. These mean that the declaration or declarations update the type environment (and integer constant environment) as shown. Note that there is a special-case rule for provide value for when the value is an integer constant; this enters the constant into Σ . The integer constants are in turn used when lowering the types of memory regions, which can be seen in the last two rules.

Block-lets. The rules for block-lets are effectively the same as the rules for declarations, shown in Figure 21. The ways in which block-lets are special mostly do not apply here. Note however that even though we pass through Σ (for consistency of the form of the rules) there is no rule

↦

Figure 19. Cassiopea integer constant extraction.

![image](https://ac.marcodsn.me/arxiv-markdown-images/1908.00093v5-figure-f38993ac-08a9-40a6-be91-7e965032604e.jpg)

| (Cassiopea Integer Constant Extraction)                        |                                  |
|----------------------------------------------------------------|----------------------------------|
| Σ ⊢ decl ▷ Σ ′ Σ ′ ⊢ decls ▷ Σ                                 | ′′ Σ ⊢ decls ▷ Σ ′               |
| Σ ⊢ 𝜖 ▷ Σ Σ ⊢ decl ; decls ▷ Σ ′′                              | ⊢ decls ; defops ▷ Σ ′           |
| Σ ′ = Σ [ x → C ]                                              | e ≠ C                            |
| Σ ⊢ type x 𝜏 = 𝜏 base ▷ Σ Σ ⊢ let x : int = C                  | ▷ Σ ′ Σ ⊢ let x : 𝜏 base = e ▷ Σ |
| Σ ⊢ def x func x 𝑖 : 𝜏 base 𝑖 → 𝜏 base = e ▷ Σ Σ ⊢ proc x proc | x 𝑖 : 𝜏 base 𝑖 →() = S ▷ Σ       |
| Σ ⊢ letstate x : 𝜏 reg ▷ Σ Σ ⊢ letstate                        | x mem : 𝜏 mem ▷ Σ                |
| Σ ⊢ letstate x mem : 𝜏 mem with x ▷                            | Σ Σ ⊢ let x . txt = e ▷ Σ        |

for loading integer constants into Σ from block-lets. Integer constants used in types and defined in the Alewife specification should be defined with provide value ; block-lets are intended to provide access to machine state.

Specifications. The rule for the semantics of an entire specification is large and complex. The conclusion is that a given machine, lowering module, and Alewife specification produce a final translation output Ω . The rules work by nondeterministically taking fixpoints over all the material included. decls is the combination of all declarations found both in the initial specification and all the included lowering modules, and frames is the combination of all frame information (part of the declarations in Alewife; separated in Cassiopea). Similarly, the final set of environments Γ Δ Σ , , represent fixpoints produced by processing all the declarations.

The fixpoint-based evaluation strategy for declarations is required, because the Alewife declarations rely on the Cassiopea lowering file (most notably for resolving symbolic constants), but the Cassiopea lowering file is in turn also specifically allowed to refer to objects declared by the Alewife specification, such as memory regions. In the implementation this circularity is resolved by lifting both the Cassiopea and Alewife declarations (and block-lets) into a common representation and topologically sorting them based on identifier references. (Genuinely circular references among identifiers are prohibited.) From this point, they can be handled in order in a more conventional way.

In Figure 22, the first premise expands the Alewife specification as we will need to work with the components. The next two premises generate initial environments: the Cassiopea typing environments induced by the machine description, and its integer constants, and then we require that these are included in the final environments. In the fifth and sixth premises, we then require that the result of processing the declarations from the specification appears in the final environments, and that the translation of these to Cassiopea is included in the final declarations and frame rules. Then for every lowering module requested by the specification, we require that it be provided in the input modules list and that its components appear in the final declarations and frame rules. This is followed by two more rules to ensure that these results are represented in the final environments. Later, we include the block-let material in the final environments, include its lowered form in the final declaration list (block-lets lower to declarations), bind the lowerings of the pre- and postconditions, and define the output.

Complete Output. Note that the output includes the declarations from the Cassiopea lowering modules (each declslower ). Apart from symbolic constants, we do not substitute the definitions of the lowering elements, as that would greatly complicate things, especially with functions; instead we include the definitions and let the translation refer to them. In fact, because of the declaration ordering issues, in the implementation the lowering declarations and translated Alewife declarations can be arbitrarily interleaved in the output.

Note furthermore that it would not be sufficient to include only the lowering declarations explicitly imported with require declarations, as those may refer freely to other things declared in the lowering module that the Alewife specification itself may have no cognizance of whatsoever.

↦

↦

↦

↦

↦

Figure 20. Alewife typing rules for declaration.

## 8 Lowering Alewife

The semantics of an Alewife specification depend on material taken from a Cassiopea mapping and machine description. This does not preclude defining a semantics for Alewife in terms of that material or even some abstracted concept of what any such Cassiopea material might be. However, doing so is complicated (as can be seen from the material in the previous section, which does not even attempt to handle expression evaluation) and not necessarily rewarding or illuminating.

So instead, we write only enough typing rules to prepare material for writing a translation (lowering) to Cassiopea, and then apply the Cassiopea typing (and, implicitly, semantics) to the lowered material. That material goes into the Cassiopea typing environments Δ Γ , , and as discussed in the previous section, we also maintain an additional environment Σ of integer constants used for substituting symbolic constants in types.

This section defines the translation. AC⟦ ⟧ 𝑎 defines the Cassiopea lowering of an Alewife element 𝑎 . We make the

↦

↦

↦

## (Alewife Specification Typing)

Δ Γ Σ , , ⊢ block let -▷ Δ ′ , Γ ′ , Σ ′ Δ ′ , Γ ′ , Σ ⊢ block lets -▷ Δ ′′ , Γ ′′ , Σ ′′

Δ Γ Σ , , ⊢ 𝜖 ▷ Δ Γ Σ , ,

Δ Γ Σ , , ⊢ block let -; block lets -▷ Δ ′′ , Γ ′′ , Σ ′′

↦

Δ Γ Σ , , ⊢ AC⟦ 𝜏 base ⟧ = 𝜏 Δ ⊢ wf 𝜏 Δ Γ Σ , , ⊢ AC⟦ ⟧ e = e ′ Δ Γ , ⊢ e ′ : 𝜏 Γ ′ = Γ [ x → ] 𝜏

Δ Γ Σ , , ⊢ let x : 𝜏 base = e ▷ Δ Γ , ′ , Σ

Δ Γ Σ , , ⊢ decls ▷ Δ , Γ , Σ

′ ′ ′ Δ ′ , Γ ′ , Σ ′ ⊢ block lets -▷ Δ ′′ , Γ ′′ , Σ ′′

Δ ′′ , Γ ′′ , Σ ⊢ AC⟦ pre ⟧ = pre ′ Δ ′′ , Γ ′′ , Σ ⊢ AC⟦ post ⟧ = post ′ Δ ′′ , Γ ′′ ⊢ pre ′ : bool

Δ Γ Σ , , ⊢ decls ; block lets -; pre ; post ▷ Δ ′′ , Γ ′′ , Σ ′′

Figure 21. Alewife typing rules for specifications.

## (Alewife Specification Semantics)

spec = declsale ; block lets - ; pre ; post

⊢ machine ▷ Δ 0 , Γ 0 ⊢ machine ▷ Σ 0 ( Δ 0 ⊆ Δ ) ∧ ( Γ 0 ⊆ Γ ) ∧ ( Σ 0 ⊆ Σ )

Δ Γ Σ , , ⊢ declsale ▷ Δ Γ Σ , ,

Δ Γ Σ

,

,

⊢ AC⟦

declsale

⟧ ⊆

decls

;

frames

(∀ xmodule , lower with - xmodule ∈ declsale

=

⇒

module xmodule { declslower ; frames lower } ∈ modules ∧ declslower ⊆ decls ∧ frames lower ⊆ frames )

Δ Γ

,

⊢

decls

▷

Δ Γ

,

Σ

⊢

decls

▷

Σ

Δ Γ Σ

,

,

⊢

block lets

-

▷

Δ Γ Σ

,

,

Δ Γ Σ , , ⊢ AC⟦ block lets - ⟧ ⊆ decls

Δ Γ Σ , , ⊢ AC⟦ pre ⟧ = pre ′

Δ Γ Σ , , ⊢ AC⟦ post ⟧ = post ′

Ω = decls ; frames ; pre ′ ; post ′

machine modules spec , , ▷ Ω

Figure 22. Alewife semantics for specifications.

## Alewife - Cassiopea Type Translation

$$\left | \Delta, \Gamma, \Sigma \vdash \mathcal { A } C [ N ] = \begin{cases} C & N = C \\ \Sigma ( x ) & N = x \wedge x \in \Sigma & \Delta, \Gamma, \Sigma \vdash \mathcal { A } C [ x _ { \tau } ] = \begin{cases} \Delta ( x _ { \tau } ) & x _ { \tau } \in \Delta \\ \perp & x _ { \tau } \notin \Delta \end{cases} \\ \sim \sim \sigma ^ { \prime } \cdot \cdot \cdot \cdot \cdot \begin{matrix} \Delta \\ \end{matrix} \end{cases} \right | \begin{matrix} \Delta ( x _ { \tau } ) & x _ { \tau } \in \Delta \\ \end{matrix} \right | \begin{matrix} \Delta \\ \perp & x _ { \tau } \notin \Delta \end{matrix}$$

AC⟦ bool ⟧ = bool

 AC⟦ int ⟧ = int

AC⟦ N vec ⟧ = AC⟦ N ⟧ bit

AC⟦ N ptr ⟧ = AC⟦ N ⟧ bit

AC⟦ N reg ⟧ = AC⟦ N ⟧ reg

AC⟦ N reg set ⟧ = AC⟦ N ⟧ reg set

AC⟦ N 1 bit N 2 len N 3 ref ⟧ = AC⟦ N 1 ⟧ bit AC⟦ N 2 ⟧ len AC⟦ N 3 ⟧ ref

$$\mathcal { A C } [ \overline { \tau _ { b a s e _ { i } } } \rightarrow \tau _ { b a s e } ] \right ] = \mathcal { A C } [ \tau _ { b a s e _ { i } } ] \rightarrow \mathcal { A C } [ \tau _ { b a s e } ]$$

Figure 23. Alewife - Cassiopea type translation.

translation polymorphic over the various kinds of element; that is, AC⟦ ⟧ 𝜏 is the translation of a type (shown in Figure 23), AC⟦ ⟧ e is the translation of an expression, etc. Some of the translation rules rely on the environments; these are written Δ Γ Σ , , ⊢ AC⟦ ⟧ 𝑎 (shown in Figure 24).

Notice that the translations for require declarations are empty, because the declarations from the mapping module are output along with the translated Alewife specification.

Some of the translation rules produce ⊥ . If these are reached, the translation fails; this can happen if the Alewife spec was malformed and, potentially, if the mapping module failed to declare elements that were expected of it or declared them in an incompatible or inconsistent way. The rules in the previous section exclude some of these cases, but we are not (yet) prepared to argue that they rule out all translation-time failures.

Δ ′′ , Γ ′′ ⊢ post ′ : bool

## Alewife - Cassiopea Expression Translation

```
                                                                                                                                                                                                       
                                                                                                                                                                                                       

                                                                                                                                                                                                        }
                                                                                                                                                                                                       }

                                                                                                                                                                                                       }                                                                                                                                                                                                        <?                                                                                                                                                                                                        </?
```

Figure 24. Alewife - Cassiopea translation.

## 9 Conclusion

In this technical report, we described two domain specific languages involved in the Aquarium kernel synthesis project. First, we presented a machine modeling language named

Cassiopea that can be used used to describe the semantics of many different processor ISAs at the assembly language level. Then, we presented a specification language named Alewife that allows stating abstract specifications for blocks of assembly code, such that these abstract specifications can

be lowered to concrete specifications and used for synthesis and verification against Cassiopea machine descriptions.

We note that this is work in progress, and does not yet present a final or complete view of either the Aquarium system or the languages presented.
David A. Holland Jingmei Hu Ming Kawaguchi Eric Lu Stephen Chong Margo I. Seltzer
Aquarium: Cassiopea and Alewife Languages
This technical report describes two of the domain specific languages used in
the Aquarium kernel code synthesis project. It presents the language cores in
terms of abstract syntax. Cassiopea is a machine description language for
describing the semantics of processor instruction sets. Alewife is a
specification language that can be used to write machine-independent
specifications for assembly-level instruction blocks. An Alewife specification
can be used to verify and synthesize code for any machine described in
Cassiopea, given a machine-specific translation for abstractions used in the
specification. This article does not include an introduction to either the
Aquarium system or the use of the languages. In addition to this version of the
article being a draft, the Aquarium project and the languages are works in
progress. This article cannot currently be considered either final or complete.