Skip to content

Mutable structures in Low*

Tahina Ramananandro (professional account) edited this page Mar 4, 2017 · 2 revisions

Besides homogeneous buffers, the F* standard library provides mutable structures based on heterogeneous dependent maps. This wiki page presents the formalization and implementation as of February 27th 2017.

Dependent maps

The FStar.DependentMap module provides a library for pure heterogeneous maps.

A type key for element names shall be provided. keys shall have decidable equality. Then, the type of each element of the map shall be given by a function value: (f: field_name) -> Tot Type.

Based on those, to create a heterogeneous map, the user shall provide a function initial_values: (k: key) -> value k. Then, FStar.DependentMap.create #key #value initial_values will create a map of type FStar.DependentMap.t key value.

Then, given a map m: FStar.DependentMap.t keys value_types and a key k: key, FStar.DependentMap.sel m k will retrieve the element associated to the key k.

Then, given a map m: FStar.DependentMap.t keys value_types, a key k: key and a value v: value k, FStar.DependentMap.upd m k v will retrieve the new map with the key k now associated to v and others unchanged.

Structures

Based on this library for heterogeneous maps, we can define mutable structures. The FStar.Struct library provides a way to define mutable structures, and pointers to their fields.

Allocation

If init is a dependent map, of some type DependentMap.t key value containing the initial values of the fields of a structure, then FStar.Struct.screate #(FStar.DependentMap.t key value) init allocates a new structure into the stack, with those initial values. The structure is of type FStar.Struct.struct_ptr (FStar.DependentMap.t key value). This is an effectful operation (similar to FStar.Buffer.create for buffers.)

Similarly, for an eternal region r, FStar.Struct.ecreate #(FStar.DependentMap.t key value) r init will allocate the structure into the region r instead of the stack. This is also an effectful operation (similar to FStar.Buffer.rcreate for buffers.)

In both cases, the result is live (FStar.Struct.live) in the post memory state.

In fact, it is also possible to create a reference-like pointer to a value of type t where t is any type (not necessarily a FStar.DependentMap.t). In that case, the notion of field is not defined.

Field access

If s is a mutable structure, of type FStar.Struct.struct_ptr (FStar.DependentMap.t key value), and f: key is a field, then FStar.Struct.field s f returns a pointer to the field f in s, of type (FStar.Struct.struct_ptr (value f)). This is an effectful operation.

This operation has a ghost counterpart, for use in specifications: FStar.Struct.gfield.

The type value f can itself be FStar.DependentMap.t, thus providing a way to nest a structure into s.

Both operations are similar to FStar.Buffer.sub for buffers.

Read value from memory

If s is a structure, or a pointer to a field (or other kind of pointer), of type FStar.Struct.struct_ptr t, then FStar.Struct.read s reads the value stored in s, thus returning a value of type t. This is an effectful operation (similar to FStar.Buffer.index for buffers.)

This operation has a ghost counterpart, for use in specifications: FStar.Struct.as_value (similar to FStar.Buffer.as_seq for buffers.)

Write value to memory

If s is a structure, or a pointer to a field, of type FStar.Struct.struct_ptr t, and v is a value of type t, then FStar.Struct.write s v write the value v into s. This is an effectful operation (similar to FStar.Buffer.upd for buffers.)

Structs from buffers

It is possible to nest a structure into a buffer without any indirection. To this end, it suffices to create a buffer of dependent maps, thus of type FStar.Buffer.buffer (FStar.DependentMap.t key value).

Then, if b is such a buffer, then it is possible to obtain a pointer to its cell i, by the operation FStar.Struct.from_buffer_index b i, thus yielding a (pointer to a) mutable structure, of type FStar.Struct.struct_ptr (FStar.DependentMap.t key value), thus making it possible to take a pointer to one of its fields. This is an effectful operation (requiring b to be live.)

This operation has a ghost counterpart: FStar.Struct.gfrom_buffer_index

However useful, this operation is possible if the buffer data is of any type t (not necessarily a FStar.DependentMap.t), thus yielding a reference-like pointer to a value of type t; of course, there, the notion of field is no longer defined.

Future work

Arbitrary interleaving between structures and arrays

Structures can be nested. Currently, structures are built on top of buffers, but buffers cannot be nested into structures without any indirection.

It would be nice to fully allow nesting them so that arrays can be flatly stored as structure fields with no indirection.

Then, we would reimplement buffers as a special case of structs.

Dependent structures

What if we want structures with fields whose types depend on each other's values?

A possible implementation could be based on the following telescope type:

noeq type telescope:
 (field: eqtype) ->
 (repr: Type) ->
 Type =
| TNil:
  (field: eqtype { ~ field } ) ->
  telescope field unit
| TCons:
  (field: eqtype) ->
  (f: field) ->
  (repr:Type) ->
  (tele: telescope (f': field {f' <> f}) repr) ->
  (v: (repr -> Tot Type)) ->
  telescope field ((x: repr) & v x)

(UPDATE: does not work very well with unification in practice. Maybe we should have a separate list of fields, consume it in the telescope, and have the two totality and non-duplication conditions separately on the list.)

Clone this wiki locally