diff options
author | Greg Brown <greg.brown@cl.cam.ac.uk> | 2022-05-17 17:39:22 +0100 |
---|---|---|
committer | Greg Brown <greg.brown@cl.cam.ac.uk> | 2022-05-17 17:39:22 +0100 |
commit | 7a9d577ec36d7b7499483ed366f9855d27a31bad (patch) | |
tree | 4a7257a0bbdd05053f620cb16778d08984d2ea7c | |
parent | 915209899560b58e86aa7f164d37b823af958633 (diff) |
Fresh start.
-rw-r--r-- | thesis.bib | 14 | ||||
-rw-r--r-- | thesis.org | 2092 |
2 files changed, 286 insertions, 1820 deletions
@@ -144,3 +144,17 @@ date = {2019-12}, pages = {751–807}, numpages = {57} } + +@InProceedings{10.1007/3-540-47721-7_24, +author="Barrett, Paul", +editor="Odlyzko, Andrew M.", +title="Implementing the Rivest Shamir and Adleman Public Key Encryption Algorithm on a Standard Digital Signal Processor", +booktitle="Advances in Cryptology --- CRYPTO' 86", +date="1987", +publisher="Springer Berlin Heidelberg", +address="Berlin, Heidelberg", +pages="311--323", +abstract="A description of the techniques employed at Oxford University to obtain a high speed implementation of the RSA encryption algorithm on an ``off-the-shelf'' digital signal processing chip. Using these techniques a two and a half second (average) encrypt time (for 512 bit exponent and modulus) was achieved on a first generation DSP (The Texas Instruments TMS 32010) and times below one second are achievable on second generation parts. Furthermore the techniques of algorithm development employed lead to a provably correct implementation.", +isbn="978-3-540-47721-1", +doi = {10.1007/3-540-47721-7_24} +} @@ -1,10 +1,15 @@ #+options: ':t *:t -:t ::t <:t H:4 \n:nil ^:t arch:headline author:t #+options: broken-links:nil c:nil creator:nil d:(not "LOGBOOK") date:t e:t #+options: email:nil f:t inline:t num:t p:nil pri:nil prop:nil stat:t tags:t -#+options: tasks:t tex:t timestamp:t title:t toc:t todo:t |:t +#+options: tasks:t tex:t timestamp:t title:t toc:nil todo:t |:t + #+title: Semantics of an embedded vector architecture for formal verification of software -#+date: \today +#+date: May 2022 #+author: Greg Brown +#+latex_header: \newcommand{\candidatenumber}{2487C} +#+latex_header: \newcommand{\college}{Queens' College} +#+latex_header: \newcommand{\course}{Computer Science Tripos, Part III} + #+email: greg.brown@cl.cam.ac.uk #+language: en-GB #+select_tags: export @@ -12,16 +17,21 @@ #+creator: Emacs 27.2 (Org mode 9.6) #+cite_export: biblatex #+bibliography: ./thesis.bib + #+latex_class: thesis -#+latex_class_options: [twoside,a4paper,11pt] -#+latex_header: \usepackage[hyperref=true,url=true,backend=biber,natbib=true]{biblatex} -#+latex_header: \usepackage[moderate]{savetrees} -#+latex_header: \usepackage[a4paper]{geometry} -#+latex_header: \usepackage{minted} -#+latex_header: \usepackage{newunicodechar} -#+latex_header: \usepackage{mathtools} -#+latex_header: \usepackage{stmaryrd} -#+latex_header: \usepackage[english=british]{csquotes} +#+latex_class_options: [12pt,a4paper,twoside] + +#+latex_header: \usepackage[hyperref=true,url=true,backend=biber,natbib=true]{biblatex} % citations +#+latex_header: \usepackage[vmargin=20mm,hmargin=25mm]{geometry} % page margins +#+latex_header: \usepackage{minted} % code snippets +#+latex_header: \usepackage{parskip} % vertical space for paragraphs +#+latex_header: \usepackage{setspace} % line spacing +#+latex_header: \usepackage{newunicodechar} % unicode in code snippets +#+latex_header: \usepackage{mathtools} % a math character? +#+latex_header: \usepackage{stmaryrd} % some math characters +#+latex_header: \usepackage{refcount} % for counting pages +#+latex_header: \usepackage{upquote} % for correct quotation marks in verbatim text + #+latex_compiler: pdflatex #+latex_header: \newunicodechar{Γ}{\ensuremath{\Gamma}} @@ -38,6 +48,7 @@ #+latex_header: \newunicodechar{₂}{\ensuremath{_2}} #+latex_header: \newunicodechar{ₛ}{\ensuremath{_\texttt{s}}} #+latex_header: \newunicodechar{ₜ}{\ensuremath{_\texttt{t}}} + #+latex_header: \newunicodechar{ℓ}{l} #+latex_header: \newunicodechar{ℕ}{\ensuremath{\mathbb{N}}} #+latex_header: \newunicodechar{ℚ}{\ensuremath{\mathbb{Q}}} @@ -69,1887 +80,328 @@ #+latex_header: \newunicodechar{⦄}{\}\}} #+latex_header: \newunicodechar{𝕀}{\ensuremath{\mathbb{I}}} -#+latex_header: %TC:envir minted 1 other - -#+begin_abstract -All good implementations of any algorithm should be correct and fast. To -maximise performance some algorithms are written in hand-tuned assembly. This -can introduce subtle bugs that invalidate correctness or other safety -properties. Whilst tools exists to help formally verify these algorithms, none -are designed to target the recent M-profile Vector Extension for the Armv8.1-M -architecture. This work describes a denotational and Hoare logic semantics for -the language used to specify the instructions, and attempts to use them to -formally verify the correctness of hand-written assembly for cryptographic -applications. -#+end_abstract - -# Flip this all around. What am I doing? What is the stuff? Why is it hard? (Why -# am I smart?) -* Introduction +#+latex_header: %TC:envir minted 1 ignore -# Merge these two paras -In nearly all cases, the best implementation of an algorithm for a particular -purpose is both correct and fast. If the implementation is not correct, then it -does not implement the algorithm. If the implementation is slow, then resources are -wasted executing this algorithm over performing other useful work. Ensuring -implementations are both performant and correct is typically difficult. - -One case that proves particularly tricky is writing assembly code. -# dubious claim -Most modern handwritten assembly is for cryptographic algorithms, where it is -vital that compilers do not introduce timing attacks or other side channels. -Hand written assembly can also take advantage of microarchitectural differences -to gain a slight performance benefit. - -Another domain that frequently deals with assembly code is the output of -compilers. Optimising compilers take the semantic operations described by -high-level code and attempt to produce the optimal machine code to perform those -actions. - -In both of these cases, either humans or machines shuffle around instructions in -an attempt to eek out the best performance from hardware. The resulting code -warps the original algorithms in ways that make it difficult to determine -whether the actions performed actually have the same result. The correctness of -the assembly code comes into question. - -It is only possible to prove the assembly code is correct if there is a model -describing how it should behave. Suitable mathematical models of the behaviour -allow for the construction of formal proofs certifying the assembly code has the -correct behaviour. Due to the size and complexity of different instruction set -architectures (ISAs), formal verification of software in this manner makes use -of a number of software tools. - -Formal verification software is well-developed for the x86-64 and Arm A-profile -ISAs. For instance, Jasmin [cite:@10.1145/3133956.3134078] is a programming -language which verifies that compiling to x86-64 preserves a set of user-defined -safety properties. Another similar tool is CompCert [cite:@hal/01238879] which -is a verified C compiler, ensuring that the program semantics are preserved -during compilation. - -Unfortunately, little work has been done to formally verify software written for -the Arm M-profile architecture. This architecture is designed for low-power and -low-latency microcontrollers, which operate in resource-constrained -environments. - -The goal of this project is to formally verify an assembly function for the Arm -M-profile architecture. The specific algorithm -[cite:@10.46586/tches.v2022.i1.482-505] is a hand-written highly-optimised -assembly-code implementation of the number-theoretic transform, which is an -important procedure for post-quantum cryptography. - -This work has made progress on two fronts. Most work has been spent developing -an embedding and semantic model of the Armv8.1-M pseudocode specification -language for the Agda programming language [cite:@10.1007/978-3-642-03359-9_6]. -All instructions in the Armv8.1-M architecture are given a "precise description" -[cite:@arm/DDI0553B.s §\(\texttt{I}_\texttt{RTDZ}\)] using the pseudocode, and -by developing an embedding within Agda, it is possible to use its powerful type -system to construct formal proofs of correctness. - -Progress has also been made on the formal algebraic verification of Barrett -reduction, a subroutine of the NTT. Whilst there exists a paper proof of the -correctness of Barrett reduction, a proof of Barrett reduction within Agda is -necessary to be able to prove the correctness of the given implementation of the -NTT. - -# Focus on contributions -# Structure of the paper: -# - [[Armv8.1-M Pseudocode Specification Language]] describes the Armv8.1-M pseudocode -# specification language. This is an imperative programming language used by the -# Armv8.1-M manual to describe the operation of the instructions. -# - A simple recap of Hoare logic is given in [[Hoare Logic]]. This is the backbone of -# one of the formal verification techniques used in this work. -# - [[Formal Definition of MSL]] contains a description of MSL. This is a language -# similar to the Armv8.1-M pseudocode embedded within the Agda programming -# language. -# - The denotational semantics and a Hoare logic semantics of MSL are detailed in -# [[Semantics of MSL]]. Due to Agda's nature of being a dependently-typed language, -# these are both given using Agda code. -# - [[Application to Proofs]] describes the experience of using the Hoare logic and -# denotational semantics of MSL to prove the correctness of some simple -# routines, given in [[Proofs using Hoare Semantics]] and [[Proofs using Denotational -# Semantics]] respectively. -# - Formal verification of Barrett reduction, an important subroutine in the NTT, -# is given in [[Proofs of Barrett Reduction]]. In particular, it proves that Barrett -# reduction performs a modulo reduction and gives bounds on the size of the -# result. -# - Finally, [[Future Work]] describes the steps necessary to complete the formal -# verification of the NTT, as well as listing some other directions this work -# can be taken. +#+latex_header: \newif\ifsubmission -* Background -The Armv8.1-M pseudocode specification language is used to describe the -operation of instructions in the Armv8-M instruction set [cite:@arm/DDI0553B.s §E1.1]. If the semantics of this pseudocode can be formalised, then it is -possible to formulate proofs about the action of all Armv8-M instructions, and -hence construct proofs about the algorithms written with them. The language is -rather simple, being pseudocode used as a descriptive aid, but has some -interesting design choices atypical of regular imperative programming languages. - -As the pseudocode is an imperative language, one useful proof system for it is -Hoare logic. Hoare logic is a proof system driven by the syntax of a program, -with most of the hard work of proofs being in the form of choosing suitable -program invariants and solving simple logical implications -[cite:@10.1145/363235.363259]. As the logic is syntax driven, proofs using Hoare -logic are less impacted than other proof systems by the large number of loops -used in Armv8-M instruction descriptions. Further, solving simple logical -implications is a task well-suited to Agda and other proof assistants, making -proofs even simpler to construct. - -** Armv8.1-M Pseudocode Specification Language -The Armv8.1-M pseudocode specification language is a strongly-typed imperative -programming language [cite:@arm/DDI0553B.s §E1.2.1]. It has a first-order type -system, a small set of operators and basic control flow, as you would find in -most imperative languages. There are some interesting design choices atypical of -regular imperative languages to better fulfil the requirements of being a -descriptive aid over an executable language. - -Something common to nearly all imperative languages is the presence of a -primitive type for Booleans. Other typical type constructors are tuples, -structs, enumerations and fixed-length arrays. The first interesting type used -by the pseudocode is mathematical integers as a primitive type. Most imperative -languages use fixed-width integers for primitive types, with exact integers -available through some library. This is because the performance benefits of -using fixed-width integers in code far outweigh the risk of overflow. However, -as the pseudocode is a descriptive aid, with no intention of being executed, it -can use exact mathematical integers and eliminate overflow errors without any -performance cost [cite:@arm/DDI0553B.s §E1.3.4]. - -Another such type present in the pseudocode is mathematical real numbers. As -most real numbers are impossible to record using finite storage, any executable -programming language must make compromises to the precision of real numbers. -Because the pseudocode does not concern itself with being executable, it is free -to use real numbers and have exact precision in real-number arithmetic -[cite:@arm/DDI0553B.s §E1.2.4]. - -The final primitive type used by the pseudocode is the bitstring; a fixed-length -sequence of 0s and 1s. Some readers may wonder what the difference is between -this type and arrays of Booleans. The justification given by -[cite/t:@arm/DDI0553B.s §E1.2.2] is more philosophical than practical: -"bitstrings are the only concrete data type in pseudocode". In some places, -bitstrings can be used instead of integers in arithmetic operations. - -Most of the operators used by the pseudocode are unsurprising. For instance, -Booleans have the standard set of short-circuiting operations; integers and -reals have addition, subtraction and multiplication; reals have division; and -integers have integer division (division rounding to \(-\infty\)) and modulus -(the remainder of division). - -By far the two most interesting operations in the pseudocode are bitstring -concatenation and slicing. Bitstring concatenation is much like appending two -arrays together, or regular string concatenation. Bitstring slicing is a more -nuanced process. Slicing a bitstring by a single index is no different from a -regular array access. If instead a bitstring is sliced by a range of integers, -the result is the concatenation of each single-bit access. Finally, when -integers are sliced instead of bitstring, the pseudocode "treats an integer as -equivalent to a sufficiently long \textelp{} bitstring" [cite:@arm/DDI0553B.s -§E1.3.3]. - -The final interesting difference between the pseudocode and most imperative -languages is the variety of top-level items. The pseudocode has three forms of -items: procedures, functions and array-like functions. Procedures and functions -behave like procedures and functions of other imperative languages. The -arguments to them are passed by value, and the only difference between the two -is that procedures do not return values whilst functions do -[cite:@arm/DDI0553B.s §E1.4.2]. - -Array-like functions act as getters and setters for machine state. Every -array-like function has a reader form, and most have a writer form. This -distinction exists because "reading from and writing to an array element require -different functions", [cite:@arm/DDI0553B.s §E1.4.2], likely due to the nature -of some machine registers being read-only instead of read-writeable. The writer -form acts as one of the targets of assignment expressions, along with variables -and the result of bitstring concatenation and slicing [cite:@arm/DDI0553B.s -§E1.3.5]. +# Uncomment when anonymous +# #+latex_header: \submissiontrue -** Hoare Logic -Hoare logic is a proof system for programs written in imperative programming -languages. At its core, the logic describes how to build partial correctness -triples, which describe how program statements affect assertions about machine -state. The bulk of a Hoare logic derivation is dependent only on the syntax of -the program the proof targets. - -A partial correctness triple is a relation between a precondition \(P\), a -program statement \(s\) and a postcondition \(Q\). If \((P , s , Q)\) is a -partial correctness triple, then whenever \(P\) holds for some machine state, -then when executing \(s\), \(Q\) holds for the state after it terminates -[cite:@10.1145/363235.363259]. Those last three words, "after it terminates", -are what leads the relation being a /partial/ correctness triple. If all -statements terminate, which we will see later, then this relation is called a -correctness triple. - -Along with the syntactic rules for derivations, Hoare logic typically also -features a number of adaptation rules. The most-widely known of these is the -rule of consequence, which can strengthen the precondition and weaken the -postcondition. This requires an additional logic for assertions. Typically, this -is first-order or higher-order logic -[cite:@10.1007/s00165-019-00501-3;@10.1007/s001650050057]. - -One vital feature of Hoare logic with regards to specification is auxiliary -variables. These are variables that cannot be used by programs, hence remain -constant between the precondition and postcondition -[cite:@10.1007/s001650050057]. - -* The MIPSL Language -The ultimate goal of this project is to formally verify an implementation of the -number theoretic transform using the M-profile vector extension of the Armv8.1-M -architecture. Work has proceeded on two fronts. This chapter discusses the -first; a formal definition of a language similar to the Armv8-M pseudocode -specification language, which I will call MIPSL (MIPSL imitates pseudocode -specification language). This definition exists within Agda, allowing for the -use of Agda's dependent-typing features to construct proofs about programs -written in MIPSL. - -To construct proofs about how MIPSL behaves, it is necessary to describe its -semantics. This is done through providing a denotational semantics. Denotational -semantics interpret program expressions and statements as mathematical -functions, something which Agda is well-suited to do. - -One downside of denotational semantics is that control flow for looping -constructs is fully evaluated. This is inefficient for loops that undergo many -iterations. This can be resolved by a syntax-directed Hoare logic for MIPSL. -Hoare logic derivations assign a precondition and a postcondition assertion to -each statement. These are chained together though a number of simple logical -implications. - -** Definition of MIPSL -MIPSL is a language similar to the Armv8-M pseudocode specification language -written entirely in Agda. Unfortunately, the pseudocode has a number of small -features that make it difficult to work with in Agda directly. MIPSL makes a -number of small changes to the pseudocode to better facilitate this embedding, -typically generalising existing features of the pseudocode. - -*** MIPSL Types - -#+name: MIPSL-types -#+caption: The Agda datatype representing the types present in MIPSL. Most have -#+caption: a direct analogue in the Armv8-M pseudocode specification language -#+attr_latex: :float t -#+begin_src agda2 -data Type : Set where - bool : Type - int : Type - fin : (n : ℕ) → Type - real : Type - tuple : Vec Type n → Type - array : Type → (n : ℕ) → Type -#+end_src +#+begin_src elisp :exports results :results none :eval export +(make-variable-buffer-local 'org-latex-title-command) +(setq org-latex-title-command +" +%%TC:ignore -[[MIPSL-types]] gives the Agda datatype representing the types of MIPSL. Most of -these have a direct analogue to the pseudocode types. For example, ~bool~ is a -Boolean, ~int~ mathematical integers, ~real~ is for real numbers and ~array~ -constructs array types. Instead of an enumeration construct, MIPSL uses the ~fin -n~ type, representing a finite set of ~n~ elements. Similarly, structs are -represented by ~tuple~ types. +\\begin{sffamily} -The most significant difference between the pseudocode and MIPSL is the -representation of bitstrings. Whilst the pseudocode has the ~bits~ datatype, -MIPSL instead treats bitstrings as an array of Booleans, demonstrated by this -code fragment: +\\begin{titlepage} -#+begin_src agda2 -bit : Type -bit = bool +\\makeatletter +\\hspace*{-14mm}\\includegraphics[width=65mm]{logo-dcst-colour} -bits : ℕ → Type -bits = array bit -#+end_src +\\ifsubmission -This removes the distinction between arrays and bitstrings, and allows a number -of operations to be generalised to work on both types. This makes MIPSL more -expressive than the pseudocode, in the sense that there are a greater number and -more concise ways to write programs that are functionally equivalent. - -#+name: MIPSL-type-properties -#+caption: Prototypes for the three predicates on types. -#+attr_latex: :float t -#+begin_src agda2 -data HasEquality : Type → Set -data Ordered : Type → Set -data IsNumeric : Type → Set -#+end_src +%% submission proforma cover page for blind marking +\\begin{Large} +\\vspace{20mm} +Research project report title page -The pseudocode implicitly specifies three different properties of types: equality -comparisons, order comparisons and arithmetic operations. Whilst the types -satisfying these properties need to be listed explicitly in Agda, using instance -arguments allows for these proofs to be elided whenever they are required. -[[MIPSL-type-properties]] gives the type signatures of the three predicates. - -MIPSL has only two differences in types that satisfy these properties compared -to the pseudocode. First, all array types have equality as long as the -enumerated type also has equality. This is a natural generalisation of the -equality between types, and allows for the MIPSL formulation of bitstrings as -arrays of Booleans to have equality. Secondly, finite sets also have ordering. -This change is primarily a convenience feature for comparing finite representing -a subset of integers. As the pseudocode has no ordering comparisons between -enumerations, this causes no problems for converting pseudocode programs into -MIPSL. - -#+name: MIPSL-type-addition -#+caption: Definition of the "type addition" feature of MIPSL. -#+attr_latex: :float t -#+begin_src agda2 -_+ᵗ_ : IsNumeric t₁ → IsNumeric t₂ → Type -int +ᵗ int = int -int +ᵗ real = real -real +ᵗ t₂ = real -#+end_src +\\vspace{35mm} +Candidate \\candidatenumber -The final interesting feature of the types in MIPSL is "type addition". As -pseudocode arithmetic is polymorphic for integers and reals, MIPSL needs a -function to decide the type of the result. [[MIPSL-type-addition]] gives the -definition of this function. - -*** MIPSL Expressions - -#+name: MIPSL-literalType -#+caption: Mappings from MIPSL types into Agda types which can be used as -#+caption: literal values. ~literalTypes~ is a function that returns a product -#+caption: of the types given in the argument. -#+begin_src agda -literalType : Type → Set -literalType bool = Bool -literalType int = ℤ -literalType (fin n) = Fin n -literalType real = ℤ -literalType (tuple ts) = literalTypes ts -literalType (array t n) = Vec (literalType t) n -#+end_src +\\vspace{42mm} +\\textsl{\`\`\\@title\'\'} -Unlike the pseudocode, where only a few types have literal expressions, every -type in MIPSL has a literal form. This mapping is part of the ~literalType~ -function, given in [[MIPSL-literalType]]. Most MIPSL literals accept the -corresponding Agda type as a value. For instance, ~bool~ literals are Agda -Booleans, and ~array~ literals are fixed-length Agda vectors of the -corresponding underlying type. The only exception to this rule is for ~real~ -values. As Agda does not have a type representing mathematical reals, integers -are used instead. - -# TODO: why is this sufficient? - -#+name: MIPSL-expr-prototypes -#+caption: Prototypes of the numerous MIPSL program elements. Each one takes two -#+caption: variable contexts: ~Σ~ for global variables and ~Γ~ for local variables. -#+attr_latex: :float t -#+begin_src agda -data Expression (Σ : Vec Type o) (Γ : Vec Type n) : Type → Set -data Reference (Σ : Vec Type o) (Γ : Vec Type n) : Type → Set -data LocalReference (Σ : Vec Type o) (Γ : Vec Type n) : Type → Set -data Statement (Σ : Vec Type o) (Γ : Vec Type n) : Set -data LocalStatement (Σ : Vec Type o) (Γ : Vec Type n) : Set -data Function (Σ : Vec Type o) (Γ : Vec Type n) (ret : Type) : Set -data Procedure (Σ : Vec Type o) (Γ : Vec Type n) : Set -#+end_src - -One important benefit of using Agda to write MIPSL is that expressions and -statements can have the MIPSL types of variables be part of the Agda type. -This makes checking type safety of variable usage trivial. - -For reasons which will be discussed later, MIPSL uses two variable contexts, ~Σ~ -for global variables and ~Γ~ for local variables. [[MIPSL-expr-prototypes]] lists -the prototypes for the various MIPSL program elements. The full definitions of -these types are given in [[*MIPSL Syntax Definition]]. - -An ~Expression~ in MIPSL corresponds with expressions in the pseudocode. Many -operators are identical to those in the pseudocode (like ~+~, ~*~, ~-~), and -others are simple renamings (like ~≟~ instead of ~==~ for equality comparisons). -Unlike the pseudocode, where literals can appear unqualified, MIPSL literals -are introduced by the ~lit~ constructor. - -The most immediate change for programming in MIPSL versus the pseudocode is how -variables are handled. Because the ~Expression~ type carries fixed-length -vectors listing the MIPSL types of variables, a variable is referred to by its -index into the context. For example, a variable context \(\{x \mapsto -\mathrm{int}, y \mapsto \mathrm{real}\}\) is represented in MIPSL as the -context ~int ∷ real ∷ []~. The variable \(x\) is then represented by ~var 0F~ in -MIPSL. Because the global and local variable contexts are disjoint for the -~Expression~ types, variables are constructed using ~state~ or ~var~ respectively. - -Whilst this decision introduces much complexity to programming using MIPSL, it -greatly simplifies the language for use in constructing proofs. For instance, by -referring to variables by their index in the variable context, variable -shadowing, where two variables share a name, is completely eliminated from -MIPSL. - -MIPSL expressions also add a number of useful constructs to the pseudocode type. -One such pair is ~[_]~ and ~unbox~, which construct and destruct an array of -length one respectively. Another pair are ~fin~, which allows for arbitrary -computations on elements of finite sets, and ~asInt~, which converts a finite -value into an integer. - -The three MIPSL operators most likely to cause confusion are ~merge~, ~slice~ -and ~cut~. ~slice~ is analogous to bitstring slicing from the pseudocode, but -has some important differences. Instead of operating on bitstrings and integers, -~slice~ acts on ~array~ types. Integer slicing can be expressed using the -other operators of MIPSL, and in MIPSL a bitstring is a form of array. Another -difference is that the number of bits to take from the string is not specified -by a range, but is instead inferred from the output type. This is only possible -due to the powerful type inference features of Agda. Finally, the user only -needs to provide a single index into the array. By giving the index a ~fin~ -datatype, and taking care with the length of arrays, MIPSL can guarantee that -the array indices are always in bounds for the entire range that is required. - -The ~cut~ operation is an opposite to ~slice~. Whilst ~slice~ takes a contiguous -range of elements from the middle of an array, ~cut~ returns the two ends of the -array outside of the range. Finally, ~merge~ is a generalisation of -concatenation that allows inserting one array into another. These three -operations are part of an equivalence; for any arrays ~x~ and ~y~ and an -appropriate index ~i~, we have the following equalities: -- ~merge (slice x i) (cut x i) i ≟ x~ -- ~slice (merge x y i) i ≟ x~ -- ~cut (merge x y i) i ≟ y~ - -The ~Reference~ type is the name MIPSL gives to assignable expressions from the -pseudocode. The ~LocalReference~ type is identical to ~Reference~, except it -does not include global variables. In an earlier form of MIPSL, instead of -separate types for assignable expressions which can and cannot assign to state, -there were two predicates. However, this required carrying around a proof that -the predicate holds with each assignment. Whilst the impacts on performance were -unmeasured, it made proving statements with assignable expressions significantly -more difficult. Thankfully, Agda is able to resolve overloaded data type -constructors without much difficulty, meaning the use of ~Reference~ and -~LocalReference~ in MIPSL programs is transparent. - -**** Example MIPSL Expressions -For those who have read [[*MIPSL Syntax Definition]], you may notice that whilst the -pseudocode defines a left-shift operator, there is no such operator in MIPSL. -Left shift can be defined in the following way: - -#+begin_src agda2 -_<<_ : Expression Σ Γ int → (n : ℕ) → Expression Σ Γ int -e << n = e * lit (ℤ.+ (2 ℕ.^ n)) -#+end_src +\\end{Large} -This simple-looking expression has a lot of hidden complexity. First, consider -the type of the literal statement. The unary plus operation tells us that the -literal is an Agda integer. However, there are two MIPSL types with Agda -integers for literal values: ~int~ and ~real~. How does Agda correctly infer the -type? Recall that multiplication is polymorphic in MIPSL, with the result type -determined by "type addition" ([[MIPSL-type-addition]]). Agda knows that the -multiplication must return an ~int~, and that the first argument is also an -~int~, so by following the definition of ~_+ᵗ_~, it can infer that the second -multiplicand is an integer literal. - -The previous section claimed that it is possible to define integer slicing -entirely using the other operations in MIPSL. Here is an expression that slices -a single bit from an integer, following the procedure by [cite/t:@arm/DDI0553B.s -§E1.3.3]: - -#+begin_src agda2 -getBit : ℕ → Expression Σ Γ int → Expression Σ Γ bit -getBit i x = - if x - x >> suc i << suc i <? lit (ℤ.+ (2 ℕ.^ i)) - then lit false - else lit true -#+end_src +\\else -The left-side of the inequality finds the residual of ~x~ modulo \(2 ^ {i+1}\). -Note that right-shift is defined to always round values down hence the modulus -is always positive. If the modulus is less than \(2^i\), then the bit in the -two's complement representation of ~x~ is ~0~, otherwise it is ~1~. This is -reflected in the output values of ~lit false~ and ~lit true~ respectively. - -The last expression I will present is the ~uint~ function, which converts a -bitstring into an unsigned integer. - -#+begin_src agda2 -uint : Expression Σ Γ (bits m) → Expression Σ Γ int -uint {m = 0} x = lit ℤ.0ℤ -uint {m = 1} x = - if unbox x - then lit ℤ.1ℤ - else lit ℤ.0ℤ -uint {m = suc (suc m)} x = - lit (ℤ.+ 2) * uint (slice x (lit 1F)) + - uint (cut x (lit 1F)) -#+end_src +%% regular cover page +\\begin{center} +\\Huge +\\vspace{\\fill} -This Agda function is inductive on the number of bits in the argument. In the -first case, the unique zero-length bitstring is represented by the constant -\(0\). In the second case, a single bit can be represented by a conditional -statement: the integer \(1\) if the bit is true and \(0\) otherwise. If there -are two or more bits, the resulting expression sums two expressions. The left -addend is twice the recursive call on the high bits of the input. The right -addend is the representation of the least-significant bit as an integer. - -In more concrete terms, here's the result of ~uint~ on a 4-bit string: - -#+begin_src agda2 -lit 2 * -(lit 2 * - (lit 2 * - (if unbox (slice (slice (slice x (lit 1F)) (lit 1F)) (lit 1F)) - then lit 1 - else lit 0) - + - (if unbox (cut (slice (slice x (lit 1F)) (lit 1F)) (lit 1F)) - then lit 1 - else lit 0)) - + - (if unbox (cut (slice x (lit 1F)) (lit 1F)) - then lit 1 - else lit 0)) -+ -(if unbox (cut x (lit 1F)) - then lit 1 - else lit 0) -#+end_src +\\@title +\\vspace{\\fill} -FIXME: why did I give this example at all? It's ugly, hard to understand, and if -anything only demonstrates why my project fails to work efficiently. This will -be cut if no one can find a purpose for it during review. - -*** MIPSL Statements -MIPSL statements form a subset of statements allowed in the pseudocode. There -are three omissions: ~while...do~ loops, ~repeat...until~ loops and -~try...catch~ exception handling. Each of these structures is only used in -high-level constructs, such as the top-level execution loop -[cite:@arm/DDI0553B.s §E2.1.397], converting reals into floating-point -bitstrings [cite:@arm/DDI0553B.s §E2.1.166] and inserting instructions into the -instruction queue [cite:@arm/DDI0553B.s §E2.1.366]. - -The ultimate goal of MIPSL is to accurately represent the operation of a -specific instruction sequence on visible registers and memory, over simulating -the entirety of processor state. Hence these high-level parts of the processor -behaviour, like managing the instruction queue, are not relevant. - -Another argument for excluding these constructs is that it vastly complicates -the semantics of MIPSL. Of particular concern to Agda is that the presence of -potentially-unending loops, such as a ~while...do~ loop, would violate the -termination condition of Agda functions if it were included in a denotational -semantics. - -Most of the statements that are present in MIPSL are unsurprising. The ~skip~ -and sequencing (~_∙_~) statements should be familiar from the discussion on -Hoare logic, the assignment statement (~_≔_~) assigns a value into a reference, -the ~invoke~ statement calls a procedure and the ~if_then_else_~ statement -starts a conditional block. - -Given that MIPSL has a ~skip~ statement and an ~if_then_else_~ control-flow -structure, including the ~if_then_~ statement may appear redundant. Ultimately, -the statement is redundant. It is regardless included in MIPSL for two reasons. -The first is ergonomics. ~if_then_~ statements appear many times more often in -the pseudocode than ~if_then_else_~ statements that omitting it would only serve -to complicate the code. The other reason is that including an ~if_then_else_~ -statement makes the behaviour of a number of functions that manipulate MIPSL -code much easier to reason about. - -The form of variable declarations is significantly different in MIPSL than it is -in the pseudocode. As variables in MIPSL are accessed by index into the variable -context instead of by name, MIPSL variable declarations do not need a name. In -addition, Agda can often infer the type of a declared variable from the context -in which it is used, making type annotations unnecessary. The last difference is -that all variables in MIPSL must be initialised. This simplifies the semantics -of MIPSL greatly, and prevents the use of uninitialised variables. - -MIPSL makes a small modification to ~for~ loops that greatly improve the type -safety over what is achieved by the pseudocode. Instead of looping over a range -of dynamic values [cite:@arm/DDI0553B.s §E1.4.4], MIPSL loops perform a static -number of iterations, determined by an Agda natural ~n~. Then, instead of the -loop variable being an assignable integer expression, MIPSL introduces a new -variable with type ~fin n~. - -MIPSL has a ~LocalStatement~ type as well as a ~Statement~ type. Whilst -~Statement~ can assign values into any ~Reference~, a ~LocalStatement~ can only -assign values into a ~LocalReference~. This means that ~LocalStatement~ cannot -modify global state, only local state. - -**** Example MIPSL Statements -Here is a statement that copies elements from ~y~ into ~x~ if the corresponding -entry in ~mask~ is true: - -#+begin_src agda2 -copy : Statement Σ (array t n ∷ array t n ∷ array bool n ∷ []) -copy = - for n ( - let i = var 0F in - let x = var 1F in - let y = var 2F in - let mask = var 3F in - - if index mask i ≟ true - then - *index x i ≔ index y i - ) -#+end_src +\\@author +\\vspace{10mm} -This uses Agda functions ~index~ and ~*index~ to apply the appropriate slices, -casts and unboxing to extract an element from an array expression and reference, -respectively. One thing of note is the use of ~let...in~ to give variables -meaningful names. This is a stylistic choice that works well in this case. -Unfortunately, if the ~if_then_~ statement declared a new variable, these naming -variables would become useless, as the types would be different. For example -consider the following snippet: - -#+begin_src agda2 -VPTAdvance : Statement State (beat ∷ []) -VPTAdvance = - declare (fin div2 (tup (var 0F ∷ []))) ( - declare (elem 4 (! VPR-mask) (var 0F)) ( - let vptState = var 0F in - let maskId = var 1F in - let beat = var 2F in - - if ! vptState ≟ lit (true ∷ false ∷ false ∷ false ∷ []) - then - vptState ≔ lit (Vec.replicate false) - else if inv (! vptState ≟ lit (Vec.replicate false)) - then ( - declare (lit false) ( - let i = var 0F in - let vptState = var 1F in - -- let mask = var 2F in - let beat = var 3F in - - cons vptState (cons i nil) ≔ call (LSL-C 0) (! vptState ∷ []) ∙ - if ! i - then - *elem 4 VPR-P0 beat ≔ not (elem 4 (! VPR-P0) beat))) ∙ - if getBit 0 (asInt beat) - then - *elem 4 VPR-mask maskId ≔ ! vptState)) -#+end_src +\\Large +\\college +\\vspace{\\fill} -This corresponds to the ~VPTAdvance~ procedure by [cite/t:@arm/DDI0553B.s -§E2.1.424]. Notice how every time a new variable is introduced, the variable -names have to be restated. Whilst this is a barrier when trying to write -programs in MIPSL, the type-safety guarantees and simplified proofs over using -named variables more than make up the difference. - -*** MIPSL Functions and Procedures -Much like how a procedure in the pseudocode is a wrapper around a block of -statements, ~Procedure~ in MIPSL is a wrapper around ~Statement~. Note that -MIPSL procedures only have one exit point, the end of a statement, unlike the -pseudocode which has ~return~ statements. Any procedure using a ~return~ -statement can be transformed into one that does not by a simple refactoring, so -MIPSL does not lose any expressive power over the pseudocode. - -MIPSL functions are more complex than procedures. A function consists of a pair -of an ~Expression~ and ~LocalStatement~. The statement has the function -arguments and the return value as local variables, where the return value is -initialised to the result of the expression. The return value of the function is -then the final value of the return variable. - -Functions and procedures are the reason MIPSL has two variable contexts, one -each for the global and local variables. The set of global variables is -unchanged across function and procedure boundaries. However, the set of local -variables is completely different. Having a separate variable for each in the -type is the cleanest way to represent this separation in Agda. - -**** Example MIPSL Functions and Procedures -As ~Procedure~ is almost an alias for ~Statement~, examples of procedures can be -found in [[*Example MIPSL Statements]]. This is a simple function that converts a -bitstring to an unsigned or signed integer, depending on whether the second -argument is true or false: - -#+begin_src agda2 -Int : Function State (bits n ∷ bool ∷ []) int -Int = - init - if var 1F - then uint (var 0F) - else sint (var 0F) ∙ - skip - end -#+end_src +\\@date +\\vspace{\\fill} -The function body is the ~skip~ statement, meaning that whatever is initially -assigned to the return variable is the result of calling the function. The -initial value of the return variable is a simple conditional statement, calling -~uint~ or ~sint~ on the first argument as appropriate. Many functions that are -easy to inline have this form. - -The ~GetCurInstBeat~ function by [cite/t:@arm/DDI0553B.s §E2.1.185] is one -function that benefits from the unusual representation of functions. A -simplified MIPSL version is given below. - -#+begin_src agda2 -GetCurInstrBeat : Function State [] (tuple (beat ∷ elmtMask ∷ [])) -GetCurInstrBeat = - init - tup (! BeatId ∷ lit (Vec.replicate true) ∷ []) ∙ ( - let outA = head (var 0F) in - let outB = head (tail (var 0F)) in - if call VPTActive (! BeatId ∷ []) - then - outB ≔ !! outB and elem 4 (! VPR-P0) outA - ) - end -#+end_src +\\end{center} -The function initialises a default return value, and then modifies it based on -the current state of execution. This is easy to encode in the MIPSL function -syntax. The return variable is initialised to the default value, and the -function body performs the necessary manipulations. - -In this way a function is much like a ~declare~ statement. However, instead of -discarding the declared variable when it leaves scope, a function returns it to -the caller. - -** MIPSL Semantics -The ultimate goal of this work is to formally verify an assembly algorithm -written for the Armv8.1-M architecture that performs the number theoretic -transform. So far we have discussed the syntactic form of MIPSL, which is a -language similar to the Armv8-M pseudocode specification language, written in -Agda. We have also given a brief high-level semantics of MIPSL. Formal -verification requires a much more detailed description of the semantics than -what has been given so far. - -This section starts with a brief discussion of how to model MIPSL types. This -addresses the burning question of how to model real numbers in Agda. From this, -we discuss the denotational semantics of MIPSL, and how MIPSL program elements -can be converted into a number of different Agda function types. The section -ends with a presentation of a Hoare logic for MIPSL, allowing for efficient -syntax-directed proofs of statements. - -*** MIPSL Datatype Models -#+name: MIPSL-type-models -#+caption: The semantic encoding of MIPSL data types. The use of ~Lift~ is to -#+caption: ensure all the encodings occupy the same Agda universe level. -#+begin_src agda2 -⟦_⟧ₜ : Type → Set ℓ -⟦_⟧ₜₛ : Vec Type n → Set ℓ - -⟦ bool ⟧ₜ = Lift ℓ Bool -⟦ int ⟧ₜ = Lift ℓ ℤ -⟦ fin n ⟧ₜ = Lift ℓ (Fin n) -⟦ real ⟧ₜ = Lift ℓ ℝ -⟦ tuple ts ⟧ₜ = ⟦ ts ⟧ₜₛ -⟦ array t n ⟧ₜ = Vec ⟦ t ⟧ₜ n - -⟦ [] ⟧ₜₛ = Lift ℓ ⊤ -⟦ t ∷ [] ⟧ₜₛ = ⟦ t ⟧ₜ -⟦ t ∷ t₁ ∷ ts ⟧ₜₛ = ⟦ t ⟧ₜ × ⟦ t₁ ∷ ts ⟧ₜₛ -#+end_src +\\fi -To be able to write a denotational semantics for a language, the first step is -to find a suitable encoding for the data types. In this case, we have to be able -to find encodings of MIPSL types within Agda. [[MIPSL-type-models]] shows the full -encoding function. Most of the choices are fairly trivial; Agda Booleans for -~bool~, Agda vectors for ~array t n~ and the Agda finite set type ~Fin n~ for -the MIPSL type ~fin n~. - -# RESOLVED: bit is an alias for bool. -# In this encoding, ~bool~ and ~bit~ are both represented by Agda Booleans. This -# again begs the question of why they are distinct types in MIPSL and are not -# unified into one. The philosophical reasoning used by (FIXME: textcite) is now -# diluted; ~array~ is used for abstract arrays and physical bitstrings. (FIXME: -# why are they distinct?) - -Tuples are the next simplest type, being encoded as an n-ary product. This is -the action of the ~⟦_⟧ₜₛ~ function in [[MIPSL-type-models]]. Unfortunately the Agda -standard library does not have a dependent n-ary product type. In any case, the -Agda type checker would not accept its usage in this case due to termination -checking, hence the manual inductive definition. - -(ALTERNATIVE 1: ~int~ stays as abstract discrete ordered commutative ring) - -The other two MIPSL types are ~int~, ~real~. Whilst ~int~ could feasibly be -encoded by the Agda integer type, there is no useful Agda encoding for -mathematical real numbers. Because of this, both numeric types are represented -by abstract types with the appropriate properties. ~int~ is represented by a -discrete ordered commutative ring ℤ and ~real~ by a field ℝ. We also require -that there is a split ring monomorphism \(\mathtt{/1} : ℤ \to ℝ\) with -retraction \(\mathtt{⌊\_⌋} : ℝ \to ℤ\). \(\mathtt{⌊\_⌋}\) may not be a ring -homomorphism, but it must preserve \(\le\) ordering and satisfy the floor -property: - -\[ -\forall x y. x < y \mathtt{/1} \implies ⌊ x ⌋ < y -\] - -(ALTERNATIVE 2: ~int~ becomes integer and ~real~ stays abstract) - -The other two MIPSL types are ~int~ and ~real~. ~int~ is encoded by the Agda -integer type. However, there is no useful Agda encoding for mathematical real -numbers. Because of this, it is represented by an abstract type ℝ such that ℝ is -a field. We also require that there is a split ring monomorphism \(\mathtt{/1} : -ℤ \to ℝ\) with retraction \(\mathtt{⌊\_⌋} : ℝ \to ℤ\). \(\mathtt{⌊\_⌋}\) may not -be a ring homomorphism, but it must preserve \(\le\) ordering and satisfy the -floor property: - -\[ -\forall x y. x < y \mathtt{/1} \implies ⌊ x ⌋ < y -\] - -(ALTERNATIVE 3: ~real~ becomes rational.) - -The other two MIPSL types are ~int~ and ~real~. ~int~ is encoded by the Agda -integer type. However, there is no useful Agda encoding for mathematical real -numbers. This can be approximated using the Agda rational type ℚ. Whilst this -clearly cannot encode all real numbers, it satisfies nearly all of the -properties required by the pseudocode real-number type. The only missing -operation is square-root, which is unnecessary for the proofs MIPSL is designed -for. - -(END ALTERNATIVES) - -*** Denotational Semantics - -#+name: MIPSL-denotational-prototypes -#+caption: Function prototypes for the denotational semantics of different MIPSL -#+caption: program elements. All of them become functions from the current -#+caption: variable context into some return value. -#+begin_src agda2 -expr : Expression Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -exprs : All (Expression Σ Γ) ts → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ ts ⟧ₜₛ -ref : Reference Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -locRef : LocalReference Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -stmt : Statement Σ Γ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ -locStmt : LocalStatement Σ Γ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ -fun : Function Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -proc : Procedure Σ Γ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Σ ⟧ₜₛ -#+end_src +\\vspace{\\fill} +\\begin{center} +Submitted in partial fulfillment of the requirements for the\\\\ +\\course +\\end{center} -The denotational semantics has to represent the different MIPSL program elements -as mathematical objects. In this case, due to careful design of MIPSL's syntax, -each of the elements is represented by a total function. -[[MIPSL-denotational-prototypes]] shows the prototypes of the different semantic -interpretation functions, and the full definition is in [[*MIPSL Denotational -Semantics]]. Each function accepts the current variable context as an argument. -Because the variable contexts are an ordered sequence of values of different -types, they can be encoded in the same way as tuples. - -**** Expression Semantics - -The semantic representation of an expression converts the current variable -context into a value with the same type as the expression. Most cases are pretty -simple. For example, addition is the sum of the values of the two subexpressions -computed recursively. One of the more interesting cases are global and local -variables, albeit this is only a lookup in the variable context for the current -value. This lookup is guaranteed to be safe due to variables being a lookup into -the current context. Despite both being a subsets of the ~Expression~ type, -~Reference~ and ~LocalReference~ require their own functions to satisfy the -demands of the termination checker. - -One significant omission from this definition is the checking of evaluation -order. Due to the design choice that MIPSL functions cannot modify global state, -and that no MIPSL expression can modify state, expressions have the same value -no matter the order of evaluation for sub-expressions. This is also reflected in -the type of the denotational representation of expressions. It can only possibly -return a value and not a modified version of the state. - -**** Assignment Semantics -#+name: MIPSL-denotational-assign-prototypes -#+caption: Function prototypes for the ~assign~ and ~locAssign~ helper -#+caption: functions. The arguments are the reference, new value, original -#+caption: variable context and the context to update. The original context is -#+caption: needed to evaluate expressions within the reference. -#+begin_src agda2 -assign : Reference Σ Γ t → ⟦ t ⟧ₜ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → - ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ -locAssign : LocalReference Σ Γ t → ⟦ t ⟧ₜ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → - ⟦ Γ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ -#+end_src +\\end{titlepage} -Before considering statements as a whole, we start with assignment statements. -If assignments were only into variables, this would be a trivial update to the -relevant part of the context. However, the use of ~Reference~ makes things more -tricky. Broadly speaking, there are four types of ~Reference~: terminal -references like ~state~, ~var~ and ~nil~; isomorphism operations like ~unbox~, -~[_]~ and ~cast~; product operations like ~merge~ and ~cons~; and projection -operations like ~slice~, ~cut~, ~head~ and ~tail~. - -We will consider how to update each of the four types of references in turn, -which is the action performed by helper functions ~assign~ and ~locAssign~, the -signatures of which are given in [[MIPSL-denotational-assign-prototypes]]. - -Terminal references are the base case and easy. Assigning into ~state~ and ~var~ -updates the relevant part of the variable context, whilst assigning into -~nil~---the unique nullary product---is a no-op. Isomorphic reference operations -are also relatively simple to assign into. First, transform the argument using -the inverse operation, and assign that into the sub-reference. For example, the -assignment ~[ ref ] ≔ v~ is the same as ~ref ≔ unbox v~. - -Assigning into product reference operations is also not too complex, by -assigning into each projection in turn. For example, ~cons r₁ r₂ ≔ v~ is -equivalent to ~r₁ ≔ head v ∙ r₂ ≔ tail v~. However, some care must be taken to -ensure only that any expressions in the second assignment use the original, and -not updated, variable context. This is why ~assign~ and ~locAssign~ have the -extra context argument. - -The final type of reference to consider are the projection reference operations. -Assigning into one projection of a reference means that the other part remains -unchanged. Consider the assignment ~head r ≔ v~ as an example. This is -equivalent to ~r ≔ cons v (tail r)~, which makes it clear that the second -projection remains constant. The second projection must be computed using the -original variable context. - -This interplay between product and projection reference types is a large part of -the reason why MIPSL has ~merge~, ~cut~ and ~slice~ instead of the bitstring -concatenation and slicing present in the Armv8-M pseudocode specification -language. There is no way to form a product-projection triple with only -bitstring joining and slicing, so any denotational semantics with these -operations would require merge and cut operations on the encoding of values. -MIPSL takes these semantic necessities and makes them available to programmers. - -~assign~ and ~locAssign~, when given a reference and initial context, return -unary operations on the full and local variable contexts respectively. As -~Reference~ includes both ~state~ and ~var~, assigning into a reference can -modify both global and local references. In contrast, ~LocalReference~ only -features ~var~, so can only modify local variables. - -**** Statement Semantics -Compared to assignment, the semantics of other statements are trivial to -compute. Skip statements map to the identity function and sequencing is function -composition, reflecting that they do nothing and compose statements together -respectively. As expressions cannot modify state, ~if_then_else_~ and ~if_then_~ -statements become simple---evaluate the condition and both branches on the input -state, and return the branch depending on the value of the condition. Local -variable declarations are also quite simple. The initial value is computed and -added to the variable context. After evaluating the subsequent statement, the -final value of the new variable is stripped away from the context. - -The only looping construct in MIPSL is the ~for~ loop. Because it performs a -fixed number of iterations, it too has easy-to-implement denotational semantics. -This is because it is effectively a fixed number of ~declare~ statements all -sequenced together. This is also one of the primary reasons why the denotational -semantics can have poor computational performance; every iteration of the ~for~ -loop must be evaluated individually. - -~stmt~ and ~locStmt~ return the full context and only the local variables -respectively. This is because only ~Statement~ can include ~Reference~ which can -reference global state. On the other hand, ~LocalReference~ used by -~LocalStatement~ can only refer to, and hence modify, local state. - -**** Function and Procedure Semantics -Finally there are ~proc~ and ~fun~ for denoting procedures and functions. ~proc~ -returns the global state only; ~Procedure~ is a thin wrapper around ~Statement~, -which modifies both local and global state. However, the local state is lost -when leaving a procedure, hence ~proc~ only returns the global part. ~fun~ -behaves a lot like a ~declare~ statement. It initialises the return variable to -the given expression, then evaluates the ~LocalStatement~ body. Unlike -~declare~, which discards the added variable upon exiting the statement, ~fun~ -instead returns the value of that variable. As ~LocalStatement~ cannot modify -global state, and the other local variables are lost upon exiting the function, -only this one return value is necessary. - -*** Hoare Logic Semantics -The final form of semantics specified for MIPSL is a form of Hoare logic. Unlike -the denotational semantics, which must perform a full computation, the Hoare -logic is syntax-directed; loops only require a single proof. This section starts -by explaining how a MIPSL ~Expression~ is converted into a ~Term~ for use in -Hoare logic assertions. Then the syntax and semantics of the ~Assertion~ type is -discussed before finally giving the form of correctness triples for MIPSL. - -**** Converting ~Expression~ into ~Term~ -As discussed in [[*Hoare Logic]], a simple language such as WHILE can use -expressions as terms in assertions directly. The only modification required is -the addition of auxiliary variables. MIPSL is not as simple a language as WHILE, -thanks to the presence of function calls in expressions. Whilst function calls -do not prevent converting expressions into terms, some care must be taken. In -particular, this conversion is only possible due to the pure nature of MIPSL -functions; it would not be possible if functions modified global variables. The -full definition of ~Term~ and its semantics are given in [[*MIPSL Hoare Logic -Definitions]]. - -First, a demonstration on why function calls need special care in Hoare logic. -We will work in an environment with a single Boolean-valued global variable. -Consider the following MIPSL function, a unary operator on an integer: - -#+begin_src agda2 -f : Function [ bool ] [ int ] int -f = - init - var 0F ∙ - let x = var 1F in - let ret = var 0F in - if state 0F - then ret ≔ lit 1ℤ + x - end -#+end_src +\\end{sffamily} -When the global variable ~state 0F~ is false, this is the identity function. -However, when ~state 0F~ is true, ~f~ performs an increment. - -Consider an expression ~e : Expression [ bool ] Γ int~ of ~call f [ x ]~. There -are three important aspects we need to consider for converting ~e~ into a term: -the initial conversion; substitution of variables; and the semantics. The -simplest conversion is to keep the function call as-is, and simply recursively -convert ~x~ into a term. This would result in a term ~e′ = call f [ x′ ]~, using -~′~ to indicate the term embedding. - -What would happen when we try and substitute ~state 0F~ for ~t~, a term -involving local variables in ~Γ~, into ~e′~? As ~f~ refers to ~state 0F~, it -must be modified in some way. However, ~Γ~ is a different variable context from -~[ int ]~, so there is no way of writing ~t~ inside of ~f~. This embedding is -not sufficient. - -A working solution comes from the insight that a ~Function~ in MIPSL can only -read from global variables, and never write to them. Instead of thinking of ~f~ -as a function with a set of global variables and a list of arguments, you can -consider ~f~ to be a function with two sets of arguments. In an ~Expression~, -the first set of arguments always corresponds exactly with the global variables, -so is elided. We can then define an embedding function ~↓_~, such that ~↓ e = -call f [ state 0F ] [ ↓ x ]~, and all the other expression forms as expected. -This makes the elided arguments to ~f~ explicit. - -Doing a substitution on ~↓ e~ is now simple: perform the substitution on both -sets of arguments recursively, and leave ~f~ unchanged. As the first set of -arguments correspond exactly to the global variables in ~f~, the substitution -into those arguments appears like a substitution into ~f~ itself. - -The last major consideration of this embedding is how to encode its semantics. -To be able to perform logical implications within Hoare logic, it is necessary -to have a semantic interpretation for assertions and thus terms. Going back to -~↓ e~, we already have a denotational semantics for ~f~. Hence we only need to -consider the global and local variables we pass to ~f~ to get the value. We -simply pass ~f~ the values of the global and local argument lists for the values -of the global and local arguments respectively. Thus ~↓ e~ is a valid conversion -from ~Expression~ to ~Term~. - -The only other difference between ~Expression~ and ~Term~ is the use of -auxiliary variables within Hoare logic terms. MIPSL accomplishes this by -providing a ~meta~ constructor much like ~state~ and ~var~. This indexes into a -new auxiliary variable context which forms part of the type definition of -~Term~. - -**** Hoare Logic Assertions -An important part of Hoare logic is the assertion language used within the -correctness triples. The Hoare logic for MIPSL uses a first-order logic, which -allows for the easy proof of many logical implications at the expense of not -being complete over the full set of state properties. The full definition and -semantics of the ~Assertion~ type are in [[*MIPSL Hoare Logic Definitions]]. - -The ~Assertion~ type has the usual set of Boolean connectives: ~true~, ~false~, -~_∧_~, ~_∨_~, ~¬_~ and ~_→_~. When compared to the ~fin~ MIPSL expression, which -performs arbitrary manipulations on finite sets, using this fixed set of -connectives may appear restrictive. The primary reason in favour of a fixed set -of connectives is that the properties are well-defined. This makes it possible -to prove properties about the ~Assertion~ type within proofs that would not be -possible if assertions could use arbitrary connectives. - -Another constructor of ~Assertion~ is ~pred~, which accepts an arbitrary -Boolean-valued ~Term~. This is the only way to test properties of the current -program state within assertions. As nearly all types have equality comparisons, -~pred~ can encode equality and inequality constraints on values. Furthermore, as -~Term~ embeds ~Expression~, many complex computations can be performed within -~pred~. To allow equality between two terms of any type, there is an ~equal~ -function to construct an appropriate assertion. - -The final two constructors of ~Assertion~ provide first-order quantification -over auxiliary variables. ~all~ provides universal quantification and ~some~ -provides existential quantification. - -Semantically, an assertion is a predicate on the current state of execution. For -MIPSL, this state is the current global, local and auxiliary variable contexts. -As is usual in Agda, the predicates encoded as an indexed family of sets. - -The Boolean connectives are represented by their usual type-theoretic -counterparts: the unit type for ~true~, the empty type for ~false~, product -types for ~_∧_~, sum types for ~_∨_~, function types for ~_→_~ and the negation -type for ~¬_~. - -Quantifier assertions are also quite easy to give a semantic representation. For -universal quantification, you have a function taking values of the type of the -auxiliary variable, which returns the encoding of the inner assertion with -auxiliary context extended by this value. For existential quantification, you -instead have a dependent pair of a value with the auxiliary variable type, and -semantic encoding of the inner assertion. - -The final ~Assertion~ form to consider is ~pred~. This first evaluates the -associated Boolean term. If true, the semantics returns the unit type. -Otherwise, it returns the empty type. For a language where value equality can -have many different values, some readers may feel like reducing those equalities -to a binary result loses information. Providing this information to the user -would require a way to convert Boolean-valued terms into a normal form, -with an inequality operator at the root. This conversion would be highly -non-trivial, especially due to the presence of function calls in terms. - -Fortunately, all equalities and inequalities between MIPSL values are decidable, -either by construction of the type for Booleans and finite sets, or by -specification for integers and real numbers. This allows the user to extract -Agda terms for equalities given only knowledge of whether terms are equal. - -**** Correctness Triples for MIPSL -In the traditional presentation of Hoare logic ([[*Hoare Logic]]), there are two -types of rule; structural rules based on program syntax and adaptation rules to -modify preconditions and postconditions. The Hoare logic for MIPSL unifies the -two forms of rules, eliminating the need to choose which type of rule to use -next. This allows for purely syntax-directed proofs for any choice of -precondition and postcondition. - -#+name: MIPSL-correctness-triples -#+caption: The Hoare logic correctness triples for MIPSL. This combines the -#+caption: structural and adaptation rules you would find in traditional -#+caption: renderings of Hoare logic into a single set of structural rules. -#+begin_src agda2 -data HoareTriple (P : Assertion Σ Γ Δ) (Q : Assertion Σ Γ Δ) : - Statement Σ Γ → Set (ℓsuc ℓ) where - seq : ∀ R → HoareTriple P R s → HoareTriple R Q s₁ → HoareTriple P Q (s ∙ s₁) - skip : P ⊆ Q → HoareTriple P Q skip - assign : P ⊆ subst Q ref (↓ val) → HoareTriple P Q (ref ≔ val) - declare : HoareTriple - (Var.weaken 0F P ∧ equal (var 0F) (Term.Var.weaken 0F (↓ e))) - (Var.weaken 0F Q) - s → - HoareTriple P Q (declare e s) - invoke : let metas = All.map (Term.Meta.inject Δ) (All.tabulate meta) in - let varsToMetas = λ P → Var.elimAll (Meta.weakenAll [] Γ P) metas in - let termVarsToMetas = - λ t → Term.Var.elimAll (Term.Meta.weakenAll [] Γ t) metas in - HoareTriple - ( varsToMetas P - ∧ equal (↓ tup (All.tabulate var)) (termVarsToMetas (↓ tup es)) - ) - (varsToMetas Q) - s → - HoareTriple P Q (invoke (s ∙end) es) - if : HoareTriple (P ∧ pred (↓ e)) Q s → - P ∧ pred (↓ inv e) ⊆ Q → - HoareTriple P Q (if e then s) - if-else : HoareTriple (P ∧ pred (↓ e)) Q s → - HoareTriple (P ∧ pred (↓ inv e)) Q s₁ → - HoareTriple P Q (if e then s else s₁) - for : (I : Assertion _ _ (fin _ ∷ _)) → - P ⊆ Meta.elim 0F I (↓ lit 0F) → - HoareTriple {Δ = _ ∷ Δ} - ( Var.weaken 0F - (Meta.elim 1F (Meta.weaken 0F I) - (fin inject₁ (cons (meta 0F) nil))) - ∧ equal (meta 0F) (var 0F) - ) - (Var.weaken 0F - (Meta.elim 1F (Meta.weaken 0F I) - (fin suc (cons (meta 0F) nil)))) - s → - Meta.elim 0F I (↓ lit (fromℕ m)) ⊆ Q → - HoareTriple P Q (for m s) -#+end_src +\\makeatother +\\newpage -We will now talk through each of the Hoare logic rules for MIPSL, which are -given in [[MIPSL-correctness-triples]]. The simplest rule to consider is ~skip~. -This immediately demonstrates how MIPSL Hoare logic combines structural and -adaptation rules. A purely structural rule for ~skip~ would be ~HoareTriple P P -skip~; the ~skip~ statement has no effect on the current state. By combining -this with the rule of consequence, a ~skip~ statement allows for logical -implication. - -The ~seq~ rule is as you would expect for anyone familiar with Hoare logic. The -only potential surprise is that the intermediate assertion has to be given -explicitly. This is due to Agda being unable to infer the assertion ~Q~ from the -numerous manipulations applied to it by the other correctness rules. - -Another pair of simple rules are ~if~ and ~if-else~. In fact, the ~if-else~ rule -is identical to the corresponding Hoare logic rule from WHILE, and ~if~ only -differs by directly substituting in a ~skip~ statement for the negative branch. - -The final trivial rule is ~assign~. Like the ~skip~ rule, the ~assign~ rule -combines the structural and adaptation rules of WHILE into a single Hoare logic -rule for MIPSL. A purely structural rule would have ~subst Q ref (↓ val)~ as the -precondition of the statement. MIPSL combines this with the rule of consequence -to allow for an arbitrary precondition. - -The other Hoare logic rules for MIPSL are decidedly less simple. Most of the -added complexity is a consequence of MIPSL's type safety. For example, whilst it -is trivial to add a free variable to an assertion on paper, doing so in a -type-safe way for the ~Assertion~ type requires constructing a whole new Agda -term, as the variable context forms part of the type. - -The ~declare~ rule is the simplest of the three remaining. The goal is to -describe a necessary triple on ~s~ such that ~HoareTriple P Q (declare e s)~ is -a valid correctness triple. First, note that ~P~ and ~Q~ have type ~Assertion Σ -Γ Δ~, whilst ~s~ has type ~Statement Σ (t ∷ Γ)~ due to the declaration -introducing a new variable. To be able to use ~P~ and ~Q~, they have to be -weakened to the type ~Assertion Σ (t ∷ Γ) Δ~, achieved by calling ~Var.weaken -0F~. We will denote the weakened forms ~P′~ and ~Q′~ for brevity. The new triple -we have is ~HoareTriple P′ Q′ s~. However, this does not constrain the new -variable. Thus we assert that the new variable ~var 0F~ is equal to the initial -value ~e~. However, ~e~ has type ~Expression Σ Γ~ and we need a ~Term Σ (t ∷ Γ) -Δ~. Hence we must instead use ~Term.Var.weaken 0F (↓ e)~, denoted ~e′~ , which -converts ~e~ to a term and introduces the new variable. This finally gives us -the triple we need: ~HoareTriple (P′ ∧ equal (var 0F) e′) Q′ s~. - -I will go into less detail whilst discussing ~invoke~ and ~for~, due to an even -greater level of complexity. The ~for~ rule is the simpler case, so I will start -there. The form of the ~for~ rule was inspired from the WHILE rule for a ~while~ -loop, but specialised to a form with a fixed number of iterations. - -Given a ~for n s~ statement, we first choose a loop invariant ~I : Assertion Σ Γ -(fin (suc n) ∷ Δ)~. The additional auxiliary variable indicates the number of -complete iterations of the loop, from \(0\) to \(n\). We will use ~I(x)~ to -denote the assertion ~I~ with the additional auxiliary variable replaced with -term ~x~, and make weakening variable contexts implicit. We require that ~P ⊆ -I(0)~ and ~I(n) ⊆ Q~ to ensure that the precondition and postcondition are an -adaptation of the loop invariant. The final part to consider is the correctness -triple for ~s~. We add in a new auxiliary variable representing the value of the -loop variable. This is necessary to ensure the current iteration number is -preserved between the precondition and postcondition, as the loop variable -itself can be modified by ~s~. We then require that the following triple holds: -~HoareTriple (I(meta 0F) ∧ equal (meta 0F) (var 0F)) I(1+ meta 0F) s~. This -ensures that ~I~ remains true across the loop iteration, for each possible value -of the loop variable. - -Notice that unlike the denotational semantics, which would explicitly execute -each iteration of a loop, the Hoare logic instead requires only a single proof -term for all iterations of the loop. This is one of the primary benefits of -using Hoare logic over the denotational semantics; it has a much lower -computational cost. - -The final Hoare logic rule for MIPSL is ~invoke~. Procedure invocation is tricky -in MIPSL's Hoare logic due to the changing local variable scope in the procedure -body. Of particular note, any local variables in the precondition and -postcondition for a procedure invocation cannot be accessed nor modified by the -procedure body. This is the inspiration for the form of the ~invoke~ rule. - -To construct ~HoareTriple P Q (invoke (s ∙end) es)~, we first consider the form -~P~ and ~Q~ will take in a correctness triple for ~s~. Note that local variables -in ~P~ and ~Q~ are immutable within ~s~, due to the changing local variable -scope. Also note that the local variables cannot be accessed using ~var~; ~P~ -and ~Q~ have type ~Assertion Σ Γ Δ~, but ~s~ has type ~Statement Σ Γ′~ for some -context ~Γ′~ independent of ~Γ~. As the original local variables are immutable -during the invocation, we can replace them with auxiliary variables, by -assigning a new auxiliary variable for each one. Within ~P~ and ~Q~, we then -replace all ~var x~ with ~meta x~ to reflect that the local variables have been -moved to auxiliary variables. This is the action performed by the ~varsToMetas~ -function. Finally, we have to ensure that the local variables within the -procedure body are initially set to the invocation arguments. Like ~P~ and ~Q~, -the local variables in ~es~ have to be replaced with the corresponding auxiliary -variables. This substitution is done by ~termVarsToMetas~. - -Example uses of these rules, particularly ~invoke~ and ~for~, are given in -(FIXME: forward reference). - -* Using MIPSL in Proofs -We have now fully defined MIPSL, a programming language that imitates the -Armv8-M pseudocode specification language. Whilst the specification of a new -language is interesting by itself, the purpose of MIPSL was to enable the formal -verification of algorithms written for the Armv8-M instruction set. - -This chapter demonstrates the suitability of MIPSL for this purpose by first -using MIPSL to encode the behaviour of vector-based Barrett reduction, as -specified by (FIXME: textcite). Then we will instantiate MIPSL with a concrete -model for its ~int~ and ~real~ types and explore how MIPSL is used to verify a -number of concrete examples, giving evidence for the correctness of MIPSL's -semantics. This will be followed up by a discussion on how to abstract away -details of this proof to show the Barrett reduction algorithm is correct on any -set of inputs. The chapter will conclude with a proof sketch for the correctness -of the MIPSL Hoare logic rules compared to the denotational semantics, showing -they are a useful tool. - -** Vector Barrett Reduction in MIPSL -Barrett reduction is a useful subroutine in the NTT algorithm. Briefly, it takes -an integer and finds a "small" representable modulo some fixed base. Using the -M-profile vector extension for the Armv8.1-M instruction set, the algorithm can -be applied to a vector of four 32-bit values using only two instructions, given -in [[barrett-asm]]. - -#+name: barrett-asm -#+caption: Assembly instructions for Barrett reduction of a vector of values -#+caption: using the M-profile vector extension (FIXME: cite). -#+begin_src asm -; Logical inputs: -; - n : modulo base -; Register inputs: -; - -n : general register with value -n -; - m : general register with value ⌊ 2^32 / n ⌉ -; - z : input vector register -; - t : output vector register -VQRDMULH.s32 t z m -VMLA.s32 z t -n +%%TC:endignore +") #+end_src -(FIXME: how much detail is needed? The word limit exists) - -** Concrete Examples of Semantics - - -* Conclusions -** Future Work - -#+print_bibliography: - -\appendix - - - -#+latex: %TC:ignore -* MIPSL Syntax Definition -#+begin_src agda2 -data Expression (Σ : Vec Type o) (Γ : Vec Type n) : Type → Set -data Reference (Σ : Vec Type o) (Γ : Vec Type n) : Type → Set -data LocalReference (Σ : Vec Type o) (Γ : Vec Type n) : Type → Set -data Statement (Σ : Vec Type o) (Γ : Vec Type n) : Set -data LocalStatement (Σ : Vec Type o) (Γ : Vec Type n) : Set -data Function (Σ : Vec Type o) (Γ : Vec Type n) (ret : Type) : Set -data Procedure (Σ : Vec Type o) (Γ : Vec Type n) : Set - -data Expression Σ Γ where - lit : literalType t → Expression Σ Γ t - - state : ∀ i → Expression Σ Γ (lookup Σ i) - - var : ∀ i → Expression Σ Γ (lookup Γ i) +#+begin_export latex - _≟_ : ⦃ HasEquality t ⦄ → - Expression Σ Γ t → - Expression Σ Γ t → - Expression Σ Γ bool +%TC:ignore - _<?_ : ⦃ Ordered t ⦄ → - Expression Σ Γ t → - Expression Σ Γ t → - Expression Σ Γ bool +\begin{sffamily} - inv : Expression Σ Γ bool → Expression Σ Γ bool +Total page count: \pageref{lastpage} - _&&_ : Expression Σ Γ bool → - Expression Σ Γ bool → - Expression Σ Γ bool +% calculate number of pages from +% \label{firstcontentpage} to \label{lastcontentpage} inclusive +\makeatletter +\@tempcnta=\getpagerefnumber{lastcontentpage}\relax% +\advance\@tempcnta by -\getpagerefnumber{firstcontentpage}% +\advance\@tempcnta by 1% +\xdef\contentpages{\the\@tempcnta}% +\makeatother - _||_ : Expression Σ Γ bool → - Expression Σ Γ bool → - Expression Σ Γ bool +Main chapters (excluding front-matter, references and appendix): +\contentpages~pages +(pp~\pageref{firstcontentpage}--\pageref{lastcontentpage}) - not : Expression Σ Γ (bits n) → Expression Σ Γ (bits n) +#+end_export - _and_ : Expression Σ Γ (bits n) → - Expression Σ Γ (bits n) → - Expression Σ Γ (bits n) +#+name: wordcount +#+begin_src elisp :exports none :eval export +(if (not (boundp squid-eval)) + (setq squid-eval nil)) - _or_ : Expression Σ Γ (bits n) → - Expression Σ Γ (bits n) → - Expression Σ Γ (bits n) +(if (not squid-eval) + (progn + (setq squid-eval t) + (org-latex-export-to-latex) + (setq squid-eval nil))) - [_] : Expression Σ Γ t → Expression Σ Γ (array t 1) - - unbox : Expression Σ Γ (array t 1) → Expression Σ Γ t - - merge : Expression Σ Γ (array t m) → - Expression Σ Γ (array t n) → - Expression Σ Γ (fin (suc n)) → - Expression Σ Γ (array t (n ℕ.+ m)) - - slice : Expression Σ Γ (array t (n ℕ.+ m)) → - Expression Σ Γ (fin (suc n)) → - Expression Σ Γ (array t m) - - cut : Expression Σ Γ (array t (n ℕ.+ m)) → - Expression Σ Γ (fin (suc n)) → - Expression Σ Γ (array t n) - - cast : .(eq : m ≡ n) → - Expression Σ Γ (array t m) → - Expression Σ Γ (array t n) - - -_ : ⦃ IsNumeric t ⦄ → Expression Σ Γ t → Expression Σ Γ t - - _+_ : ⦃ isNum₁ : IsNumeric t₁ ⦄ → - ⦃ isNum₂ : IsNumeric t₂ ⦄ → - Expression Σ Γ t₁ → - Expression Σ Γ t₂ → - Expression Σ Γ (isNum₁ +ᵗ isNum₂) - - _*_ : ⦃ isNum₁ : IsNumeric t₁ ⦄ → - ⦃ isNum₂ : IsNumeric t₂ ⦄ → - Expression Σ Γ t₁ → - Expression Σ Γ t₂ → - Expression Σ Γ (isNum₁ +ᵗ isNum₂) - - _^_ : ⦃ IsNumeric t ⦄ → - Expression Σ Γ t → - (n : ℕ) → - Expression Σ Γ t - - _>>_ : Expression Σ Γ int → - (n : ℕ) → - Expression Σ Γ int - - rnd : Expression Σ Γ real → Expression Σ Γ int - - fin : ∀ {ms} → - (f : literalTypes (map fin ms) → Fin n) → - Expression Σ Γ (tuple {n = k} (map fin ms)) → - Expression Σ Γ (fin n) - - asInt : Expression Σ Γ (fin n) → Expression Σ Γ int - - nil : Expression Σ Γ (tuple []) - - cons : Expression Σ Γ t → - Expression Σ Γ (tuple ts) → - Expression Σ Γ (tuple (t ∷ ts)) +(let* ((outfile (org-export-output-file-name ".tex"))) + (shell-command-to-string (concat "texcount -0 -sum \'" outfile "\'"))) +#+end_src - head : Expression Σ Γ (tuple (t ∷ ts)) → Expression Σ Γ t +Main chapters word count: call_wordcount() - tail : Expression Σ Γ (tuple (t ∷ ts)) → Expression Σ Γ (tuple ts) +#+begin_export latex +Methodology used to generate that word count: - call : (f : Function Σ Δ t) → - All (Expression Σ Γ) Δ → - Expression Σ Γ t +\begin{quote} +\begin{verbatim} +$ texcount -0 -sum report.tex +xyz +\end{verbatim} +\end{quote} - if_then_else_ : Expression Σ Γ bool → - Expression Σ Γ t → - Expression Σ Γ t → - Expression Σ Γ t +\end{sffamily} +\onehalfspacing +#+end_export -data Reference Σ Γ where - state : ∀ i → Reference Σ Γ (lookup Σ i) +* Abstract +:PROPERTIES: +:unnumbered: t +:END: - var : ∀ i → Reference Σ Γ (lookup Γ i) +#+latex: \ifsubmission\else - [_] : Reference Σ Γ t → Reference Σ Γ (array t 1) +* Acknowledgements +:PROPERTIES: +:unnumbered: t +:END: - unbox : Reference Σ Γ (array t 1) → Reference Σ Γ t +#+latex: \fi +#+latex: \cleardoublepage - merge : Reference Σ Γ (array t m) → - Reference Σ Γ (array t n) → - Expression Σ Γ (fin (suc n)) → - Reference Σ Γ (array t (n ℕ.+ m)) +#+toc: headlines 4 +# #+toc: listings +# #+toc: tables - slice : Reference Σ Γ (array t (n ℕ.+ m)) → - Expression Σ Γ (fin (suc n)) → - Reference Σ Γ (array t m) +#+latex: %TC:endignore - cut : Reference Σ Γ (array t (n ℕ.+ m)) → - Expression Σ Γ (fin (suc n)) → - Reference Σ Γ (array t n) +* Introduction - cast : .(eq : m ≡ n) → - Reference Σ Γ (array t m) → - Reference Σ Γ (array t n) +#+latex: \label{firstcontentpage} + +The ultimate goal of this work was to formally verify an implementation +[cite:@10.46586/tches.v2022.i1.482-505] of the number-theoretic transform (NTT) +for the Armv8.1-M architecture. The NTT is a vital procedure for lattice-based +post-quantum cryptography (FIXME: cite). To ensure internet-connected embedded +devices remain secure in the future of large-scale quantum computers, +implementations of these algorithms, and hence the NTT, are required for the +architectures they use. One common architecture used by embedded devices is +Armv8-M (FIXME: cite). Due to the resource-constrained nature of an embedded +device, and the huge computational demands of post-quantum cryptography, +algorithms like the NTT are presented using hand-written, highly-optimised +assembly code. To ensure the correctness of these cryptographic algorithms, and +thus the security of embedded devices, formal verification of these algorithms +is necessary. + +This report focuses on formalising the semantics of the Armv8-M architecture. +[cite/t:@arm/DDI0553B.s] provides a pseudocode description of the operation of +Armv8-M instructions using the Arm pseudocode (henceforth "the pseudocode"). +Unfortunately this language is primarily designed for describing instructions +(FIXME: cite) and not proving properties of executing them. + +To remedy this, I designed AMPSL, which mocks the pseudocode specification +language. AMPSL is written in the dependently-typed Agda proof assistant +[cite:@10.1007/978-3-642-03359-9_6]. The syntax mirrors that of the pseudocode, +save some minor modifications due to limitations within Agda and adjustments to +simplify the semantics. Using Agda enables AMPSL, its semantics, and proofs +using and about the semantics to be written using a single language. + +AMPSL is given semantics in two different forms. The first is a denotational +semantics, which converts the various program elements into functions within +Agda. This enables the explicit computation of the effect of AMPSL on the +processor state. AMPSL also has a set of Hoare logic rules, which form an +axiomatic, syntax-directed approach to describing how a statement in AMPSL +modifies assertions on the processor state. + +Another significant line of work undertaken by this report is the formal +verification of Barrett reduction. Barrett reduction is an important subroutine +used by the NTT, to efficiently find a "small" representable of a value modulo +some base [cite:@10.1007/3-540-47721-7_24]. Much like how a formalisation of the +NTT is a big step in formalising the behaviour of many PQC algorithms, +formalising Barrett reduction is a big step in formalising the NTT. + +The main contributions of this report are as follows: +- In [[*AMPSL Syntax]], I introduce the syntax of the AMPSL programming language. + The primary goal of the syntax is to facilitate easy translation of programs + from the Arm pseudocode, detailed in [[*Arm Pseudocode]] into AMPSL, whilst + allowing AMPSL semantics to remain simple. +- The semantics of AMPSL are described in [[*AMPSL Semantics]]. The primary + achievement of this work is the simple semantics of AMPSL, which facilitates + straight-forward proofs about various AMPSL programs. I detail both a + denotational semantics and a Hoare logic for AMPSL. The Hoare logic used by + AMPSL somewhat varies from the traditional presentation, given in [[*Hoare + Logic]], to enforce that Hoare logic proofs have bounded depth with respect to + the program syntax. +- In [[*Soundness of AMPSL's Hoare Logic]], I prove that the Hoare logic rules for + AMPSL are sound with respect to the denotational semantics. This proof is + possible due to Agda's foundation of Martin-Löf's type theory, the + significance of which is given in [[*Agda]]. Due to the soundness of AMPSL's Hoare + logic, the behaviour of the computationally-intensive denotational semantics + can instead be specified using syntax-directed Hoare logic. +- A number of example proofs for AMPSL programs are given in [[*Using AMPSL for + Proofs]]. This demonstrates the viability of using AMPSL for the formal + verification of a number of programs, and lays the groundwork for the formal + verification of the NTT given by [cite/t:@10.46586/tches.v2022.i1.482-505]. +- Finally, a formal proof of a Barrett reduction variant is given in [[*Proof of + Barrett Reduction]]. (FIXME: As far as I can tell) giving this well-used + algorithm a formal machine proof is a novel endeavour. Further, it is the + first proof of Barrett reduction on a domain other than integers and + rationals. + + +# This is the introduction where you should introduce your work. In +# general the thing to aim for here is to describe a little bit of the +# context for your work -- why did you do it (motivation), what was the +# hoped-for outcome (aims) -- as well as trying to give a brief overview +# of what you actually did. + +# It's often useful to bring forward some ``highlights'' into this +# chapter (e.g.\ some particularly compelling results, or a particularly +# interesting finding). + +# It's also traditional to give an outline of the rest of the document, +# although without care this can appear formulaic and tedious. Your +# call. - nil : Reference Σ Γ (tuple []) +* Background - cons : Reference Σ Γ t → - Reference Σ Γ (tuple ts) → - Reference Σ Γ (tuple (t ∷ ts)) +# A more extensive coverage of what's required to understand your work. +# In general you should assume the reader has a good undergraduate +# degree in computer science, but is not necessarily an expert in the +# particular area you have been working on. Hence this chapter may need to +# summarize some ``text book'' material. - head : Reference Σ Γ (tuple (t ∷ ts)) → Reference Σ Γ t +# This is not something you'd normally require in an academic paper, and +# it may not be appropriate for your particular circumstances. Indeed, +# in some cases it's possible to cover all of the ``background'' +# material either in the introduction or at appropriate places in the +# rest of the dissertation. - tail : Reference Σ Γ (tuple (t ∷ ts)) → Reference Σ Γ (tuple ts) +** Arm Pseudocode +** Hoare Logic +** Agda +* Related Work -data LocalReference Σ Γ where - var : ∀ i → LocalReference Σ Γ (lookup Γ i) +# This chapter covers relevant (and typically, recent) research +# which you build upon (or improve upon). There are two complementary +# goals for this chapter: +# \begin{enumerate} +# \item to show that you know and understand the state of the art; and +# \item to put your work in context +# \end{enumerate} - [_] : LocalReference Σ Γ t → LocalReference Σ Γ (array t 1) +# Ideally you can tackle both together by providing a critique of +# related work, and describing what is insufficient (and how you do +# better!) - unbox : LocalReference Σ Γ (array t 1) → LocalReference Σ Γ t +# The related work chapter should usually come either near the front or +# near the back of the dissertation. The advantage of the former is that +# you get to build the argument for why your work is important before +# presenting your solution(s) in later chapters; the advantage of the +# latter is that don't have to forward reference to your solution too +# much. The correct choice will depend on what you're writing up, and +# your own personal preference. - merge : LocalReference Σ Γ (array t m) → - LocalReference Σ Γ (array t n) → - Expression Σ Γ (fin (suc n)) → - LocalReference Σ Γ (array t (n ℕ.+ m)) +* Design of AMPSL and its Semantics - slice : LocalReference Σ Γ (array t (n ℕ.+ m)) → - Expression Σ Γ (fin (suc n)) → - LocalReference Σ Γ (array t m) +# This chapter may be called something else... but in general the +# idea is that you have one (or a few) ``meat'' chapters which describe +# the work you did in technical detail. - cut : LocalReference Σ Γ (array t (n ℕ.+ m)) → - Expression Σ Γ (fin (suc n)) → - LocalReference Σ Γ (array t n) +** AMPSL Syntax - cast : .(eq : m ≡ n) → - LocalReference Σ Γ (array t m) → - LocalReference Σ Γ (array t n) +** AMPSL Semantics - nil : LocalReference Σ Γ (tuple []) +* Properties and Evaluation of AMPSL - cons : LocalReference Σ Γ t → - LocalReference Σ Γ (tuple ts) → - LocalReference Σ Γ (tuple (t ∷ ts)) +# For any practical projects, you should almost certainly have some kind +# of evaluation, and it's often useful to separate this out into its own +# chapter. - head : LocalReference Σ Γ (tuple (t ∷ ts)) → LocalReference Σ Γ t +** Soundness of AMPSL's Hoare Logic - tail : LocalReference Σ Γ (tuple (t ∷ ts)) → LocalReference Σ Γ (tuple ts) +** Using AMPSL for Proofs +* Proof of Barrett Reduction -data Statement Σ Γ where - _∙_ : Statement Σ Γ → Statement Σ Γ → Statement Σ Γ - skip : Statement Σ Γ - _≔_ : Reference Σ Γ t → Expression Σ Γ t → Statement Σ Γ - declare : Expression Σ Γ t → Statement Σ (t ∷ Γ) → Statement Σ Γ - invoke : (f : Procedure Σ Δ) → All (Expression Σ Γ) Δ → Statement Σ Γ - for : ∀ n → Statement Σ (fin n ∷ Γ) → Statement Σ Γ - if_then_ : Expression Σ Γ bool → Statement Σ Γ → Statement Σ Γ - if_then_else_ : Expression Σ Γ bool → Statement Σ Γ → Statement Σ Γ → Statement Σ Γ +# This chapter may be called something else\ldots but in general the +# idea is that you have one (or a few) ``meat'' chapters which describe +# the work you did in technical detail. +* Summary and Conclusions -data LocalStatement Σ Γ where - _∙_ : LocalStatement Σ Γ → LocalStatement Σ Γ → LocalStatement Σ Γ - skip : LocalStatement Σ Γ - _≔_ : LocalReference Σ Γ t → Expression Σ Γ t → LocalStatement Σ Γ - declare : Expression Σ Γ t → LocalStatement Σ (t ∷ Γ) → LocalStatement Σ Γ - for : ∀ n → LocalStatement Σ (fin n ∷ Γ) → LocalStatement Σ Γ - if_then_ : Expression Σ Γ bool → LocalStatement Σ Γ → LocalStatement Σ Γ - if_then_else_ : Expression Σ Γ bool → - LocalStatement Σ Γ → - LocalStatement Σ Γ → - LocalStatement Σ Γ +# As you might imagine: summarizes the dissertation, and draws any +# conclusions. Depending on the length of your work, and how well you +# write, you may not need a summary here. +# You will generally want to draw some conclusions, and point to +# potential future work. -data Function Σ Γ ret where - init_∙_end : Expression Σ Γ ret → - LocalStatement Σ (ret ∷ Γ) → - Function Σ Γ ret +#+latex: \label{lastcontentpage} -data Procedure Σ Γ where - _∙end : Statement Σ Γ → Procedure Σ Γ -#+end_src +#+latex: %TC:ignore -* MIPSL Denotational Semantics -#+begin_src agda2 -expr : Expression Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -exprs : All (Expression Σ Γ) ts → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ ts ⟧ₜₛ -ref : Reference Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -locRef : LocalReference Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -stmt : Statement Σ Γ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ -locStmt : LocalStatement Σ Γ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ -fun : Function Σ Γ t → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ t ⟧ₜ -proc : Procedure Σ Γ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Σ ⟧ₜₛ - -assign : Reference Σ Γ t → ⟦ t ⟧ₜ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → - ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ -locAssign : LocalReference Σ Γ t → ⟦ t ⟧ₜ → ⟦ Σ ⟧ₜₛ × ⟦ Γ ⟧ₜₛ → - ⟦ Γ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ - -expr (lit {t = t} x) = const (Κ[ t ] x) -expr {Σ = Σ} (state i) = fetch i Σ ∘ proj₁ -expr {Γ = Γ} (var i) = fetch i Γ ∘ proj₂ -expr (e ≟ e₁) = lift ∘ does ∘ uncurry ≈-dec ∘ < expr e , expr e₁ > -expr (e <? e₁) = lift ∘ does ∘ uncurry <-dec ∘ < expr e , expr e₁ > -expr (inv e) = lift ∘ Bool.not ∘ lower ∘ expr e -expr (e && e₁) = lift ∘ uncurry (Bool._∧_ on lower) ∘ - < expr e , expr e₁ > -expr (e || e₁) = lift ∘ uncurry (Bool._∨_ on lower) ∘ - < expr e , expr e₁ > -expr (not e) = map (lift ∘ Bool.not ∘ lower) ∘ expr e -expr (e and e₁) = uncurry (zipWith (lift ∘₂ Bool._∧_ on lower)) ∘ - < expr e , expr e₁ > -expr (e or e₁) = uncurry (zipWith (lift ∘₂ Bool._∨_ on lower)) ∘ - < expr e , expr e₁ > -expr [ e ] = (_∷ []) ∘ expr e -expr (unbox e) = Vec.head ∘ expr e -expr (merge e e₁ e₂) = uncurry (uncurry mergeVec) ∘ - < < expr e , expr e₁ > , lower ∘ expr e₂ > -expr (slice e e₁) = uncurry sliceVec ∘ < expr e , lower ∘ expr e₁ > -expr (cut e e₁) = uncurry cutVec ∘ < expr e , lower ∘ expr e₁ > -expr (cast eq e) = castVec eq ∘ expr e -expr (- e) = neg ∘ expr e -expr (e + e₁) = uncurry add ∘ < expr e , expr e₁ > -expr (e * e₁) = uncurry mul ∘ < expr e , expr e₁ > -expr (e ^ x) = flip pow x ∘ expr e -expr (e >> n) = lift ∘ flip shift n ∘ lower ∘ expr e -expr (rnd e) = lift ∘ ⌊_⌋ ∘ lower ∘ expr e -expr (fin {ms = ms} f e) = lift ∘ f ∘ lowerFin ms ∘ expr e -expr (asInt e) = lift ∘ 𝕀⇒ℤ ∘ 𝕀.+_ ∘ Fin.toℕ ∘ lower ∘ expr e -expr nil = const _ -expr (cons {ts = ts} e e₁) = uncurry (cons′ ts) ∘ < expr e , expr e₁ > -expr (head {ts = ts} e) = head′ ts ∘ expr e -expr (tail {ts = ts} e) = tail′ ts ∘ expr e -expr (call f es) = fun f ∘ < proj₁ , exprs es > -expr (if e then e₁ else e₂) = uncurry (uncurry Bool.if_then_else_) ∘ - < < lower ∘ expr e , expr e₁ > , expr e₂ > - -exprs [] = const _ -exprs (e ∷ []) = expr e -exprs (e ∷ e₁ ∷ es) = < expr e , exprs (e₁ ∷ es) > - -ref {Σ = Σ} (state i) = fetch i Σ ∘ proj₁ -ref {Γ = Γ} (var i) = fetch i Γ ∘ proj₂ -ref [ r ] = (_∷ []) ∘ ref r -ref (unbox r) = Vec.head ∘ ref r -ref (merge r r₁ e) = uncurry (uncurry mergeVec) ∘ - < < ref r , ref r₁ > , lower ∘ expr e > -ref (slice r e) = uncurry sliceVec ∘ < ref r , lower ∘ expr e > -ref (cut r e) = uncurry cutVec ∘ < ref r , lower ∘ expr e > -ref (cast eq r) = castVec eq ∘ ref r -ref nil = const _ -ref (cons {ts = ts} r r₁) = uncurry (cons′ ts) ∘ < ref r , ref r₁ > -ref (head {ts = ts} r) = head′ ts ∘ ref r -ref (tail {ts = ts} r) = tail′ ts ∘ ref r - -locRef {Γ = Γ} (var i) = fetch i Γ ∘ proj₂ -locRef [ r ] = (_∷ []) ∘ locRef r -locRef (unbox r) = Vec.head ∘ locRef r -locRef (merge r r₁ e) = uncurry (uncurry mergeVec) ∘ - < < locRef r , locRef r₁ > , lower ∘ expr e > -locRef (slice r e) = uncurry sliceVec ∘ < locRef r , lower ∘ expr e > -locRef (cut r e) = uncurry cutVec ∘ < locRef r , lower ∘ expr e > -locRef (cast eq r) = castVec eq ∘ locRef r -locRef nil = const _ -locRef (cons {ts = ts} r r₁) = uncurry (cons′ ts) ∘ < locRef r , locRef r₁ > -locRef (head {ts = ts} r) = head′ ts ∘ locRef r -locRef (tail {ts = ts} r) = tail′ ts ∘ locRef r - -assign {Σ = Σ} (state i) val σ,γ = < updateAt i Σ val ∘ proj₁ , proj₂ > -assign {Γ = Γ} (var i) val σ,γ = < proj₁ , updateAt i Γ val ∘ proj₂ > -assign [ r ] val σ,γ = assign r (Vec.head val) σ,γ -assign (unbox r) val σ,γ = assign r (val ∷ []) σ,γ -assign (merge r r₁ e) val σ,γ = let i = expr e σ,γ in - assign r₁ (cutVec val (lower i)) σ,γ ∘ - assign r (sliceVec val (lower i)) σ,γ -assign (slice r e) val σ,γ = let i = expr e σ,γ in - let cut = cutVec (ref r σ,γ) (lower i) in - assign r (mergeVec val cut (lower i)) σ,γ -assign (cut r e) val σ,γ = let i = expr e σ,γ in - let slice = sliceVec (ref r σ,γ) (lower i) in - assign r (mergeVec slice val (lower i)) σ,γ -assign (cast eq r) val σ,γ = assign r (castVec (sym eq) val) σ,γ -assign nil val σ,γ = id -assign (cons {ts = ts} r r₁) val σ,γ = assign r₁ (tail′ ts val) σ,γ ∘ - assign r (head′ ts val) σ,γ -assign (head {ts = ts} r) val σ,γ = assign r (cons′ ts val (ref (tail r) σ,γ)) σ,γ -assign (tail {ts = ts} r) val σ,γ = assign r (cons′ ts (ref (head r) σ,γ) val) σ,γ - -locAssign {Γ = Γ} (var i) val σ,γ = updateAt i Γ val -locAssign [ r ] val σ,γ = locAssign r (Vec.head val) σ,γ -locAssign (unbox r) val σ,γ = locAssign r (val ∷ []) σ,γ -locAssign (merge r r₁ e) val σ,γ = let i = expr e σ,γ in - locAssign r₁ (cutVec val (lower i)) σ,γ ∘ - locAssign r (sliceVec val (lower i)) σ,γ -locAssign (slice r e) val σ,γ = let i = expr e σ,γ in - let cut = cutVec (ref r σ,γ) (lower i) in - locAssign r (mergeVec val cut (lower i)) σ,γ -locAssign (cut r e) val σ,γ = let i = expr e σ,γ in - let slice = sliceVec (ref r σ,γ) (lower i) in - locAssign r (mergeVec slice val (lower i)) σ,γ -locAssign (cast eq r) val σ,γ = locAssign r (castVec (sym eq) val) σ,γ -locAssign nil val σ,γ = id -locAssign (cons {ts = ts} r r₁) val σ,γ = locAssign r₁ (tail′ ts val) σ,γ ∘ - locAssign r (head′ ts val) σ,γ -locAssign (head {ts = ts} r) val σ,γ = locAssign r (cons′ ts val (locRef (tail r) σ,γ)) σ,γ -locAssign (tail {ts = ts} r) val σ,γ = locAssign r (cons′ ts (locRef (head r) σ,γ) val) σ,γ - -stmt (s ∙ s₁) = stmt s₁ ∘ stmt s -stmt skip = id -stmt (ref ≔ val) = uncurry (uncurry (assign ref)) ∘ - < < expr val , id > , id > -stmt {Γ = Γ} (declare e s) = < proj₁ , tail′ Γ ∘ proj₂ > ∘ - stmt s ∘ - < proj₁ , uncurry (cons′ Γ) ∘ < expr e , proj₂ > > -stmt (invoke p es) = < proc p ∘ < proj₁ , exprs es > , proj₂ > -stmt (if e then s) = uncurry (uncurry Bool.if_then_else_) ∘ - < < lower ∘ expr e , stmt s > , id > -stmt (if e then s else s₁) = uncurry (uncurry Bool.if_then_else_) ∘ - < < lower ∘ expr e , stmt s > , stmt s₁ > -stmt {Γ = Γ} (for m s) = Vec.foldl _ - (flip λ i → ( - < proj₁ , tail′ Γ ∘ proj₂ > ∘ - stmt s ∘ - < proj₁ , cons′ Γ (lift i) ∘ proj₂ >) ∘_) - id - (Vec.allFin m) - -locStmt (s ∙ s₁) = locStmt s₁ ∘ < proj₁ , locStmt s > -locStmt skip = proj₂ -locStmt (ref ≔ val) = uncurry (uncurry (locAssign ref)) ∘ - < < expr val , id > , proj₂ > -locStmt {Γ = Γ} (declare e s) = tail′ Γ ∘ - locStmt s ∘ - < proj₁ , uncurry (cons′ Γ) ∘ < expr e , proj₂ > > -locStmt (if e then s) = uncurry (uncurry Bool.if_then_else_) ∘ - < < lower ∘ expr e , locStmt s > , proj₂ > -locStmt (if e then s else s₁) = uncurry (uncurry Bool.if_then_else_) ∘ - < < lower ∘ expr e , locStmt s > , locStmt s₁ > -locStmt {Γ = Γ} (for m s) = proj₂ ∘ Vec.foldl _ - (flip λ i → ( - < proj₁ , tail′ Γ ∘ locStmt s > ∘ - < proj₁ , cons′ Γ (lift i) ∘ proj₂ >) ∘_) - id - (Vec.allFin m) - -fun {Γ = Γ} (init e ∙ s end) = fetch zero (_ ∷ Γ) ∘ - locStmt s ∘ - < proj₁ , uncurry (cons′ Γ) ∘ < expr e , proj₂ > > - -proc (s ∙end) = proj₁ ∘ stmt s -#+end_src +#+print_bibliography: -# LocalWords: Hoare ISAs Jasmin CompCert structs bitstring bitstrings getters MIPSL +\appendix +#+latex: \label{lastpage} #+latex: %TC:endignore - -* MIPSL Hoare Logic Definitions -First, the definition and semantics of ~Term~. -#+begin_src agda2 -data Term (Σ : Vec Type i) (Γ : Vec Type j) (Δ : Vec Type k) : Type → Set ℓ where - lit : ⟦ t ⟧ₜ → Term Σ Γ Δ t - - state : ∀ i → Term Σ Γ Δ (lookup Σ i) - - var : ∀ i → Term Σ Γ Δ (lookup Γ i) - - meta : ∀ i → Term Σ Γ Δ (lookup Δ i) - - _≟_ : ⦃ HasEquality t ⦄ → - Term Σ Γ Δ t → - Term Σ Γ Δ t → - Term Σ Γ Δ bool - - _<?_ : ⦃ Ordered t ⦄ → - Term Σ Γ Δ t → - Term Σ Γ Δ t → - Term Σ Γ Δ bool - - inv : Term Σ Γ Δ bool → Term Σ Γ Δ bool - - _&&_ : Term Σ Γ Δ bool → Term Σ Γ Δ bool → Term Σ Γ Δ bool - - _||_ : Term Σ Γ Δ bool → Term Σ Γ Δ bool → Term Σ Γ Δ bool - - not : Term Σ Γ Δ (bits n) → Term Σ Γ Δ (bits n) - - _and_ : Term Σ Γ Δ (bits n) → - Term Σ Γ Δ (bits n) → - Term Σ Γ Δ (bits n) - - _or_ : Term Σ Γ Δ (bits n) → - Term Σ Γ Δ (bits n) → - Term Σ Γ Δ (bits n) - - [_] : Term Σ Γ Δ t → Term Σ Γ Δ (array t 1) - - unbox : Term Σ Γ Δ (array t 1) → Term Σ Γ Δ t - - merge : Term Σ Γ Δ (array t m) → - Term Σ Γ Δ (array t n) → - Term Σ Γ Δ (fin (suc n)) → - Term Σ Γ Δ (array t (n ℕ.+ m)) - - slice : Term Σ Γ Δ (array t (n ℕ.+ m)) → - Term Σ Γ Δ (fin (suc n)) → - Term Σ Γ Δ (array t m) - - cut : Term Σ Γ Δ (array t (n ℕ.+ m)) → - Term Σ Γ Δ (fin (suc n)) → - Term Σ Γ Δ (array t n) - - cast : .(eq : m ≡ n) → - Term Σ Γ Δ (array t m) → - Term Σ Γ Δ (array t n) - - -_ : ⦃ IsNumeric t ⦄ → Term Σ Γ Δ t → Term Σ Γ Δ t - - _+_ : ⦃ isNum₁ : IsNumeric t₁ ⦄ → - ⦃ isNum₂ : IsNumeric t₂ ⦄ → - Term Σ Γ Δ t₁ → - Term Σ Γ Δ t₂ → - Term Σ Γ Δ (isNum₁ +ᵗ isNum₂) - - _*_ : ⦃ isNum₁ : IsNumeric t₁ ⦄ → - ⦃ isNum₂ : IsNumeric t₂ ⦄ → - Term Σ Γ Δ t₁ → - Term Σ Γ Δ t₂ → - Term Σ Γ Δ (isNum₁ +ᵗ isNum₂) - - _^_ : ⦃ IsNumeric t ⦄ → Term Σ Γ Δ t → ℕ → Term Σ Γ Δ t - - _>>_ : Term Σ Γ Δ int → (n : ℕ) → Term Σ Γ Δ int - - rnd : Term Σ Γ Δ real → Term Σ Γ Δ int - - fin : ∀ {ms} → - (f : literalTypes (map fin ms) → Fin n) → - Term Σ Γ Δ (tuple {n = o} (map fin ms)) → - Term Σ Γ Δ (fin n) - - asInt : Term Σ Γ Δ (fin n) → Term Σ Γ Δ int - - nil : Term Σ Γ Δ (tuple []) - - cons : Term Σ Γ Δ t → - Term Σ Γ Δ (tuple ts) → - Term Σ Γ Δ (tuple (t ∷ ts)) - - head : Term Σ Γ Δ (tuple (t ∷ ts)) → Term Σ Γ Δ t - - tail : Term Σ Γ Δ (tuple (t ∷ ts)) → Term Σ Γ Δ (tuple ts) - - call : Function ts ts₁ t → - All (Term Σ Γ Δ) ts → - All (Term Σ Γ Δ) ts₁ → - Term Σ Γ Δ t - - if_then_else_ : Term Σ Γ Δ bool → - Term Σ Γ Δ t → - Term Σ Γ Δ t → - Term Σ Γ Δ t - -⟦_⟧ : Term Σ Γ Δ t → ⟦ Σ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ → ⟦ Δ ⟧ₜₛ → ⟦ t ⟧ₜ -⟦_⟧ₛ : All (Term Σ Γ Δ) ts → ⟦ Σ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ → ⟦ Δ ⟧ₜₛ → ⟦ ts ⟧ₜₛ - -⟦ lit x ⟧ σ γ δ = x -⟦ state i ⟧ σ γ δ = fetch i Σ σ -⟦ var i ⟧ σ γ δ = fetch i Γ γ -⟦ meta i ⟧ σ γ δ = fetch i Δ δ -⟦ e ≟ e₁ ⟧ σ γ δ = (lift ∘₂ does ∘₂ ≈-dec) - (⟦ e ⟧ σ γ δ) - (⟦ e₁ ⟧ σ γ δ) -⟦ e <? e₁ ⟧ σ γ δ = (lift ∘₂ does ∘₂ <-dec) - (⟦ e ⟧ σ γ δ) - (⟦ e₁ ⟧ σ γ δ) -⟦ inv e ⟧ σ γ δ = lift ∘ Bool.not ∘ lower $ ⟦ e ⟧ σ γ δ -⟦ e && e₁ ⟧ σ γ δ = (lift ∘₂ Bool._∧_ on lower) - (⟦ e ⟧ σ γ δ) (⟦ e₁ ⟧ σ γ δ) -⟦ e || e₁ ⟧ σ γ δ = (lift ∘₂ Bool._∨_ on lower) - (⟦ e ⟧ σ γ δ) - (⟦ e₁ ⟧ σ γ δ) -⟦ not e ⟧ σ γ δ = map (lift ∘ Bool.not ∘ lower) (⟦ e ⟧ σ γ δ) -⟦ e and e₁ ⟧ σ γ δ = zipWith (lift ∘₂ Bool._∧_ on lower) - (⟦ e ⟧ σ γ δ) - (⟦ e₁ ⟧ σ γ δ) -⟦ e or e₁ ⟧ σ γ δ = zipWith (lift ∘₂ Bool._∨_ on lower) - (⟦ e ⟧ σ γ δ) - (⟦ e₁ ⟧ σ γ δ) -⟦ [ e ] ⟧ σ γ δ = ⟦ e ⟧ σ γ δ ∷ [] -⟦ unbox e ⟧ σ γ δ = Vec.head (⟦ e ⟧ σ γ δ) -⟦ merge e e₁ e₂ ⟧ σ γ δ = mergeVec - (⟦ e ⟧ σ γ δ) - (⟦ e₁ ⟧ σ γ δ) - (lower (⟦ e₂ ⟧ σ γ δ)) -⟦ slice e e₁ ⟧ σ γ δ = sliceVec - (⟦ e ⟧ σ γ δ) - (lower (⟦ e₁ ⟧ σ γ δ)) -⟦ cut e e₁ ⟧ σ γ δ = cutVec - (⟦ e ⟧ σ γ δ) - (lower (⟦ e₁ ⟧ σ γ δ)) -⟦ cast eq e ⟧ σ γ δ = castVec eq (⟦ e ⟧ σ γ δ) -⟦ - e ⟧ σ γ δ = neg (⟦ e ⟧ σ γ δ) -⟦ e + e₁ ⟧ σ γ δ = add (⟦ e ⟧ σ γ δ) (⟦ e₁ ⟧ σ γ δ) -⟦ e * e₁ ⟧ σ γ δ = mul (⟦ e ⟧ σ γ δ) (⟦ e₁ ⟧ σ γ δ) -⟦ e ^ x ⟧ σ γ δ = pow (⟦ e ⟧ σ γ δ) x -⟦ e >> n ⟧ σ γ δ = lift ∘ flip shift n ∘ lower $ ⟦ e ⟧ σ γ δ -⟦ rnd e ⟧ σ γ δ = lift ∘ ⌊_⌋ ∘ lower $ ⟦ e ⟧ σ γ δ -⟦ fin {ms = ms} f e ⟧ σ γ δ = lift ∘ f ∘ lowerFin ms $ ⟦ e ⟧ σ γ δ -⟦ asInt e ⟧ σ γ δ = lift ∘ 𝕀⇒ℤ ∘ 𝕀.+_ ∘ Fin.toℕ ∘ lower $ ⟦ e ⟧ σ γ δ -⟦ nil ⟧ σ γ δ = _ -⟦ cons {ts = ts} e e₁ ⟧ σ γ δ = cons′ ts (⟦ e ⟧ σ γ δ) (⟦ e₁ ⟧ σ γ δ) -⟦ head {ts = ts} e ⟧ σ γ δ = head′ ts (⟦ e ⟧ σ γ δ) -⟦ tail {ts = ts} e ⟧ σ γ δ = tail′ ts (⟦ e ⟧ σ γ δ) -⟦ call f es es₁ ⟧ σ γ δ = Den.Semantics.fun f - (⟦ es ⟧ₛ σ γ δ , ⟦ es₁ ⟧ₛ σ γ δ) -⟦ if e then e₁ else e₂ ⟧ σ γ δ = Bool.if lower (⟦ e ⟧ σ γ δ) - then ⟦ e₁ ⟧ σ γ δ - else ⟦ e₂ ⟧ σ γ δ - -⟦_⟧ₛ [] σ γ δ = _ -⟦_⟧ₛ {ts = _ ∷ ts} (e ∷ es) σ γ δ = cons′ ts (⟦ e ⟧ σ γ δ) (⟦ es ⟧ₛ σ γ δ) -#+end_src - -And here is the definition of ~Assertion~ with its semantics. - -#+begin_src agda2 -data Assertion (Σ : Vec Type o) (Γ : Vec Type n) (Δ : Vec Type m) - : Set (ℓsuc ℓ) where - all : Assertion Σ Γ (t ∷ Δ) → Assertion Σ Γ Δ - some : Assertion Σ Γ (t ∷ Δ) → Assertion Σ Γ Δ - pred : Term Σ Γ Δ bool → Assertion Σ Γ Δ - true : Assertion Σ Γ Δ - false : Assertion Σ Γ Δ - ¬_ : Assertion Σ Γ Δ → Assertion Σ Γ Δ - _∧_ : Assertion Σ Γ Δ → Assertion Σ Γ Δ → Assertion Σ Γ Δ - _∨_ : Assertion Σ Γ Δ → Assertion Σ Γ Δ → Assertion Σ Γ Δ - _⟶_ : Assertion Σ Γ Δ → Assertion Σ Γ Δ → Assertion Σ Γ Δ - -⟦_⟧ : Assertion Σ Γ Δ → ⟦ Σ ⟧ₜₛ → ⟦ Γ ⟧ₜₛ → ⟦ Δ ⟧ₜₛ → Set ℓ -⟦_⟧ {Δ = Δ} (all P) σ γ δ = ∀ x → ⟦ P ⟧ σ γ (cons′ Δ x δ) -⟦_⟧ {Δ = Δ} (some P) σ γ δ = ∃ λ x → ⟦ P ⟧ σ γ (cons′ Δ x δ) -⟦ pred p ⟧ σ γ δ = Lift ℓ (Bool.T (lower (TS.⟦ p ⟧ σ γ δ))) -⟦ true ⟧ σ γ δ = Lift ℓ ⊤ -⟦ false ⟧ σ γ δ = Lift ℓ ⊥ -⟦ ¬ P ⟧ σ γ δ = ⟦ P ⟧ σ γ δ → ⊥ -⟦ P ∧ Q ⟧ σ γ δ = ⟦ P ⟧ σ γ δ × ⟦ Q ⟧ σ γ δ -⟦ P ∨ Q ⟧ σ γ δ = ⟦ P ⟧ σ γ δ ⊎ ⟦ Q ⟧ σ γ δ -⟦ P ⟶ Q ⟧ σ γ δ = ⟦ P ⟧ σ γ δ → ⟦ Q ⟧ σ γ δ -#+end_src |