bedrock.lang.cpp.semantics.promotion
(*
* Copyright (c) 2020-2024 BlueRock Security, Inc.
* This software is distributed under the terms of the BedRock Open-Source License.
* See the LICENSE-BedRock file in the repository root for details.
*)
* Copyright (c) 2020-2024 BlueRock Security, Inc.
* This software is distributed under the terms of the BedRock Open-Source License.
* See the LICENSE-BedRock file in the repository root for details.
*)
Type-level semantics of type promotions
Require Import bedrock.prelude.base.
Require Import bedrock.prelude.numbers.
Require Export bedrock.lang.cpp.arith.operator.
Require Import bedrock.lang.cpp.syntax.
Require Import bedrock.lang.cpp.semantics.values.
#[local] Open Scope Z_scope.
Definition underlying_type (tu : translation_unit) (nm : globname) : option type :=
match tu.(types) !! nm with
| Some (Genum ty _) => Some $ drop_qualifiers ty
| _ => None
end.
match tu.(types) !! nm with
| Some (Genum ty _) => Some $ drop_qualifiers ty
| _ => None
end.
representation_type tu ty is the type that underlies ty if ty is an enum,
otherwise, it is the orginal type.
Note that if the enum is not defined in the translation unit, then the result
it the original enum.
Definition representation_type (tu : translation_unit) (ty : type) : type :=
match drop_qualifiers ty with
| Tenum nm as ty => default ty $ underlying_type tu nm
| ty => ty
end.
Succeed Example underlying_int : forall tu, representation_type tu Tint = Tint :=
ltac:(compute; auto).
Succeed Example underlying_int : forall tu, representation_type tu (Tconst Tint) = Tint :=
ltac:(compute; auto).
match drop_qualifiers ty with
| Tenum nm as ty => default ty $ underlying_type tu nm
| ty => ty
end.
Succeed Example underlying_int : forall tu, representation_type tu Tint = Tint :=
ltac:(compute; auto).
Succeed Example underlying_int : forall tu, representation_type tu (Tconst Tint) = Tint :=
ltac:(compute; auto).
underlying_unqual_type tu ty is the unqualified type that underlies ty.
This is the strip qualifiers function if ty is not an `enum`.
Definition underlying_unqual_type (tu : translation_unit) (ty : type) : type :=
let '(cv, ty') := decompose_type ty in
match ty' with
| Tenum nm as ty =>
match tu.(types) !! nm with
| Some (Genum ty _) => ty
| _ => ty'
end
| _ => ty'
end.
Succeed Example underlying_int : forall tu, underlying_unqual_type tu (Tconst Tint) = Tint :=
ltac:(compute; auto).
Section representable.
Context {σ : genv}.
(* fully_representable a b is true if all (well-typed) values of a values of b *)
Definition fully_representable (a b : type) : bool :=
let to_equiv ty :=
match ty with
| Tnum a b => Some $ integral_type.mk a b
| Tchar_ ct => Some $ equivalent_int_type _ ct
| _ => None
end
in
let min ty := (fun '(integral_type.mk sz sgn) => int_rank.min_val sz sgn) <$> to_equiv ty in
let max ty := (fun '(integral_type.mk sz sgn) => int_rank.max_val sz sgn) <$> to_equiv ty in
match min a , min b , max a , max b with
| Some an , Some bn , Some ax , Some bx =>
bool_decide (bn <= an /\ ax <= bx)%Z
| _ , _ , _ , _ => false
end.
(* first_representable ty ls gets the first type in ls that is able to
fully represent ty
*)
Fixpoint first_representable (ty : type) (ls : list type) : option type :=
match ls with
| nil => None
| l :: ls => if fully_representable ty l then Some l else first_representable ty ls
end.
End representable.
let '(cv, ty') := decompose_type ty in
match ty' with
| Tenum nm as ty =>
match tu.(types) !! nm with
| Some (Genum ty _) => ty
| _ => ty'
end
| _ => ty'
end.
Succeed Example underlying_int : forall tu, underlying_unqual_type tu (Tconst Tint) = Tint :=
ltac:(compute; auto).
Section representable.
Context {σ : genv}.
(* fully_representable a b is true if all (well-typed) values of a values of b *)
Definition fully_representable (a b : type) : bool :=
let to_equiv ty :=
match ty with
| Tnum a b => Some $ integral_type.mk a b
| Tchar_ ct => Some $ equivalent_int_type _ ct
| _ => None
end
in
let min ty := (fun '(integral_type.mk sz sgn) => int_rank.min_val sz sgn) <$> to_equiv ty in
let max ty := (fun '(integral_type.mk sz sgn) => int_rank.max_val sz sgn) <$> to_equiv ty in
match min a , min b , max a , max b with
| Some an , Some bn , Some ax , Some bx =>
bool_decide (bn <= an /\ ax <= bx)%Z
| _ , _ , _ , _ => false
end.
(* first_representable ty ls gets the first type in ls that is able to
fully represent ty
*)
Fixpoint first_representable (ty : type) (ls : list type) : option type :=
match ls with
| nil => None
| l :: ls => if fully_representable ty l then Some l else first_representable ty ls
end.
End representable.
Integral Promotion
This is used to promote a type to a type that supports arithmetic operators. For example, it converts types smaller than `int` to `int` (of some signedness). Because it converts `enum` types to integral types, it takes a translation_unit.
Definition promote_integral {σ : genv} (tu : translation_unit) (ty : type) : option type :=
match representation_type tu ty with
(* signed char or short can be converted to int *)
| Tschar
| Tshort => Some Tint
(* unsigned char or unsigned short can be converted to int if it can hold its entire
value range, and unsigned int otherwise *)
| Tuchar as ty
| Tushort as ty => Some $ if fully_representable ty Tint then Tint else Tuint
| Tnum _ _ as ty => Some ty
| Tchar =>
Some $ let rty := if σ.(char_signed) is Signed then Tschar else Tuchar in
if fully_representable rty Tint then Tint else Tuint
| Twchar
| Tchar_ char_type.C8
| Tchar_ char_type.C16
| Tchar_ char_type.C32 =>
(* wchar_t, char8_t (since C++20), char16_t, and char32_t
(since C++11) can be converted to the first type from the
following list able to hold their entire value range: int,
unsigned int, long, unsigned long, long long,
unsigned long long (since C++11) *)
first_representable ty [Tint;Tuint;Tlong;Tulong;Tlonglong;Tulonglong]
| Tenum nm => None
(* unreachable because of underlying_type and because the
underlying type of an `enum` must be a fundamental type. *)
(* an unscoped enumeration type whose underlying type
is not fixed can be converted to the first type from the following
list able to hold their entire value range: int, unsigned int, long,
unsigned long, long long, or unsigned long long, extended integer
types with higher conversion rank (in rank order, signed given
preference over unsigned) (since C++11). If the value range is
greater, no integral promotions apply;
an unscoped enumeration type whose underlying type is
fixed can be converted to its underlying type, and, if the
underlying type is also subject to integral promotion, to the
promoted underlying type. Conversion to the unpromoted underlying
type is better for the purposes of overload resolution)
NOTE: this only applies to `unscoped` enumerations; however, the
program will never contain these sorts of implicit conversions for
scoped enumerations, so we do not need to check this fact.
*)
| Tbool => Some Tint
| Tptr _
| Tref _
| Trv_ref _
| Tvoid
| Tarray _ _
| Tincomplete_array _
| Tvariable_array _ _
| Tnamed _
| Tfunction _
| Tmember_pointer _ _
| Tfloat_ _
| Tnullptr
| Tarch _ _ => None
| Tqualified _ _ => None (* unreachable *)
| Tunsupported _ => None
| Tparam _
| Tresult_param _
| Tresult_global _
| Tresult_parenlist _ _
| Tresult_member_call _ _ _
| Tresult_member _ _
| Tresult_unop _ _
| Tresult_binop _ _ _
| Tdecltype _ (* ?? *)
| Texprtype _ (* ?? *)
| Tresult_call _ _ => None
end.
Goal forall {σ : genv} tu, promote_integral tu Tchar = Some Tint.
Proof.
intros. rewrite /promote_integral/=.
destruct (char_signed σ); done.
Succeed Qed. Abort.
Goal forall {σ : genv} tu, promote_integral tu Twchar = Some (integral_type.to_type $ equivalent_int_type _ char_type.Cwchar).
Proof.
intros. rewrite /promote_integral/equivalent_int_type/=.
rewrite /fully_representable/=.
destruct (wchar_signed σ); done.
Succeed Qed. Abort.
(* Test cases *)
Succeed Example promote_short : forall {σ : genv} tu,
promote_integral tu Tshort = Some Tint :=
ltac:(compute; reflexivity).
Succeed Example promote_uchar : forall {σ : genv} tu,
promote_integral tu Tuchar = Some Tint :=
ltac:(compute; reflexivity).
Succeed Example promote_schar : forall {σ : genv} tu,
promote_integral tu Tschar = Some Tint :=
ltac:(compute; reflexivity).
match representation_type tu ty with
(* signed char or short can be converted to int *)
| Tschar
| Tshort => Some Tint
(* unsigned char or unsigned short can be converted to int if it can hold its entire
value range, and unsigned int otherwise *)
| Tuchar as ty
| Tushort as ty => Some $ if fully_representable ty Tint then Tint else Tuint
| Tnum _ _ as ty => Some ty
| Tchar =>
Some $ let rty := if σ.(char_signed) is Signed then Tschar else Tuchar in
if fully_representable rty Tint then Tint else Tuint
| Twchar
| Tchar_ char_type.C8
| Tchar_ char_type.C16
| Tchar_ char_type.C32 =>
(* wchar_t, char8_t (since C++20), char16_t, and char32_t
(since C++11) can be converted to the first type from the
following list able to hold their entire value range: int,
unsigned int, long, unsigned long, long long,
unsigned long long (since C++11) *)
first_representable ty [Tint;Tuint;Tlong;Tulong;Tlonglong;Tulonglong]
| Tenum nm => None
(* unreachable because of underlying_type and because the
underlying type of an `enum` must be a fundamental type. *)
(* an unscoped enumeration type whose underlying type
is not fixed can be converted to the first type from the following
list able to hold their entire value range: int, unsigned int, long,
unsigned long, long long, or unsigned long long, extended integer
types with higher conversion rank (in rank order, signed given
preference over unsigned) (since C++11). If the value range is
greater, no integral promotions apply;
an unscoped enumeration type whose underlying type is
fixed can be converted to its underlying type, and, if the
underlying type is also subject to integral promotion, to the
promoted underlying type. Conversion to the unpromoted underlying
type is better for the purposes of overload resolution)
NOTE: this only applies to `unscoped` enumerations; however, the
program will never contain these sorts of implicit conversions for
scoped enumerations, so we do not need to check this fact.
*)
| Tbool => Some Tint
| Tptr _
| Tref _
| Trv_ref _
| Tvoid
| Tarray _ _
| Tincomplete_array _
| Tvariable_array _ _
| Tnamed _
| Tfunction _
| Tmember_pointer _ _
| Tfloat_ _
| Tnullptr
| Tarch _ _ => None
| Tqualified _ _ => None (* unreachable *)
| Tunsupported _ => None
| Tparam _
| Tresult_param _
| Tresult_global _
| Tresult_parenlist _ _
| Tresult_member_call _ _ _
| Tresult_member _ _
| Tresult_unop _ _
| Tresult_binop _ _ _
| Tdecltype _ (* ?? *)
| Texprtype _ (* ?? *)
| Tresult_call _ _ => None
end.
Goal forall {σ : genv} tu, promote_integral tu Tchar = Some Tint.
Proof.
intros. rewrite /promote_integral/=.
destruct (char_signed σ); done.
Succeed Qed. Abort.
Goal forall {σ : genv} tu, promote_integral tu Twchar = Some (integral_type.to_type $ equivalent_int_type _ char_type.Cwchar).
Proof.
intros. rewrite /promote_integral/equivalent_int_type/=.
rewrite /fully_representable/=.
destruct (wchar_signed σ); done.
Succeed Qed. Abort.
(* Test cases *)
Succeed Example promote_short : forall {σ : genv} tu,
promote_integral tu Tshort = Some Tint :=
ltac:(compute; reflexivity).
Succeed Example promote_uchar : forall {σ : genv} tu,
promote_integral tu Tuchar = Some Tint :=
ltac:(compute; reflexivity).
Succeed Example promote_schar : forall {σ : genv} tu,
promote_integral tu Tschar = Some Tint :=
ltac:(compute; reflexivity).
Arithmetic Promotion
This computes a "join" on the types of the two arguments to an arithmetic operator.
Definition promote_arith (ty1 ty2 : type) : option type :=
match ty1 , ty2 with
| Tnum sz1 sgn1 , Tnum sz2 sgn2 =>
if bool_decide (sgn1 = sgn2) then
Some $ Tnum (int_rank.t_max sz1 sz2) sgn1
else
let (ssz, usz) := match sgn1 with
| Signed => (sz1, sz2)
| Unsigned => (sz2, sz1)
end
in
if bool_decide (int_rank.t_le ssz usz) then
Some $ Tnum usz Unsigned
else if bool_decide (int_rank.t_le usz ssz) then
Some $ Tnum ssz Signed
else
Some $ Tnum ssz Unsigned
| _ , _ => None
end.
(* Test cases *)
Succeed Example promote_int_long : promote_arith Tint Tlong = Some Tlong := eq_refl.
Succeed Example promote_int_ulong : promote_arith Tint Tulong = Some Tulong := eq_refl.
Succeed Example promote_uint_long : promote_arith Tuint Tlong = Some Tlong := eq_refl.
match ty1 , ty2 with
| Tnum sz1 sgn1 , Tnum sz2 sgn2 =>
if bool_decide (sgn1 = sgn2) then
Some $ Tnum (int_rank.t_max sz1 sz2) sgn1
else
let (ssz, usz) := match sgn1 with
| Signed => (sz1, sz2)
| Unsigned => (sz2, sz1)
end
in
if bool_decide (int_rank.t_le ssz usz) then
Some $ Tnum usz Unsigned
else if bool_decide (int_rank.t_le usz ssz) then
Some $ Tnum ssz Signed
else
Some $ Tnum ssz Unsigned
| _ , _ => None
end.
(* Test cases *)
Succeed Example promote_int_long : promote_arith Tint Tlong = Some Tlong := eq_refl.
Succeed Example promote_int_ulong : promote_arith Tint Tulong = Some Tulong := eq_refl.
Succeed Example promote_uint_long : promote_arith Tuint Tlong = Some Tlong := eq_refl.