Systems Programming
Type Contracts in Ada 2012 and SPARK 2014
Systems Programming – What is it?
Bare metal programming
bare board applications (no Operating System)
Operating Systems (ex: Muen separation kernel)
device drivers (ex: Ada Drivers Library)
communication stacks (ex: AdaCore TCP/IP stack)
Specifics of Systems Programming
direct access to hardware: registers, memory, etc.
side-effects (yes!)
efficiency is paramount (sometimes real-time even)
hard/impossible to debug
Systems Programming – How can SPARK help?
SPARK is a Systems Programming language
same features as Ada for accessing hardware (representation clauses, address clauses)
as efficient as Ada or C
Side-effects can be modeled in SPARK
reads and writes to memory-mapped devices are modeled
concurrent interactions with environment are modeled
SPARK can help catch problems by static analysis
correct flows, initialization, concurrent accesses
absence of run-time errors and preservation of invariants
Systems Programming – A trivial example
package Show_Trivial_Sys_Prog is Y : Integer; -- Y'Address could be replaced by any -- external address X : Integer with Volatile, Address => Y'Address; procedure Get (Val : out Integer) with Global => (In_Out => X), Depends => (Val => X, X => X); end Show_Trivial_Sys_Prog;package body Show_Trivial_Sys_Prog is procedure Get (Val : out Integer) is begin Val := X; end Get; end Show_Trivial_Sys_Prog;
Comments:
X
is volatileX
is also an output; outputX
depends on inputX
X
is only read
Volatile Variables and Volatile Types
Variables whose reads/writes cannot be optimized away
Identified through multiple aspects (or pragmas)
aspect
Volatile
but also aspect
Atomic
and GNAT aspect
Volatile_Full_Access
all the above aspects can be set on type or object
Other aspects are useful on volatile variables
aspect
Address
to specify location in memoryaspect
Import
to skip definition/initialization
type T is new Integer with Volatile;
X : Integer with Atomic, Import, Address => ... ;
Flavors of Volatile Variables
Using Async_Readers
/ Async_Writers
Boolean aspects describing asynchronous behavior
Async_Readers
if variable may be read asynchronouslyAsync_Writers
if variable may be written asynchronously
Effect of
Async_Readers
on flow analysisEffect of
Async_Writers
on flow analysis & proofalways initialized, always has an unknown value
package Volatile_Vars is pragma Elaborate_Body; Ext : array (1 .. 2) of Integer; X : Integer with Volatile, Address => Ext (1)'Address, Async_Readers; Y : Integer with Volatile, Address => Ext (2)'Address, Async_Writers; procedure Set; end Volatile_Vars;package body Volatile_Vars is procedure Set is U, V : constant Integer := Y; begin pragma Assert (U = V); X := 0; X := 1; end Set; begin Ext := (others => 0); end Volatile_Vars;
with Volatile_Vars; procedure Show_Volatile_Vars is begin Volatile_Vars.Set; end Show_Volatile_Vars;
Using Effective_Reads
/ Effective_Writes
Boolean aspects distinguishing values & sequences
Effective_Reads
if reading the variable has an effect on its valueEffective_Writes
if writing the variable has an effect on its value
Effect of both on proof and flow dependencies
Final value of variable is seen as a sequence of values it took
package Volatile_Vars is pragma Elaborate_Body; Ext : array (1 .. 2) of Integer; X : Integer with Volatile, Address => Ext (1)'Address, Async_Readers, Effective_Writes; Y : Integer with Volatile, Address => Ext (2)'Address, Async_Writers, Effective_Reads; procedure Set with Depends => (X => Y, Y => Y); end Volatile_Vars;package body Volatile_Vars is procedure Set is begin X := Y; X := 0; end Set; begin Ext := (others => 0); end Volatile_Vars;
with Volatile_Vars; procedure Show_Volatile_Vars is begin Volatile_Vars.Set; end Show_Volatile_Vars;
Combinations of Flavors of Volatile Variables
All four flavors can be set independently
Default for Volatile/Atomic is all four
True
When some aspects set, all others default to
False
Only half the possible combinations are legal
Async_Readers
and/orAsync_Writers
is setEffective_Reads = True
forcesAsync_Writers = True
Effective_Writes = True
forcesAsync_Readers = True
sensor:
AW=True
actuator:
AR=True
input port:
AW=True
,ER=True
output port:
AR=True
,EW=True
Constraints on Volatile Variables
Volatile variables must be defined at library level
Expressions (and functions) cannot have side-effects
read of variable with
AW=True
must appear alone on rhs of assigna function cannot read a variable with
ER=True
package Volatile_Vars is pragma Elaborate_Body; Ext : array (1 .. 4) of Integer; AR : Integer with Volatile, Address => Ext (1)'Address, Async_Readers; AW : Integer with Volatile, Address => Ext (2)'Address, Async_Writers; ER : Integer with Volatile, Address => Ext (3)'Address, Async_Writers, Effective_Reads; EW : Integer with Volatile, Address => Ext (4)'Address, Async_Readers, Effective_Writes; procedure Read_All; function Read_ER return Integer; procedure Set (V : Integer); end Volatile_Vars;package body Volatile_Vars is procedure Read_All is Tmp : Integer := 0; begin Tmp := Tmp + AR; Tmp := Tmp + AW; EW := Tmp; Set (ER); end Read_All; function Read_ER return Integer is Tmp : Integer := ER; begin return Tmp; end Read_ER; procedure Set (V : Integer) is begin AW := V; end Set; begin Ext := (others => 0); end Volatile_Vars;
with Volatile_Vars; procedure Show_Volatile_Vars is V : Integer; begin Volatile_Vars.Read_All; V := Volatile_Vars.Read_ER; end Show_Volatile_Vars;
Comments:
AW not alone on rhs
ER not alone on rhs
ER output of Read_ER
Constraints on Volatile Functions
Functions should have mathematical interpretation
a function reading a variable with
AW=True
is marked as volatile with aspectVolatile_Function
calls to volatile functions are restricted like reads of
Async_Writers
package Volatile_Vars is pragma Elaborate_Body; Ext : array (1 .. 4) of Integer; AR : Integer with Volatile, Address => Ext (1)'Address, Async_Readers; AW : Integer with Volatile, Address => Ext (2)'Address, Async_Writers; ER : Integer with Volatile, Address => Ext (3)'Address, Async_Writers, Effective_Reads; EW : Integer with Volatile, Address => Ext (4)'Address, Async_Readers, Effective_Writes; function Read_Non_Volatile return Integer; function Read_Volatile return Integer with Volatile_Function; function Read_ER return Integer with Volatile_Function; end Volatile_Vars;package body Volatile_Vars is function Read_Non_Volatile return Integer is Tmp : Integer := 0; begin -- reads AR, AW, EW -- ERROR: not a volatile function Tmp := Tmp + AR; Tmp := Tmp + AW; Tmp := Tmp + EW; return Tmp; end Read_Non_Volatile; function Read_Volatile return Integer is Tmp : Integer := 0; begin -- reads AR, AW, EW -- OK for volatile function Tmp := Tmp + AR; Tmp := Tmp + AW; Tmp := Tmp + EW; return Tmp; end Read_Volatile; function Read_ER return Integer is Tmp : Integer := ER; begin -- reads ER -- ERROR: ER output of Read_ER return Tmp; end Read_ER; begin Ext := (others => 0); end Volatile_Vars;
with Volatile_Vars; procedure Show_Volatile_Vars is V : Integer; begin V := Volatile_Vars.Read_Non_Volatile; V := Volatile_Vars.Read_Volatile; V := Volatile_Vars.Read_ER; end Show_Volatile_Vars;
State Abstraction on Volatile Variables
Abstract state needs to be identified as
External
Flavors of volatility can be specified
Default if none specified is all True
package P1 with Abstract_State => (S with External) is procedure Process (Data : out Integer) with Global => (In_Out => S); end P1;
package P2 with Abstract_State => (S with External => (Async_Writers, -- OK if refined into AW, ER Effective_Reads) -- not OK if refined into AR, EW ) is procedure Process (Data : out Integer) with Global => (In_Out => S); end P2;
Constraints on Address Attribute
Address of volatile variable can be specified
package Show_Address_Attribute is Ext : array (1 .. 2) of Integer; X : Integer with Volatile, Address => Ext (1)'Address; Y : Integer with Volatile; for Y'Address use Ext (2)'Address; end Show_Address_Attribute;
Address attribute not allowed in expressions
Overlays are allowed
GNATprove does not check absence of overlays
GNATprove does not model the resulting aliasing
procedure Show_Address_Overlay is X : Integer := 1; Y : Integer := 0 with Address => X'Address; pragma Assert (X = 1); -- assertion wrongly proved begin null; end Show_Address_Overlay;
Can something be known of volatile variables?
Variables with
Async_Writers
have no known value... but they have a known type!
type range, ex:
0 .. 360
type predicate, ex:
0 .. 15 | 17 .. 42 | 43 .. 360
Variables without
Async_Writers
have a known valueGNATprove also assumes all values are valid (
X'Valid
)
package Show_Provable_Volatile_Var is X : Integer with Volatile, Async_Readers; procedure Read_Value; end Show_Provable_Volatile_Var;package body Show_Provable_Volatile_Var is procedure Read_Value is begin X := 42; pragma Assert (X = 42); -- proved! end Read_Value; end Show_Provable_Volatile_Var;
Other Concerns in Systems Programming
Software startup state ⟶ elaboration rules
SPARK follows Ada static elaboration model
... with additional constraints for ensuring correct initialization
... but GNATprove follows the relaxed GNAT static elaboration
Handling of faults ⟶ exception handling
raising exceptions is allowed in SPARK
... but exception handlers are
SPARK_Mode => Off
... typically the last-chance-handler is used instead
Concurrency inside the application ⟶ tasking support
Ravenscar and Extended_Ravenscar profiles supported in SPARK
Code Examples / Pitfalls
Example #1
package Example_01 is Ext : Integer; X : Integer with Volatile, Address => Ext'Address; procedure Get (Val : out Integer) with Global => (Input => X), Depends => (Val => X); end Example_01;package body Example_01 is procedure Get (Val : out Integer) is begin Val := X; end Get; end Example_01;
This code is not correct. X
has Effective_Reads
set by default,
hence it is also an output.
Example #2
package Example_02 is Ext : Integer; X : Integer with Volatile, Address => Ext'Address, Async_Readers, Async_Writers, Effective_Writes; procedure Get (Val : out Integer) with Global => (Input => X), Depends => (Val => X); end Example_02;package body Example_02 is procedure Get (Val : out Integer) is begin Val := X; end Get; end Example_02;
This code is correct. X
has Effective_Reads = False
, hence it
is only an input.
Example #3
package Example_03 is Speed : Float with Volatile, Async_Writers; Motor : Float with Volatile, Async_Readers; procedure Adjust with Depends => (Motor =>+ Speed); end Example_03;package body Example_03 is procedure Adjust is Cur_Speed : constant Float := Speed; begin if abs (Cur_Speed) > 100.0 then Motor := Motor - 1.0; end if; end Adjust; end Example_03;
This code is correct. Speed
is an input only, Motor
is both an
input and output. Note how the current value of Speed
is first copied
to be tested in a larger expression.
Example #4
package Example_04 is Raw_Data : Float with Volatile, Async_Writers, Effective_Reads; Data : Float with Volatile, Async_Readers, Effective_Writes; procedure Smooth with Depends => (Data => Raw_Data); end Example_04;package body Example_04 is procedure Smooth is Data1 : constant Float := Raw_Data; Data2 : constant Float := Raw_Data; begin Data := Data1; Data := (Data1 + Data2) / 2.0; Data := Data2; end Smooth; end Example_04;
This code is not correct. Raw_Data
has Effective_Reads
set,
hence it is also an output.
Example #5
package Example_05 is type Regval is new Integer with Volatile; type Regnum is range 1 .. 32; type Registers is array (Regnum) of Regval; Regs : Registers with Async_Writers, Async_Readers; function Reg (R : Regnum) return Integer is (Integer (Regs (R))) with Volatile_Function; end Example_05;
This code is not correct. Regs
has Async_Writers
set, hence it
cannot appear as the expression in an expression function.
Example #6
package Example_06 is type Regval is new Integer with Volatile; type Regnum is range 1 .. 32; type Registers is array (Regnum) of Regval; Regs : Registers with Async_Writers, Async_Readers; function Reg (R : Regnum) return Integer with Volatile_Function; end Example_06;package body Example_06 is function Reg (R : Regnum) return Integer is V : Regval := Regs (R); begin return Integer (V); end Reg; end Example_06;
This code is not correct. Regval
is a volatile type, hence variable
V
is volatile and cannot be declared locally.
Example #7
package Example_07 is type Regval is new Integer with Volatile; type Regnum is range 1 .. 32; type Registers is array (Regnum) of Regval; Regs : Registers with Async_Writers, Async_Readers; function Reg (R : Regnum) return Integer with Volatile_Function; end Example_07;package body Example_07 is function Reg (R : Regnum) return Integer is begin return Integer (Regs (R)); end Reg; end Example_07;
This code is correct. Regs
has Effective_Reads = False
hence
can be read in a function. Function Reg
is marked as volatile with
aspect Volatile_Function
. No volatile variable is declared locally.
Example #8
package Example_08 with Abstract_State => (State with External), Initializes => State is procedure Dummy; end Example_08;package body Example_08 with Refined_State => (State => (X, Y, Z)) is X : Integer with Volatile, Async_Readers; Y : Integer with Volatile, Async_Writers; Z : Integer := 0; procedure Dummy is begin null; end Dummy; end Example_08;
This code is not correct. X
has Async_Writers = False
, hence is
not considered as always initialized. As aspect Initializes
specifies that State
should be initialized after elaboration, this is
an error. Note that is allowed to bundle volatile and non-volatile
variables in an external abstract state.
Example #9
package Example_09 is type Pair is record U, V : Natural; end record with Predicate => U /= V; X : Pair with Atomic, Async_Readers, Async_Writers; function Max return Integer with Volatile_Function, Post => Max'Result /= 0; end Example_09;package body Example_09 is function Max return Integer is Val1 : constant Natural := X.U; Val2 : constant Natural := X.V; begin return Natural'Max (Val1, Val2); end Max; end Example_09;
This code is not correct. X
has Async_Writers
set, hence it may
have been written between the successive reads of X.U
and X.V
.
Example #10
package Example_10 is type Pair is record U, V : Natural; end record with Predicate => U /= V; X : Pair with Atomic, Async_Readers, Async_Writers; function Max return Integer with Volatile_Function, Post => Max'Result /= 0; end Example_10;package body Example_10 is function Max return Integer is P : constant Pair := X; Val1 : constant Natural := P.U; Val2 : constant Natural := P.V; begin return Natural'Max (Val1, Val2); end Max; end Example_10;
This code is correct. Values of P.U
and P.V
are provably
different, and the postcondition is proved.