Page 1
Dependability through Dependent Types
(or Peek and Poke with Dependent Types)
Thomas Hallgren
SICS, 2008-10-21
(based on a ProgLog meeting talk, Chalmers, December 2006)
Page 2
How safe is House?
Uses GHC (the Glasgow Haskell Compiler)
- The compiler is big!
- The run-time system is big!
- If we are implementing a micro-kernel in Haskell, this will be the
biggest part.
- Can it be trusted?
- Can critical properties be verified formally?
Page 3
How safe is House?
Does the Haskell type system give us what we need?
- The H(ardware) monad interface [1] relies on
run-time checks for safety
- Although safety isn't compromised, bugs can go undetected until
run-time.
- For some things, silent truncation is used, so bugs will
result in misbehavior rather than error messages.
- We don't know how to deal safely with DMA.
Page 4
At the Machine Level...
Primitives for memory access
Page 5
Safety Through Abstraction
- Safety = maintaining invariants...
- Build abstraction layers that guarantee that invariants are maintained
- Most code should be written on top of safe abstractions, obviously!
Page 6
Hardware Monad Examples
- Safety by abstract data types + silent truncation:
type PAddr = (PhysPage,POffset)
type PhysPage -- abstract
type POffset = Word12
getPAddr ∈ PAddr → H Word8
setPAddr ∈ PAddr → Word8 → H ()
allocPhysPage ∈ H (Maybe PhysPage)
- Safety by abstract data types + run-time bounds checks:
data MemRegion -- abstract
type Offset = Word32
pokeByteOff ∈ Storable a ⇒ MemRegion → Offset → a → H ()
peekByteOff ∈ Storable a ⇒ MemRegion → Offset → H a
Page 7
SafeHouse: an experiment
- (1) Start with a minimal compiler and run-time system
- There is hope that critical properties can be verified formally
- Grow it without sacrificing formal verification
- (2) Make use of dependent types
- More safety constraints can be captured as type-safety
- More safety checks can be moved from run-time to compile-time
- Potential for better performance!
Page 8
A Minimal Compiler and Run-Time System
- Compiler:
- Front-end: Alfa [6] (programming environment, type checking)
- Back-end: ~750 lines of Haskell code (excluding standard libraries)
- Input: a useful subset of the language Alfa supports
- Output: a small subset of C
- Run-time system size: ~630 lines of C
- Including a simple copying garbage collector: ~120 lines of C
- Compile-time configurable for hosted and bare-metal use
Page 9
Compiler and run-time system
Based on the <ν,G> machine [2]
- Designed to support parallel execution on a multi-processor machine
- Easy to support low-latency interrupt handling
- Very simple memory management
- No stack, everything is in the heap
- Root set for GC: just one pointer (ν, the current redex)
Page 10
An example
Hello world!
- A bootable executable that displays a message on the screen of a PC.
- Requires writing to the text screen buffer located at a particular
absolute address in the PC physical memory layout.
Page 11
Peek and Poke again
Primitives for memory access
Page 12
Abstraction for limited memory access
- A Safe Interface:
Readable ∈ Addr → Prop
Writable ∈ Addr → Prop
peek16 ∈ (a∈Addr) → Readable a → H Word16
poke16 ∈ (a∈Addr) → Writable a → Word16 → H ()
- Primitives to declare what constitutes safe memory access:
Unsafe.readable ∈ (a∈Addr) → Readable a
Unsafe.writable ∈ (a∈Addr) → Writable a
- Dependent types! Propositions as Types!
Lets us talk about values (memory addresses) at the type level.
Page 13
Text screen buffer access (1)
- Limit access to the text screen buffer
data Range = Range { lo,hi∈Addr }
screen ∈ Range
screen = Range 0xb8000 0xb8ffe
InRange ∈ Range → Addr → Prop
screenPoke ∈ (a∈Addr) → InRange screen a → Word16 → H ()
- Implementation of
screenPoke
writableScreen ∈ (a∈Addr) → InRange screen a → Writable a
writableScreen a r = Unsafe.writable a
screenPoke ∈ (a∈Addr)→ InRange screen a → Word16 → H ()
screenPoke a r d = poke16 a (writableScreen a r) d
Page 14
Text screen buffer access (2)
Using Predicate Based Subtyping
Page 15
Abstraction for displaying text
- Putting characters with attributes at specific screen locations
- Outputting text (scrolling if necessary)
- The code that needs to deal with the extra safety constraints is
small and localized!
Page 16
Proofs
- Computing the memory address for given screen coordinates:
charAddr ∈ Column → Row → ScreenAddr
charAddr (x,xr) (y,yr) = (lo screen + 2*(80*x+y),?)
- A lemma for
charAddr
:
All x,y . InRange xRange x ⇒
InRange yRange y ⇒
InRange screen (lo screen + 2*(80*x+y))
- Decidable, should prove itself:
- Formulate it as an expression of type
Bool
.
The proof that a boolean is True
is trivial.
- Requires a proof checker that has a reasonably efficient interpreter.
Others have worked on this problem [3].
Page 17
Example of extended static checking
Displaying strings that fit on the screen
Page 18
Page 19
Problems
- Modulo arithmetic
- Simple inequalities don't hold!
- i < base+size ⇔ i-base < size ?
- base ≤ i ⇒ base ≤ i+1 ?
- Termination checking
- Used forms of recursion not recognized by the termination checker.
Page 20
Future work?
- Do more of the current H monad interface.
- Make more complex memory access patterns safe:
- Manipulating linked data structures.
- Safe DMA programming.
- A dependently typed H monad (sketchy)?
- Separation Logic in the types? [4]
- Verifying the compiler and run-time system properties. (Use ideas from [5]?)
Page 21
The End
Any more questions?
Page 22
Page 23