123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443(**************************************************************************)(* This file is part of the Codex semantics library. *)(* *)(* Copyright (C) 2013-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file LICENSE). *)(* *)(**************************************************************************)(** This defines the syntax for the operators usable in the
internal languages of Codex, expressed as signatures as in the
{{: http://okmij.org/ftp/tagless-final/}Tagless final} approach.
The signatures are grouped by type of values manipulated
(boolean, integer, bitvector, binary, memory, enum). We define
two set of functions: the forward are the normal operations, and
the backward exclude the functions of arity 0 (for which a
backward operation is meaningless). *)openOperator_idsopenUnitstypeaccess_type=|Read(** Loading from memory. *)|Write(** Storing into memory. *)typearith_type=|Plus(** Addition operation *)|Minus(** Substraction operation *)(** Arity of function symbols. ['r] represents the result type and ['a], ['b], ['c] the arguments. *)moduletypeARITY=sigtype('r)ar0type('a,'r)ar1type('a,'b,'r)ar2type('a,'b,'c,'r)ar3end(** Standard arities for forward transfer functions: given the arguments, return the results.
These match the arities of the concrete functions they represent (but with concrete types
substituted for their abstract counterparts). *)moduleForward_Arity=structtype'rar0='rtype('a,'r)ar1='a->'rtype('a,'b,'r)ar2='a->'b->'rtype('a,'b,'c,'r)ar3='a->'b->'c->'rend(** Standard arities for backward transfer functions (used to refined the arguments from information
on the result values). These take the result value ['r] as argument and return a new-improved
value for each argument. They return [None] when no improvement is possible for that argument.
We generally don't include backward function for symbols of arity 0. *)moduleBackward_Arity=structtype'rar0='r->unittype('a,'r)ar1='a->'r->('aoption)type('a,'b,'r)ar2='a->'b->'r->('aoption*'boption)type('a,'b,'c,'r)ar3='a->'b->'c->'r->('aoption*'boption*'coption)end(** Note: in the following, we distinguish between backward and forward
because there is no need to implement backward transfer functions
for symbols with arity 0. *)(****************************************************************)(** {1 Boolean transfer functions}
Transfer functions for boolean values:
{{!Boolean_Backward.not}[not]},
and ({{!Boolean_Backward.(&&)}[&&]}),
or ({{!Boolean_Backward.(||)}[||]}),
as well as contants {{!Boolean_Forward.true_}[true_]} and
{{!Boolean_Forward.false_}[false_]}. *)moduletypeBOOLEAN_BACKWARD=sigtypebooleanmoduleArity:ARITYvalnot:(boolean,boolean)Arity.ar1val(&&):(boolean,boolean,boolean)Arity.ar2val(||):(boolean,boolean,boolean)Arity.ar2endmoduletypeBOOLEAN_FORWARD=sigincludeBOOLEAN_BACKWARDvaltrue_:booleanArity.ar0valfalse_:booleanArity.ar0end(****************************************************************)(** {1 Integer transfer functions}
Transfer functions for unbounded integers:
- addition ({{!Integer_Backward.iadd}[iadd]}); subtraction ({{!Integer_Backward.isub}[isub]});
- multiplication ({{!Integer_Backward.imul}[imul]}, in general, {{!Integer_Backward.itimes}[itimes]}
when multiplying by a constant);
- division ({{!Integer_Backward.idiv}[idiv]}), remainder ({{!Integer_Backward.iadd}[imod]});
- comparisons ({{!Integer_Backward.ieq}[ieq]} for [==], {{!Integer_Backward.ile}[ile]} for [<=]);
- shifts (left {{!Integer_Backward.ishl}[ishl]} and right {{!Integer_Backward.ishr}[ishr]})
- bitwise operations ({{!Integer_Backward.ior}[ior]}, {{!Integer_Backward.iand}[iand]}, {{!Integer_Backward.ixor }[ixor]}).
For the bitwise operation, we
assume an infinite two-complement representation: i.e. [-1] is
represented by an infinite sequence of [1], and [0] by an infinite
sequence of [0]. *)moduletypeINTEGER_BACKWARD=sigtypeintegertypebooleanmoduleArity:ARITY(* Minimum versions. *)valitimes:Z.t->(integer,integer)Arity.ar1(** Multiply an integer by a constant *)valiadd:(integer,integer,integer)Arity.ar2valimul:(integer,integer,integer)Arity.ar2validiv:(integer,integer,integer)Arity.ar2(** This is truncated (C99-like) integer division *)(* Maybe: rename to itdiv/itmod? *)valimod:(integer,integer,integer)Arity.ar2valishl:(integer,integer,integer)Arity.ar2valishr:(integer,integer,integer)Arity.ar2valiand:(integer,integer,integer)Arity.ar2(** Bitwise and, where negative integers are seen as prefixed by infinite ones *)valior:(integer,integer,integer)Arity.ar2(** Bitwise or, where negative integers are seen as prefixed by infinite ones *)valixor:(integer,integer,integer)Arity.ar2valisub:(integer,integer,integer)Arity.ar2valieq:(integer,integer,boolean)Arity.ar2valile:(integer,integer,boolean)Arity.ar2endmoduletypeINTEGER_FORWARD_MIN=sigincludeINTEGER_BACKWARDvaliconst:Z.t->integerArity.ar0(** Integer constant *)endmoduletypeINTEGER_FORWARD=sigincludeINTEGER_FORWARD_MIN(** These can be defined from the others, but it may be more
efficient to implement them directly (no need to build temporary
values...). They are also often convenient to use directly. *)valzero:integerArity.ar0valone:integerArity.ar0end(****************************************************************)(** {1:bitvector Bitvector transfer functions}
Purely numerical operations on fixed-size bitvectors. Includes
bitwise operations and arithmetic, but not pointer arithmetic.
Note: the [size] argument is generally the size of both arguments
and the result. *)moduletypeBITVECTOR_BACKWARD=sigtypebitvectortypebooleanmoduleArity:ARITY(* nsw means no signed overflow, nuw means no unwrapped overflow. No
(signed) overflow means that when taking the signed integer value
of the variable, then performing the operation remains in the
signed range (and respectively for unsigned overflow). Transfer
functions can take advantage of this fact to improve their
precision. *)(** Bitvector Integer ADDition.
Operaters on bitvectors of size [size]. The flags represent behavior on overflow:
- [nuw]: no unsigned wrap: the operation is partial, i.e. fails
if the sum of the two operands (interpreted as unsigned numbers)
is not in the {m 0} to {m 2^{size}-1} interval.
- [nsw]: no signed wrap: the operation is partial, i.e. fails if
the sum of the two operands (interpreted as signed numbers) is
not in the {m -2^{size-1}} to{m 2^{size-1} - 1} interval.
- [nusw]: no unsigned plus signed wrap: the addition of the
first operand (interpreted as an unsigned number) and the second
one (interpreted as a signed number) fails if its value is not
in the {m 0} to {m 2^{size}-1} interval. This is useful when
doing pointer arithmetic (address (unsigned) + offset (signed))
Note that the simultaneous combination of different flags may be
unimplemented (as it never happens in practice). *)valbiadd:size:In_bits.t->flags:Flags.Biadd.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector Integer SUBtraction.
See {!biadd} for the flag meanings *)valbisub:size:In_bits.t->flags:Flags.Bisub.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector Integer MULtiplication.
See {!biadd} for the flag meanings *)valbimul:size:In_bits.t->flags:Flags.Bimul.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector SHift Left,
If second argument is larger than size, bshl returns 0. *)valbshl:size:In_bits.t->flags:Flags.Bshl.t->(bitvector,bitvector,bitvector)Arity.ar2(** Arithmetic shift right: fill with the most significant bit. *)valbashr:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Logical shift right: fill with 0. *)valblshr:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector equality *)valbeq:size:In_bits.t->(bitvector,bitvector,boolean)Arity.ar2(** Signed comparison between two bitvectors (using less-or-equal [<=]).
Bitvectors are interpreted as integers in {m [-2^{size-1}..-2^{size-1}-1]} using two's complement. *)valbisle:size:In_bits.t->(bitvector,bitvector,boolean)Arity.ar2(** Unsigned comparison between two bitvectors (using less-or-equal [<=]).
Bitvectors are interpreted as positive integers in {m [0..-2^{size}-1]}. *)valbiule:size:In_bits.t->(bitvector,bitvector,boolean)Arity.ar2(** Bitvector concatenation: the new bitvector's size is the sum of the sizes of the arguments.
The first argument becomes the most significant bits. *)valbconcat:size1:In_bits.t->size2:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Extract a [size] of a bitvector of total size [oldsize], starting at [index].
Should satisfy [index + size <= oldsize]. *)valbextract:size:In_bits.t->index:In_bits.t->oldsize:In_bits.t->(bitvector,bitvector)Arity.ar1(** Bitvector bitwise and *)valband:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector bitwise or *)valbor:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector bitwise xor *)valbxor:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Unsingned-extend (pad left with zero) the argument bitvector until it reaches the specified [size]. *)valbuext:size:In_bits.t->oldsize:In_bits.t->(bitvector,bitvector)Arity.ar1(** Sign-extend (pad left with topbit) the argument bitvector until it reaches the specified [size]. *)valbsext:size:In_bits.t->oldsize:In_bits.t->(bitvector,bitvector)Arity.ar1(** Bitvector signed division (truncate towards 0) *)valbisdiv:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector signed modulo: should satisfy
[a = b*(bisdiv a b) + bismod a b], just like C-modulo.
This means it can be negative for negative divisions. *)valbismod:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector unsigned division (corresponds to euclidian division) *)valbiudiv:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Bitvector unsigned modulo (corresponds to euclidian remainder) *)valbiumod:size:In_bits.t->(bitvector,bitvector,bitvector)Arity.ar2(** Turn a boolean into a bitvector of the given [size]:
[false] is [0] and [true] is [1]. *)valbofbool:size:In_bits.t->(boolean,bitvector)Arity.ar1endmoduletypeBITVECTOR_FORWARD=sigincludeBITVECTOR_BACKWARDvalbiconst:size:In_bits.t->Z.t->bitvectorArity.ar0(** Bitvector constant with the given size and value. *)endmoduletypeBITVECTOR_FORWARD_WITH_BIMUL_ADD=sigincludeBITVECTOR_FORWARD(** Combined operation for multiplication and addition [x -> prod*x + offset].
This operation does not overflow if only the intermediate term [prod*x] overflows. *)valbimul_add:size:In_bits.t->prod:Z.t->offset:Z.t->(bitvector,bitvector)Arity.ar1end(****************************************************************)(** {1 Binary transfer functions}
Binary is the name of values handled by C or machine-level
programs, i.e. either numeric {{!bitvector}bitvectors} or
pointers. *)moduletypeBINARY_BACKWARD=sigtypebinaryincludeBITVECTOR_BACKWARDwithtypebitvector:=binary(* TODO: We should drop the size argument here. Those functions
apply only to addresses, and we know the size of addresses. *)(* TODO: Should be a predicate on memory, and take both a binary and memory. *)(** Returns true if the access to the pointer in memory is valid. *)valvalid:size:In_bits.t->access_type->(binary,boolean)Arity.ar1(** [valid_ptr_arith(ptr,off)] where [ptr] is a pointer (unsigned)
and [off] a signed integer offset returns [true] if [ptr + off]
(or [ptr - off], depending on the [arith_type]) is in-bound. *)(* MAYBE: Could be replaced by a new \valid access types, and overflow assertions on addition. *)valvalid_ptr_arith:size:In_bits.t->arith_type->(binary,binary,boolean)Arity.ar2(* [max], if not None, limits further pointer arithmetics: one cannot go beyond max. *)(* MAYBE: separate this between bshift and a new operator [bnarrow]. *)(** [bshift ptr ~offset] returns [ptr + offset].
If [max] is not None, limits further pointer arithmetics: one cannot go beyond max.
Note: offset and max are in bytes, not in bits. *)valbshift:size:In_bits.t->offset:int->max:intoption->(binary,binary)Arity.ar1(** [bindex] takes an integer [k] and two binary values [ptr] and [off].
It returns [ptr + k*off]. *)valbindex:size:In_bits.t->int->(binary,binary,binary)Arity.ar2(* b + k * exp. *)(** If s is a set, and c is a "choice" (i.e. valuation of conditions),
choose an element of set according to choice. *)(* MAYBE: A more proper solution could be to have types for
collections, i.e. arrays and sets, but this would require
duplicating the work on the single elements to collections. *)valbchoose:size:In_bits.t->Choice.t->(binary,binary)Arity.ar1endmoduletypeBINARY_FORWARD=sigincludeBINARY_BACKWARDvalbiconst:size:In_bits.t->Z.t->binaryArity.ar0(** Binary constant with given size and value*)valbuninit:size:In_bits.t->binaryArity.ar0(** Uninitialized binary value*)end(****************************************************************)(* Offset transfer functions. *)moduletypeOFFSET_BACKWARD=sigtypebooleantypeoffsetmoduleArity:ARITY(* <= *)valle:size:int->(offset,offset,boolean)Arity.ar2(* = *)valeq:size:int->(offset,offset,boolean)Arity.ar2(* offset + k *)valshift:size:int->offset:int->(offset,offset)Arity.ar1endmoduletypeOFFSET_FORWARD=sigincludeOFFSET_BACKWARDend(****************************************************************)(* Block transfer functions. *)moduletypeBLOCK_BACKWARD=sigtypebooleantypeoffsettypevaluetypeblockmoduleArity:ARITY(** Size of a block in bytes *)valsizeof:(block,offset)Arity.ar1(** Concatenates two blocks *)valconcat:(block,block,block)Arity.ar2(** Loads (extracts) a value of a fixed size at a given index from a block *)valload:size:In_bits.t->(block,offset,value)Arity.ar2(** Stores (writes) a fixed size value of a given index in a block *)valstore:size:In_bits.t->(block,offset,value,block)Arity.ar3(** Converts a fixed size value to a block *)valbinary_to_block:size:In_bits.t->(value,block)Arity.ar1endmoduletypeBLOCK_FORWARD=sigincludeBLOCK_BACKWARDend(****************************************************************)(** {1 Enum transfer functions} *)(** Transfer function for enum values. Enums are types with a fixed (small) number of possible cases. *)moduletypeENUM_BACKWARD=sigtypebooleantypeenummoduleArity:ARITY(** Boolean operation to check if the enumeration value is in case [case] *)valcaseof:case:int->(enum,boolean)Arity.ar1endmoduletypeENUM_FORWARD=sigincludeENUM_BACKWARDvalenum_const:case:int->enumArity.ar0(** Constant enum value *)end(****************************************************************)(** {1 Memory transfer functions} *)moduletypeMEMORY_BACKWARD=sigtypeblocktypememorytypeaddresstypevaluetypebooleanmoduleArity:ARITY(* TODO: All sizes should probably be in bytes. *)valload:size:In_bits.t->(memory,address,value*memory)Arity.ar2valstore:size:In_bits.t->(memory,address,value,memory)Arity.ar3valload_block:(memory,address,block*memory)Arity.ar2valstore_block:(memory,address,block,memory)Arity.ar3(* Fixed-size memcpy(store,from_,to_). *)valmemcpy:size:In_bits.t->(memory,address,address,memory)Arity.ar3valmalloc:id:Malloc_id.t->malloc_size:In_bytes.t->(memory,address*memory)Arity.ar1valfree:(memory,address,memory)Arity.ar2valunknown:level:int->memoryArity.ar0endmoduletypeMEMORY_FORWARD=sigincludeMEMORY_BACKWARDend