[Ada] Implement AI12-0280 Making 'Old more flexible
authorSteve Baird <baird@adacore.com>
Thu, 21 May 2020 21:42:53 +0000 (14:42 -0700)
committerPierre-Marie de Rodat <derodat@adacore.com>
Tue, 20 Oct 2020 07:21:48 +0000 (03:21 -0400)
gcc/ada/

* sem_util.ads: Declare a new package, Old_Attr_Util, which in
turn declares two more packages, Conditional_Evaluation and
Indirect_Temps. Conditional_Evaluation provides a predicate for
deciding whether a given 'Old attribute reference is eligible
for conditional evaluation and, in the case where it is
eligible, a function that constructs the Boolean-valued
condition that is to be evaluated at run time in deciding
whether to evaluate the attribute prefix.  Indirect_Temps
provides support for declaring a temporary which is only
initialized conditionally; more specifically, an access type and
a variable of that type are declared (unconditionally) and then
the variable is (conditionally) initialized with an allocator.
The existence of the access type and the pointer variable is
hidden from clients, except that a predicate,
Is_Access_Type_For_Indirect_Temp, is provided for identifying
such access types. This is needed because we want such an access
type to be treated like a "normal" access type (specifically
with respect to finalization of allocated objects). Other parts
of the compiler treat access types differently if
Comes_From_Source is False, or if the secondary stack storage
pool is used; this predicate is used to disable this special
treatment.
* sem_attr.adb (Uneval_Old_Msg): Improve message text to reflect
Ada202x changes.
(Analyze_Attribute): A previously-illegal 'Old attribute
reference is accepted in Ada2020 if it is eligible for
conditional evaluation.
* sem_res.adb (Valid_Conversion): Do not treat a rewritten 'Old
attribute like other rewrite substitutions. This makes a
difference, for example, in the case where we are generating the
expansion of a membership test of the form "Saooaaat'Old in
Named_Access_Type"; in this case Valid_Conversion needs to
return True (otherwise the expansion will be False - see the
call site in exp_ch4.adb).
* exp_attr.adb (Expand_N_Attribute_Reference): When expanding a
'Old attribute reference, test for the case where the reference
is eligible for conditional evaluation. In that case, use the
new "indirect temporary" mechanism provided by Sem_Util.
* exp_prag.adb
(Expand_Attributes_In_Consequence.Expand_Attributes): If
Sem_Util.Indirect_Temp_Needed indicates that there could be
correctness problems associated with the old expansion scheme
for dealing with 'Old attributes in contract cases consequences,
then we use the new "indirect temporary" mechanism provided by
Sem_Util instead. We do not want to do this unconditionally.
* sem_util.adb: Provide a body for the new Old_Attr_Util
package. Further work is needed in several areas for
correctness:
- The function Is_Repeatedly_Evaluated does not deal with
container aggregates yet.
- The function Is_Known_On_Entry does not deal with interactions
with the Global aspect.
Each area where more work is needed is indicated with a "???"
comment in the code; a more detailed description can be found
there. Some optimization opportunties are similarly indicated
with a "???" comment.
* exp_ch3.adb (Freeze_Type): In deciding whether to generate
expansion for the list controller of an access type, take the
predicate Is_Access_Type_For_Indirect_Temp into account. If the
predicate yields True, then generate the expansion.
* exp_util.adb (Build_Allocate_Deallocate_Proc): We don't
normally finalize allocated objects that are allocated on the
secondary stack. Add an exception to this rule if the predicate
Is_Access_Type_For_Indirect_Temp yields True.  As a result of
this exception, we have to deal with the fact that gigi expects
a different parameter profile if we are using the secondary
stack pool; the Pool and Alignment parameters must be omitted in
this case.

gcc/ada/exp_attr.adb
gcc/ada/exp_ch3.adb
gcc/ada/exp_prag.adb
gcc/ada/exp_util.adb
gcc/ada/sem_attr.adb
gcc/ada/sem_res.adb
gcc/ada/sem_util.adb
gcc/ada/sem_util.ads

index 49888d1..0bc08ae 100644 (file)
@@ -4665,6 +4665,8 @@ package body Exp_Attr is
          Subp    : Node_Id;
          Temp    : Entity_Id;
 
+         use Old_Attr_Util.Conditional_Evaluation;
+         use Old_Attr_Util.Indirect_Temps;
       begin
          --  Generating C code we don't need to expand this attribute when
          --  we are analyzing the internally built nested postconditions
@@ -4748,10 +4750,60 @@ package body Exp_Attr is
             Ins_Nod := First (Declarations (Ins_Nod));
          end if;
 
+         if Eligible_For_Conditional_Evaluation (N) then
+            declare
+               Eval_Stmts : constant List_Id := New_List;
+
+               procedure Append_For_Indirect_Temp
+                 (N : Node_Id; Is_Eval_Stmt : Boolean);
+               --  Append either a declaration (which is to be elaborated
+               --  unconditionally) or an evaluation statement (which is
+               --  to be executed conditionally).
+
+               -------------------------------
+               --  Append_For_Indirect_Temp --
+               -------------------------------
+
+               procedure Append_For_Indirect_Temp
+                 (N : Node_Id; Is_Eval_Stmt : Boolean)
+               is
+               begin
+                  if Is_Eval_Stmt then
+                     Append_To (Eval_Stmts, N);
+                  else
+                     Insert_Before_And_Analyze (Ins_Nod, N);
+                  end if;
+               end Append_For_Indirect_Temp;
+
+               procedure Declare_Indirect_Temporary is new
+                 Declare_Indirect_Temp
+                   (Append_Item => Append_For_Indirect_Temp);
+            begin
+               Declare_Indirect_Temporary
+                 (Attr_Prefix => Pref, Indirect_Temp => Temp);
+
+               Insert_Before_And_Analyze (
+                 Ins_Nod,
+                 Make_If_Statement
+                   (Sloc            => Loc,
+                    Condition       => Conditional_Evaluation_Condition  (N),
+                    Then_Statements => Eval_Stmts));
+
+               Rewrite (N, Indirect_Temp_Value
+                             (Temp => Temp,
+                              Typ  => Etype (Pref),
+                              Loc  => Loc));
+
+               if Present (Subp) then
+                  Pop_Scope;
+               end if;
+               return;
+            end;
+
          --  Preserve the tag of the prefix by offering a specific view of the
          --  class-wide version of the prefix.
 
-         if Is_Tagged_Type (Typ) then
+         elsif Is_Tagged_Type (Typ) then
 
             --  Generate:
             --    CW_Temp : constant Typ'Class := Typ'Class (Pref);
index b5b86d8..8b8462a 100644 (file)
@@ -8178,8 +8178,9 @@ package body Exp_Ch3 is
             --  Taft-amendment types, which potentially have controlled
             --  components), expand the list controller object that will store
             --  the dynamically allocated objects. Don't do this transformation
-            --  for expander-generated access types, but do it for types that
-            --  are the full view of types derived from other private types.
+            --  for expander-generated access types, except do it for types
+            --  that are the full view of types derived from other private
+            --  types and for access types used to implement indirect temps.
             --  Also suppress the list controller in the case of a designated
             --  type with convention Java, since this is used when binding to
             --  Java API specs, where there's no equivalent of a finalization
@@ -8188,6 +8189,8 @@ package body Exp_Ch3 is
 
             if not Comes_From_Source (Def_Id)
               and then not Has_Private_Declaration (Def_Id)
+              and then not Old_Attr_Util.Indirect_Temps
+                             .Is_Access_Type_For_Indirect_Temp (Def_Id)
             then
                null;
 
index f4b15fa..050b05c 100644 (file)
@@ -1365,9 +1365,43 @@ package body Exp_Prag is
          -----------------------
 
          function Expand_Attributes (N : Node_Id) return Traverse_Result is
-            Decl : Node_Id;
-            Pref : Node_Id;
-            Temp : Entity_Id;
+            Decl     : Node_Id;
+            Pref     : Node_Id;
+            Temp     : Entity_Id;
+            Indirect : Boolean := False;
+
+            use Sem_Util.Old_Attr_Util.Indirect_Temps;
+
+            procedure Append_For_Indirect_Temp
+              (N : Node_Id; Is_Eval_Stmt : Boolean);
+
+            --  Append either a declaration (which is to be elaborated
+            --  unconditionally) or an evaluation statement (which is
+            --  to be executed conditionally).
+
+            -------------------------------
+            --  Append_For_Indirect_Temp --
+            -------------------------------
+
+            procedure Append_For_Indirect_Temp
+              (N : Node_Id; Is_Eval_Stmt : Boolean)
+            is
+            begin
+               if Is_Eval_Stmt then
+                  Append_To (Eval_Stmts, N);
+               else
+                  Prepend_To (Decls, N);
+                  --  This use of Prepend (as opposed to Append) is why
+                  --  we have the Append_Decls_In_Reverse_Order parameter.
+               end if;
+            end Append_For_Indirect_Temp;
+
+            procedure Declare_Indirect_Temporary is new
+              Declare_Indirect_Temp (
+                Append_Item                   => Append_For_Indirect_Temp,
+                Append_Decls_In_Reverse_Order => True);
+
+         --  Start of processing for Expand_Attributes
 
          begin
             --  Attribute 'Old
@@ -1376,37 +1410,49 @@ package body Exp_Prag is
               and then Attribute_Name (N) = Name_Old
             then
                Pref := Prefix (N);
-               Temp := Make_Temporary (Loc, 'T', Pref);
-               Set_Etype (Temp, Etype (Pref));
 
-               --  Generate a temporary to capture the value of the prefix:
-               --    Temp : <Pref type>;
+               Indirect := Indirect_Temp_Needed (Etype (Pref));
 
-               Decl :=
-                 Make_Object_Declaration (Loc,
-                   Defining_Identifier => Temp,
-                   Object_Definition   =>
-                     New_Occurrence_Of (Etype (Pref), Loc));
+               if Indirect then
+                  if No (Eval_Stmts) then
+                     Eval_Stmts := New_List;
+                  end if;
 
-               --  Place that temporary at the beginning of declarations, to
-               --  prevent anomalies in the GNATprove flow-analysis pass in
-               --  the precondition procedure that follows.
+                  Declare_Indirect_Temporary
+                    (Attr_Prefix   => Pref,
+                     Indirect_Temp => Temp);
 
-               Prepend_To (Decls, Decl);
+               --  Declare a temporary of the prefix type with no explicit
+               --  initial value. If the appropriate contract case is selected
+               --  at run time, then the temporary will be initialized via an
+               --  assignment statement.
 
-               --  If the type is unconstrained, the prefix provides its
-               --  value and constraint, so add it to declaration.
+               else
+                  Temp := Make_Temporary (Loc, 'T', Pref);
+                  Set_Etype (Temp, Etype (Pref));
 
-               if not Is_Constrained (Etype (Pref))
-                 and then Is_Entity_Name (Pref)
-               then
-                  Set_Expression (Decl, Pref);
-                  Analyze (Decl);
+                  --  Generate a temporary to capture the value of the prefix:
+                  --    Temp : <Pref type>;
 
-               --  Otherwise add an assignment statement to temporary using
-               --  prefix as RHS.
+                  Decl :=
+                    Make_Object_Declaration (Loc,
+                      Defining_Identifier => Temp,
+                      Object_Definition   =>
+                        New_Occurrence_Of (Etype (Pref), Loc));
 
-               else
+                  --  Place that temporary at the beginning of declarations, to
+                  --  prevent anomalies in the GNATprove flow-analysis pass in
+                  --  the precondition procedure that follows.
+
+                  Prepend_To (Decls, Decl);
+
+                  --  Initially Temp is uninitialized (which is required for
+                  --  correctness if default initialization might have side
+                  --  effects). Assign prefix value to temp on Eval_Statement
+                  --  list, so assignment will be executed conditionally.
+
+                  Set_Ekind (Temp, E_Variable);
+                  Set_Suppress_Initialization (Temp);
                   Analyze (Decl);
 
                   if No (Eval_Stmts) then
@@ -1417,7 +1463,6 @@ package body Exp_Prag is
                     Make_Assignment_Statement (Loc,
                       Name       => New_Occurrence_Of (Temp, Loc),
                       Expression => Pref));
-
                end if;
 
                --  Ensure that the prefix is valid
@@ -1429,7 +1474,13 @@ package body Exp_Prag is
                --  Replace the original attribute 'Old by a reference to the
                --  generated temporary.
 
-               Rewrite (N, New_Occurrence_Of (Temp, Loc));
+               if Indirect then
+                  Rewrite (N,
+                    Indirect_Temp_Value
+                      (Temp => Temp, Typ => Etype (Pref), Loc => Loc));
+               else
+                  Rewrite (N, New_Occurrence_Of (Temp, Loc));
+               end if;
 
             --  Attribute 'Result
 
index 2c08ea9..67c3a36 100644 (file)
@@ -734,12 +734,13 @@ package body Exp_Util is
 
       --  Local variables
 
-      Desig_Typ    : Entity_Id;
-      Expr         : Node_Id;
-      Needs_Fin    : Boolean;
-      Pool_Id      : Entity_Id;
-      Proc_To_Call : Node_Id := Empty;
-      Ptr_Typ      : Entity_Id;
+      Desig_Typ                : Entity_Id;
+      Expr                     : Node_Id;
+      Needs_Fin                : Boolean;
+      Pool_Id                  : Entity_Id;
+      Proc_To_Call             : Node_Id := Empty;
+      Ptr_Typ                  : Entity_Id;
+      Use_Secondary_Stack_Pool : Boolean;
 
    --  Start of processing for Build_Allocate_Deallocate_Proc
 
@@ -804,17 +805,22 @@ package body Exp_Util is
          Desig_Typ := Corresponding_Record_Type (Desig_Typ);
       end if;
 
+      Use_Secondary_Stack_Pool :=
+        Is_RTE (Pool_Id, RE_SS_Pool)
+          or else (Nkind (Expr) = N_Allocator
+                    and then Is_RTE (Storage_Pool (Expr), RE_SS_Pool));
+
       --  Do not process allocations / deallocations without a pool
 
       if No (Pool_Id) then
          return;
 
       --  Do not process allocations on / deallocations from the secondary
-      --  stack.
+      --  stack, except for access types used to implement indirect temps.
 
-      elsif Is_RTE (Pool_Id, RE_SS_Pool)
-        or else (Nkind (Expr) = N_Allocator
-                  and then Is_RTE (Storage_Pool (Expr), RE_SS_Pool))
+      elsif Use_Secondary_Stack_Pool
+        and then not Old_Attr_Util.Indirect_Temps
+                       .Is_Access_Type_For_Indirect_Temp (Ptr_Typ)
       then
          return;
 
@@ -951,7 +957,9 @@ package body Exp_Util is
          Append_To (Actuals, New_Occurrence_Of (Addr_Id, Loc));
          Append_To (Actuals, New_Occurrence_Of (Size_Id, Loc));
 
-         if Is_Allocate or else not Is_Class_Wide_Type (Desig_Typ) then
+         if (Is_Allocate or else not Is_Class_Wide_Type (Desig_Typ))
+           and then not Use_Secondary_Stack_Pool
+         then
             Append_To (Actuals, New_Occurrence_Of (Alig_Id, Loc));
 
          --  For deallocation of class-wide types we obtain the value of
@@ -967,6 +975,9 @@ package body Exp_Util is
             --  into the code that reads the value of alignment from the TSD
             --  (see Expand_N_Attribute_Reference)
 
+            --  In the Use_Secondary_Stack_Pool case, Alig_Id is not
+            --  passed in and therefore must not be referenced.
+
             Append_To (Actuals,
               Unchecked_Convert_To (RTE (RE_Storage_Offset),
                 Make_Attribute_Reference (Loc,
@@ -1116,55 +1127,67 @@ package body Exp_Util is
          --  Create a custom Allocate / Deallocate routine which has identical
          --  profile to that of System.Storage_Pools.
 
-         Insert_Action (N,
-           Make_Subprogram_Body (Loc,
-             Specification              =>
-
-               --  procedure Pnn
-
-               Make_Procedure_Specification (Loc,
-                 Defining_Unit_Name       => Proc_Id,
-                 Parameter_Specifications => New_List (
-
-                  --  P : Root_Storage_Pool
-
-                   Make_Parameter_Specification (Loc,
-                     Defining_Identifier => Make_Temporary (Loc, 'P'),
-                     Parameter_Type      =>
-                       New_Occurrence_Of (RTE (RE_Root_Storage_Pool), Loc)),
-
-                  --  A : [out] Address
-
-                   Make_Parameter_Specification (Loc,
-                     Defining_Identifier => Addr_Id,
-                     Out_Present         => Is_Allocate,
-                     Parameter_Type      =>
-                       New_Occurrence_Of (RTE (RE_Address), Loc)),
-
-                  --  S : Storage_Count
-
-                   Make_Parameter_Specification (Loc,
-                     Defining_Identifier => Size_Id,
-                     Parameter_Type      =>
-                       New_Occurrence_Of (RTE (RE_Storage_Count), Loc)),
-
-                  --  L : Storage_Count
-
-                   Make_Parameter_Specification (Loc,
-                     Defining_Identifier => Alig_Id,
-                     Parameter_Type      =>
-                       New_Occurrence_Of (RTE (RE_Storage_Count), Loc)))),
-
-             Declarations               => No_List,
+         declare
+            --  P : Root_Storage_Pool
+            function Pool_Param return Node_Id is (
+              Make_Parameter_Specification (Loc,
+                Defining_Identifier => Make_Temporary (Loc, 'P'),
+                Parameter_Type      =>
+                  New_Occurrence_Of (RTE (RE_Root_Storage_Pool), Loc)));
+
+            --  A : [out] Address
+            function Address_Param return Node_Id is (
+              Make_Parameter_Specification (Loc,
+                Defining_Identifier => Addr_Id,
+                Out_Present         => Is_Allocate,
+                Parameter_Type      =>
+                  New_Occurrence_Of (RTE (RE_Address), Loc)));
+
+            --  S : Storage_Count
+            function Size_Param return Node_Id is (
+              Make_Parameter_Specification (Loc,
+                Defining_Identifier => Size_Id,
+                Parameter_Type      =>
+                  New_Occurrence_Of (RTE (RE_Storage_Count), Loc)));
+
+            --  L : Storage_Count
+            function Alignment_Param return Node_Id is (
+              Make_Parameter_Specification (Loc,
+                Defining_Identifier => Alig_Id,
+                Parameter_Type      =>
+                  New_Occurrence_Of (RTE (RE_Storage_Count), Loc)));
+
+            Formal_Params : List_Id;
+         begin
+            if Use_Secondary_Stack_Pool then
+               --  Gigi expects a different profile in the Secondary_Stack_Pool
+               --  case. There must be no uses of the two missing formals
+               --  (i.e., Pool_Param and Alignment_Param) in this case.
+               Formal_Params := New_List (Address_Param, Size_Param);
+            else
+               Formal_Params := New_List (
+                 Pool_Param, Address_Param, Size_Param, Alignment_Param);
+            end if;
 
-             Handled_Statement_Sequence =>
-               Make_Handled_Sequence_Of_Statements (Loc,
-                 Statements => New_List (
-                   Make_Procedure_Call_Statement (Loc,
-                     Name                   =>
-                       New_Occurrence_Of (Proc_To_Call, Loc),
-                     Parameter_Associations => Actuals)))),
-           Suppress => All_Checks);
+            Insert_Action (N,
+              Make_Subprogram_Body (Loc,
+                Specification              =>
+                  --  procedure Pnn
+                  Make_Procedure_Specification (Loc,
+                    Defining_Unit_Name       => Proc_Id,
+                    Parameter_Specifications => Formal_Params),
+
+                Declarations               => No_List,
+
+                Handled_Statement_Sequence =>
+                  Make_Handled_Sequence_Of_Statements (Loc,
+                    Statements => New_List (
+                      Make_Procedure_Call_Statement (Loc,
+                        Name                   =>
+                          New_Occurrence_Of (Proc_To_Call, Loc),
+                        Parameter_Associations => Actuals)))),
+              Suppress => All_Checks);
+         end;
 
          --  The newly generated Allocate / Deallocate becomes the default
          --  procedure to call when the back end processes the allocation /
index 8bbf829..fcb04a2 100644 (file)
@@ -2828,9 +2828,21 @@ package body Sem_Attr is
 
          case Uneval_Old_Setting is
             when 'E' =>
+               --  ??? In the case where Ada_Version is < Ada_2020 and
+               --  an illegal 'Old prefix would be legal in Ada_2020,
+               --  we'd like to call Error_Msg_Ada_2020_Feature.
+               --  Identifying that case involves some work.
+
                Error_Attr_P
                  ("prefix of attribute % that is potentially "
-                  & "unevaluated must statically name an entity");
+                  & "unevaluated must statically name an entity"
+
+                  --  further text needed for accuracy if Ada_2020
+                  & (if Ada_Version >= Ada_2020
+                       and then Attr_Id = Attribute_Old
+                     then " or be eligible for conditional evaluation"
+                          & " (RM 6.1.1 (27))"
+                     else ""));
 
             when 'W' =>
                Error_Msg_Name_1 := Aname;
@@ -5136,10 +5148,15 @@ package body Sem_Attr is
 
          else
             --  Ensure that the prefix of attribute 'Old is an entity when it
-            --  is potentially unevaluated (6.1.1 (27/3)).
+            --  is potentially unevaluated (6.1.1 (27/3)). This rule is
+            --  relaxed in Ada2020 - this relaxation is reflected in the
+            --  call (below) to Eligible_For_Conditional_Evaluation.
 
             if Is_Potentially_Unevaluated (N)
               and then not Statically_Names_Object (P)
+              and then not
+                Old_Attr_Util.Conditional_Evaluation
+                 .Eligible_For_Conditional_Evaluation (N)
             then
                Uneval_Old_Msg;
 
index 66ad1e4..8b9902d 100644 (file)
@@ -13422,11 +13422,21 @@ package body Sem_Res is
             --  rewritten. The Comes_From_Source test isn't sufficient because
             --  nodes in inlined calls to predefined library routines can have
             --  Comes_From_Source set to False. (Is there a better way to test
-            --  for implicit conversions???)
+            --  for implicit conversions???).
+            --
+            --  Do not treat a rewritten 'Old attribute reference like other
+            --  rewrite substitutions. This makes a difference, for example,
+            --  in the case where we are generating the expansion of a
+            --  membership test of the form
+            --     Saooaaat'Old in Named_Access_Type
+            --  because in this case Valid_Conversion needs to return True
+            --  (otherwise the expansion will be False - see the call site
+            --  in exp_ch4.adb).
 
             if Ada_Version >= Ada_2012
               and then not Comes_From_Source (N)
               and then Is_Rewrite_Substitution (N)
+              and then not Is_Attribute_Old (Original_Node (N))
               and then Ekind (Base_Type (Target_Type)) = E_General_Access_Type
               and then Ekind (Opnd_Type) = E_Anonymous_Access_Type
             then
index f85dfd7..45a551f 100644 (file)
@@ -29698,6 +29698,915 @@ package body Sem_Util is
 
    end Interval_Lists;
 
+   package body Old_Attr_Util is
+      package body Conditional_Evaluation is
+         type Determining_Expr_Context is
+           (No_Context, If_Expr, Case_Expr, Short_Circuit_Op, Membership_Test);
+
+         --  Determining_Expr_Context enumeration elements (except for
+         --  No_Context) correspond to the list items in RM 6.1.1 definition
+         --  of "determining expression".
+
+         type Determining_Expr
+           (Context : Determining_Expr_Context := No_Context)
+         is record
+            Expr : Node_Id := Empty;
+            case Context is
+               when Short_Circuit_Op =>
+                  Is_And_Then         : Boolean;
+               when If_Expr =>
+                  Is_Then_Part        : Boolean;
+               when Case_Expr =>
+                  Alternatives        : Node_Id;
+               when Membership_Test =>
+                  --  Given a subexpression of <exp4> in a membership test
+                  --    <exp1> in <exp2> | <exp3> | <exp4> | <exp5>
+                  --  the corresponding determining expression value would
+                  --  have First_Non_Preceding = <exp4> (See RM 6.1.1).
+                  First_Non_Preceding : Node_Id;
+               when No_Context =>
+                  null;
+            end case;
+         end record;
+
+         type Determining_Expression_List is
+           array (Positive range <>) of Determining_Expr;
+
+         function Determining_Condition (Det : Determining_Expr)
+           return Node_Id;
+         --  Given a determining expression, build a Boolean-valued
+         --  condition that incorporates that expression into condition
+         --  suitable for deciding whether to initialize a 'Old constant.
+         --  Polarity is "True => initialize the constant".
+
+         function Determining_Expressions
+           (Expr : Node_Id; Expr_Trailer : Node_Id := Empty)
+           return Determining_Expression_List;
+         --  Given a conditionally evaluated expression, return its
+         --  determining expressions.
+         --  See RM 6.1.1 for definition of term "determining expressions".
+         --  Tests should be performed in the order they occur in the
+         --  array, with short circuiting.
+         --  A determining expression need not be of a boolean type (e.g.,
+         --  it might be the determining expression of a case expression).
+         --  The Expr_Trailer parameter should be defaulted for nonrecursive
+         --  calls.
+
+         function Is_Conditionally_Evaluated (Expr : Node_Id) return Boolean;
+         --  See RM 6.1.1 for definition of term "conditionally evaluated".
+
+         function Is_Known_On_Entry (Expr : Node_Id) return Boolean;
+         --  See RM 6.1.1 for definition of term "known on entry".
+
+         --------------------------------------
+         -- Conditional_Evaluation_Condition --
+         --------------------------------------
+
+         function Conditional_Evaluation_Condition
+           (Expr : Node_Id) return Node_Id
+         is
+            Determiners : constant Determining_Expression_List :=
+              Determining_Expressions (Expr);
+            Loc         : constant Source_Ptr := Sloc (Expr);
+            Result      : Node_Id :=
+              New_Occurrence_Of (Standard_True, Loc);
+         begin
+            pragma Assert (Determiners'Length > 0 or else
+                           Is_Anonymous_Access_Type (Etype (Expr)));
+
+            for I in Determiners'Range loop
+               Result := Make_And_Then
+                          (Loc,
+                           Left_Opnd  => Result,
+                           Right_Opnd =>
+                             Determining_Condition (Determiners (I)));
+            end loop;
+            return Result;
+         end Conditional_Evaluation_Condition;
+
+         ---------------------------
+         -- Determining_Condition --
+         ---------------------------
+
+         function Determining_Condition (Det : Determining_Expr) return Node_Id
+         is
+            Loc : constant Source_Ptr := Sloc (Det.Expr);
+         begin
+            case Det.Context is
+               when Short_Circuit_Op =>
+                  if Det.Is_And_Then then
+                     return New_Copy_Tree (Det.Expr);
+                  else
+                     return Make_Op_Not (Loc, New_Copy_Tree (Det.Expr));
+                  end if;
+
+               when If_Expr =>
+                  if Det.Is_Then_Part then
+                     return New_Copy_Tree (Det.Expr);
+                  else
+                     return Make_Op_Not (Loc, New_Copy_Tree (Det.Expr));
+                  end if;
+
+               when Case_Expr =>
+                  declare
+                     Alts : List_Id := Discrete_Choices (Det.Alternatives);
+                  begin
+                     if Nkind (First (Alts)) = N_Others_Choice then
+                        Alts := Others_Discrete_Choices (First (Alts));
+                     end if;
+
+                     return Make_In (Loc,
+                       Left_Opnd    => New_Copy_Tree (Det.Expr),
+                       Right_Opnd   => Empty,
+                       Alternatives => New_Copy_List (Alts));
+                  end;
+
+               when Membership_Test =>
+                  declare
+                     function Copy_Prefix
+                       (List : List_Id; Suffix_Start : Node_Id)
+                       return List_Id;
+                     --  Given a list and a member of that list, returns
+                     --  a copy (similar to Nlists.New_Copy_List) of the
+                     --  prefix of the list up to but not including
+                     --  Suffix_Start.
+
+                     -----------------
+                     -- Copy_Prefix --
+                     -----------------
+
+                     function Copy_Prefix
+                       (List : List_Id; Suffix_Start : Node_Id)
+                       return List_Id
+                     is
+                        Result : constant List_Id := New_List;
+                        Elem   : Node_Id := First (List);
+                     begin
+                        while Elem /= Suffix_Start loop
+                           Append (New_Copy (Elem), Result);
+                           Next (Elem);
+                           pragma Assert (Present (Elem));
+                        end loop;
+                        return Result;
+                     end Copy_Prefix;
+
+                  begin
+                     return Make_In (Loc,
+                       Left_Opnd    => New_Copy_Tree (Left_Opnd (Det.Expr)),
+                       Right_Opnd   => Empty,
+                       Alternatives => Copy_Prefix
+                                         (Alternatives (Det.Expr),
+                                          Det.First_Non_Preceding));
+                  end;
+
+               when No_Context =>
+                  raise Program_Error;
+            end case;
+         end Determining_Condition;
+
+         -----------------------------
+         -- Determining_Expressions --
+         -----------------------------
+
+         function Determining_Expressions
+           (Expr : Node_Id; Expr_Trailer : Node_Id := Empty)
+           return Determining_Expression_List
+         is
+            Par           : Node_Id := Expr;
+            Trailer       : Node_Id := Expr_Trailer;
+            Next_Element  : Determining_Expr;
+         begin
+            --  We want to stop climbing up the tree when we reach the
+            --  postcondition expression. An aspect_specification is
+            --  transformed into a pragma, so reaching a pragma is our
+            --  termination condition. This relies on the fact that
+            --  pragmas are not allowed in declare expressions (or any
+            --  other kind of expression).
+
+            loop
+               Next_Element.Expr := Empty;
+
+               case Nkind (Par) is
+                  when N_Short_Circuit =>
+                     if Trailer = Right_Opnd (Par) then
+                        Next_Element :=
+                          (Expr        => Left_Opnd (Par),
+                           Context     => Short_Circuit_Op,
+                           Is_And_Then => Nkind (Par) = N_And_Then);
+                     end if;
+
+                  when N_If_Expression =>
+                     --  For an expression like
+                     --    (if C1 then ... elsif C2 then ... else Foo'Old)
+                     --  the RM says are two determining expressions,
+                     --  C1 and C2. Our treatment here (where we only add
+                     --  one determining expression to the list) is ok because
+                     --  we will see two if-expressions, one within the other.
+
+                     if Trailer /= First (Expressions (Par)) then
+                        Next_Element :=
+                           (Expr         => First (Expressions (Par)),
+                            Context      => If_Expr,
+                            Is_Then_Part =>
+                              Trailer = Next (First (Expressions (Par))));
+                     end if;
+
+                  when N_Case_Expression_Alternative =>
+                     pragma Assert (Nkind (Parent (Par)) = N_Case_Expression);
+
+                     Next_Element :=
+                       (Expr         => Expression (Parent (Par)),
+                        Context      => Case_Expr,
+                        Alternatives => Par);
+
+                  when N_Membership_Test =>
+                     if Trailer /= Left_Opnd (Par)
+                       and then Is_Non_Empty_List (Alternatives (Par))
+                       and then Trailer /= First (Alternatives (Par))
+                     then
+                        pragma Assert (not Present (Right_Opnd (Par)));
+                        pragma Assert
+                          (Is_List_Member (Trailer)
+                           and then List_Containing (Trailer)
+                                    = Alternatives (Par));
+
+                        --  This one is different than the others
+                        --  because one element in the array result
+                        --  may represent multiple determining
+                        --  expressions (i.e. every member of the list
+                        --     Alternatives (Par)
+                        --  up to but not including Trailer).
+
+                        Next_Element :=
+                          (Expr                => Par,
+                           Context             => Membership_Test,
+                           First_Non_Preceding => Trailer);
+                     end if;
+
+                  when N_Pragma =>
+                     declare
+                        Previous : constant Node_Id := Prev (Par);
+                        Prev_Expr : Node_Id;
+                     begin
+                        if Nkind (Previous) = N_Pragma and then
+                          Split_PPC (Previous)
+                        then
+                           --  A source-level postcondition of
+                           --    A and then B and then C
+                           --  results in
+                           --    pragma Postcondition (A);
+                           --    pragma Postcondition (B);
+                           --    pragma Postcondition (C);
+                           --  with Split_PPC set to True on all but the
+                           --  last pragma. We account for that here.
+
+                           Prev_Expr :=
+                             Expression (First
+                               (Pragma_Argument_Associations (Previous)));
+
+                           --  This Analyze call is needed in the case when
+                           --  Sem_Attr.Analyze_Attribute calls
+                           --  Eligible_For_Conditional_Evaluation. Without
+                           --  it, we end up passing an unanalyzed expression
+                           --  to Is_Known_On_Entry and that doesn't work.
+
+                           Analyze (Prev_Expr);
+
+                           Next_Element :=
+                             (Expr        => Prev_Expr,
+                              Context     => Short_Circuit_Op,
+                              Is_And_Then => True);
+
+                           return Determining_Expressions (Prev_Expr)
+                             & Next_Element;
+                        else
+                           pragma Assert
+                             (Get_Pragma_Id (Pragma_Name (Par)) in
+                                Pragma_Post | Pragma_Postcondition
+                                | Pragma_Post_Class | Pragma_Refined_Post
+                                | Pragma_Check | Pragma_Contract_Cases);
+
+                           return (1 .. 0 => <>); -- recursion terminates here
+                        end if;
+                     end;
+
+                  when N_Empty =>
+                     --  This case should be impossible, but if it does
+                     --  happen somehow then we don't want an infinite loop.
+                     raise Program_Error;
+
+                  when others =>
+                     null;
+               end case;
+
+               Trailer := Par;
+               Par := Parent (Par);
+
+               if Present (Next_Element.Expr) then
+                  return Determining_Expressions
+                           (Expr => Par, Expr_Trailer => Trailer)
+                         & Next_Element;
+               end if;
+            end loop;
+         end Determining_Expressions;
+
+         -----------------------------------------
+         -- Eligible_For_Conditional_Evaluation --
+         -----------------------------------------
+
+         function Eligible_For_Conditional_Evaluation
+           (Expr : Node_Id) return Boolean
+         is
+         begin
+            if Is_Anonymous_Access_Type (Etype (Expr)) then
+               --  The code in exp_attr.adb that also builds declarations
+               --  for 'Old constants doesn't handle the anonymous access
+               --  type case correctly, so we avoid that problem by
+               --  returning True here.
+               return True;
+            elsif Ada_Version < Ada_2020 then
+               return False;
+            elsif not Is_Conditionally_Evaluated (Expr) then
+               return False;
+            else
+               declare
+                  Determiners : constant Determining_Expression_List :=
+                    Determining_Expressions (Expr);
+               begin
+                  pragma Assert (Determiners'Length > 0);
+
+                  for Idx in Determiners'Range loop
+                     if not Is_Known_On_Entry (Determiners (Idx).Expr) then
+                        return False;
+                     end if;
+                  end loop;
+               end;
+               return True;
+            end if;
+         end Eligible_For_Conditional_Evaluation;
+
+         --------------------------------
+         -- Is_Conditionally_Evaluated --
+         --------------------------------
+
+         function Is_Conditionally_Evaluated (Expr : Node_Id) return Boolean
+         is
+            --  There are three possibilities - the expression is
+            --  unconditionally evaluated, repeatedly evaluated, or
+            --  conditionally evaluated (see RM 6.1.1). So we implement
+            --  this test by testing for the other two.
+
+            function Is_Repeatedly_Evaluated (Expr : Node_Id) return Boolean;
+            --  See RM 6.1.1 for definition of "repeatedly evaluated".
+
+            -----------------------------
+            -- Is_Repeatedly_Evaluated --
+            -----------------------------
+
+            function Is_Repeatedly_Evaluated (Expr : Node_Id) return Boolean is
+               Par : Node_Id := Expr;
+               Trailer : Node_Id := Empty;
+
+               --  There are three ways that an expression can be repeatedly
+               --  evaluated. We only test for two of them here because
+               --  container aggregates and the Aggregate aspect are not
+               --  implemented yet. ???
+
+            begin
+               --  An aspect_specification is transformed into a pragma, so
+               --  reaching a pragma is our termination condition. We want to
+               --  stop when we reach the postcondition expression.
+
+               while Nkind (Par) /= N_Pragma loop
+                  pragma Assert (Present (Par));
+
+                  --  test for case 1:
+                  --    A subexpression of a predicate of a
+                  --    quantified_expression.
+
+                  if Nkind (Par) = N_Quantified_Expression
+                     and then Trailer = Condition (Par)
+                  then
+                     return True;
+                  end if;
+
+                  --  test for case 2:
+                  --    A subexpression of the expression of an
+                  --    array_component_association
+
+                  if Nkind (Par) = N_Component_Association
+                    and then Trailer = Expression (Par)
+                  then
+
+                     --  determine whether Par is part of an array aggregate
+                     declare
+                        Rover : Node_Id := Par;
+                     begin
+                        while Nkind (Rover) not in N_Has_Etype loop
+                           pragma Assert (Present (Rover));
+                           Rover := Parent (Rover);
+                        end loop;
+                        if Present (Etype (Rover))
+                          and then Is_Array_Type (Etype (Rover))
+                        then
+                           return True;
+                        end if;
+                     end;
+                  end if;
+
+                  --  As noted above, there is a case 3 that we don't yet
+                  --  test for. When we do, that test goes here. ???
+                  null;
+
+                  Trailer := Par;
+                  Par := Parent (Par);
+               end loop;
+
+               return False;
+            end Is_Repeatedly_Evaluated;
+
+         begin
+            if not Is_Potentially_Unevaluated (Expr) then
+               --  the expression is unconditionally evaluated
+               return False;
+            elsif Is_Repeatedly_Evaluated (Expr) then
+               return False;
+            end if;
+
+            return True;
+         end Is_Conditionally_Evaluated;
+
+         -----------------------
+         -- Is_Known_On_Entry --
+         -----------------------
+
+         function Is_Known_On_Entry (Expr : Node_Id) return Boolean is
+            --  ??? This implementation is incomplete. See RM 6.1.1
+            --  for details. In particular, this function *should* return
+            --  True for a function call (or a user-defined literal, which
+            --  is equivalent to a function call) if all actual parameters
+            --  (including defaulted params) are known on entry and the
+            --  function has "Globals => null" specified; the current
+            --  implementation will incorrectly return False in this case.
+
+            function All_Exps_Known_On_Entry
+              (Expr_List : List_Id) return Boolean;
+            --  Given a list of expressions, returns False iff
+            --  Is_Known_On_Entry is False for at least one list element.
+
+            -----------------------------
+            -- All_Exps_Known_On_Entry --
+            -----------------------------
+
+            function All_Exps_Known_On_Entry
+              (Expr_List : List_Id) return Boolean
+            is
+               Expr : Node_Id := First (Expr_List);
+            begin
+               while Present (Expr) loop
+                  if not Is_Known_On_Entry (Expr) then
+                     return False;
+                  end if;
+                  Next (Expr);
+               end loop;
+               return True;
+            end All_Exps_Known_On_Entry;
+
+         begin
+            if Is_Static_Expression (Expr) then
+               return True;
+            end if;
+
+            if Is_Attribute_Old (Expr) then
+               return True;
+            end if;
+
+            declare
+               Pref : Node_Id := Expr;
+            begin
+               loop
+                  case Nkind (Pref) is
+                     when N_Selected_Component =>
+                        null;
+
+                     when N_Indexed_Component =>
+                        if not All_Exps_Known_On_Entry (Expressions (Pref))
+                        then
+                           return False;
+                        end if;
+
+                     when N_Slice =>
+                        return False; -- just to be clear about this case
+
+                     when others =>
+                        exit;
+                  end case;
+
+                  Pref := Prefix (Pref);
+               end loop;
+
+               if Is_Entity_Name (Pref)
+                 and then Is_Constant_Object (Entity (Pref))
+               then
+                  declare
+                     Obj     : constant Entity_Id := Entity (Pref);
+                     Obj_Typ : constant Entity_Id := Etype (Obj);
+                  begin
+                     case Ekind (Obj) is
+                        when E_In_Parameter =>
+                           if not Is_Elementary_Type (Obj_Typ) then
+                              return False;
+                           elsif Is_Aliased (Obj) then
+                              return False;
+                           end if;
+
+                        when E_Constant =>
+                           --  return False for a deferred constant
+                           if Present (Full_View (Obj)) then
+                              return False;
+                           end if;
+
+                           --  return False if not "all views are constant".
+                           if Is_Immutably_Limited_Type (Obj_Typ)
+                             or Needs_Finalization (Obj_Typ)
+                           then
+                              return False;
+                           end if;
+
+                        when others =>
+                           null;
+                     end case;
+                  end;
+
+                  return True;
+               end if;
+
+               --  ??? Cope with a malformed tree. Code to cope with a
+               --  nonstatic use of an enumeration literal should not be
+               --  necessary.
+               if Is_Entity_Name (Pref)
+                 and then Ekind (Entity (Pref)) = E_Enumeration_Literal
+               then
+                  return True;
+               end if;
+            end;
+
+            case Nkind (Expr) is
+               when N_Unary_Op =>
+                  return Is_Known_On_Entry (Right_Opnd (Expr));
+
+               when N_Binary_Op =>
+                  return Is_Known_On_Entry (Left_Opnd (Expr))
+                    and then Is_Known_On_Entry (Right_Opnd (Expr));
+
+               when N_Type_Conversion | N_Qualified_Expression =>
+                  return Is_Known_On_Entry (Expression (Expr));
+
+               when N_If_Expression =>
+                  if not All_Exps_Known_On_Entry (Expressions (Expr)) then
+                     return False;
+                  end if;
+
+               when N_Case_Expression =>
+                  if not Is_Known_On_Entry (Expression (Expr)) then
+                     return False;
+                  end if;
+
+                  declare
+                     Alt : Node_Id := First (Alternatives (Expr));
+                  begin
+                     while Present (Alt) loop
+                        if not Is_Known_On_Entry (Expression (Alt)) then
+                           return False;
+                        end if;
+                        Next (Alt);
+                     end loop;
+                  end;
+
+                  return True;
+
+               when others =>
+                  null;
+            end case;
+
+            return False;
+         end Is_Known_On_Entry;
+
+      end Conditional_Evaluation;
+
+      package body Indirect_Temps is
+
+         Indirect_Temp_Access_Type_Char : constant Character := 'K';
+         --  The character passed to Make_Temporary when declaring
+         --  the access type that is used in the implementation of an
+         --  indirect temporary.
+
+         --------------------------
+         -- Indirect_Temp_Needed --
+         --------------------------
+
+         function Indirect_Temp_Needed (Typ : Entity_Id) return Boolean is
+         begin
+            --  There should be no correctness issues if the only cases where
+            --  this function returns False are cases where Typ is an
+            --  anonymous access type and we need to generate a saooaaat (a
+            --  stand-alone object of an anonymous access type) in order get
+            --  accessibility right. In other cases where this function
+            --  returns False, there would be no correctness problems with
+            --  returning True instead; however, returning False when we can
+            --  generally results in simpler code.
+
+            return False
+
+               --  If Typ is not definite, then we cannot generate
+               --    Temp : Typ;
+
+              or else not Is_Definite_Subtype (Typ)
+
+              --  If Typ is tagged, then generating
+              --    Temp : Typ;
+              --  might generate an object with the wrong tag. If we had
+              --  a predicate that indicated whether the nominal tag is
+              --  trustworthy, we could use that predicate here.
+
+              or else Is_Tagged_Type (Typ)
+
+              --  If Typ needs finalization, then generating an implicit
+              --    Temp : Typ;
+              --  declaration could have user-visible side effects.
+
+              or else Needs_Finalization (Typ)
+
+              --  In the anonymous access type case, we need to
+              --  generate a saooaaat. We don't want the code in
+              --  in exp_attr.adb that deals with the case where this
+              --  function returns False to have to deal with that case
+              --  (just to avoid code duplication). So we cheat a little
+              --  bit and return True here for an anonymous access type.
+
+              or else Is_Anonymous_Access_Type (Typ);
+
+            --  ??? Unimplemented - spec description says:
+            --    For an unconstrained-but-definite discriminated subtype,
+            --    returns True if the potential difference in size between an
+            --    unconstrained object and a constrained object is large.
+            --
+            --  For example,
+            --    type Typ (Len : Natural := 0) is
+            --      record F : String (1 .. Len); end record;
+            --
+            --  See Large_Max_Size_Mutable function elsewhere in this
+            --  file (currently declared inside of
+            --  New_Requires_Transient_Scope, so it would have to be
+            --  moved if we want it to be callable from here).
+
+         end Indirect_Temp_Needed;
+
+         ---------------------------
+         -- Declare_Indirect_Temp --
+         ---------------------------
+
+         procedure Declare_Indirect_Temp
+           (Attr_Prefix : Node_Id; Indirect_Temp : out Entity_Id)
+         is
+            Loc         : constant Source_Ptr := Sloc (Attr_Prefix);
+            Prefix_Type : constant Entity_Id := Etype (Attr_Prefix);
+            Temp_Id     : constant Entity_Id :=
+              Make_Temporary (Loc, 'P', Attr_Prefix);
+
+            procedure Declare_Indirect_Temp_Via_Allocation;
+            --  Handle the usual case.
+
+            -------------------------------------------
+            --  Declare_Indirect_Temp_Via_Allocation --
+            -------------------------------------------
+
+            procedure Declare_Indirect_Temp_Via_Allocation is
+               Access_Type_Id : constant Entity_Id
+                 := Make_Temporary
+                      (Loc, Indirect_Temp_Access_Type_Char, Attr_Prefix);
+
+               Temp_Decl : constant Node_Id :=
+                 Make_Object_Declaration (Loc,
+                   Defining_Identifier => Temp_Id,
+                   Object_Definition   =>
+                     New_Occurrence_Of (Access_Type_Id, Loc));
+
+               Allocate_Class_Wide : constant Boolean :=
+                 Is_Specific_Tagged_Type (Prefix_Type);
+               --  If True then access type designates the class-wide type in
+               --  order to preserve (at run time) the value of the underlying
+               --  tag.
+               --  ??? We could do better here (in the case where Prefix_Type
+               --  is tagged and specific) if we had a predicate which takes an
+               --  expression and returns True iff the expression is of
+               --  a specific tagged type and the underlying tag (at run time)
+               --  is statically known to match that of the specific type.
+               --  In that case, Allocate_Class_Wide could safely be False.
+
+               function Designated_Subtype_Mark return Node_Id;
+               --  Usually, a subtype mark indicating the subtype of the
+               --  attribute prefix. If that subtype is a specific tagged
+               --  type, then returns the corresponding class-wide type.
+               --  If the prefix is of an anonymous access type, then returns
+               --  the designated type of that type.
+
+            -----------------------------
+               -- Designated_Subtype_Mark --
+            -----------------------------
+
+               function Designated_Subtype_Mark return Node_Id is
+                  Typ : Entity_Id := Prefix_Type;
+               begin
+                  if Allocate_Class_Wide then
+                     if Is_Private_Type (Typ)
+                       and then Present (Full_View (Typ))
+                     then
+                        Typ := Full_View (Typ);
+                     end if;
+                     Typ := Class_Wide_Type (Typ);
+                  end if;
+
+                  return New_Occurrence_Of (Typ, Loc);
+               end Designated_Subtype_Mark;
+
+               Access_Type_Def : constant Node_Id
+                 := Make_Access_To_Object_Definition
+                      (Loc, Subtype_Indication => Designated_Subtype_Mark);
+
+               Access_Type_Decl : constant Node_Id
+                 := Make_Full_Type_Declaration
+                      (Loc, Access_Type_Id,
+                       Type_Definition => Access_Type_Def);
+            begin
+               Set_Ekind (Temp_Id, E_Variable);
+               Set_Etype (Temp_Id, Access_Type_Id);
+               Set_Ekind (Access_Type_Id, E_Access_Type);
+
+               if Append_Decls_In_Reverse_Order then
+                  Append_Item (Temp_Decl, Is_Eval_Stmt => False);
+                  Append_Item (Access_Type_Decl, Is_Eval_Stmt => False);
+               else
+                  Append_Item (Access_Type_Decl, Is_Eval_Stmt => False);
+                  Append_Item (Temp_Decl, Is_Eval_Stmt => False);
+               end if;
+
+               Analyze (Access_Type_Decl);
+               Analyze (Temp_Decl);
+
+               pragma Assert
+                 (Is_Access_Type_For_Indirect_Temp (Access_Type_Id));
+
+               declare
+                  Expression : Node_Id := Attr_Prefix;
+                  Allocator  : Node_Id;
+               begin
+                  if Allocate_Class_Wide then
+                     --  generate T'Class'(T'Class (<prefix>))
+                     Expression :=
+                       Make_Type_Conversion (Loc,
+                         Subtype_Mark => Designated_Subtype_Mark,
+                         Expression   => Expression);
+                  end if;
+
+                  Allocator :=
+                    Make_Allocator (Loc,
+                      Make_Qualified_Expression
+                        (Loc,
+                         Subtype_Mark => Designated_Subtype_Mark,
+                         Expression   => Expression));
+
+                  --  Allocate saved prefix value on the secondary stack
+                  --  in order to avoid introducing a storage leak. This
+                  --  allocated object is never explicitly reclaimed.
+                  --
+                  --  ??? Emit storage leak warning if RE_SS_Pool
+                  --  unavailable?
+
+                  if RTE_Available (RE_SS_Pool) then
+                     Set_Storage_Pool (Allocator, RTE (RE_SS_Pool));
+                     Set_Procedure_To_Call
+                       (Allocator, RTE (RE_SS_Allocate));
+                     Set_Uses_Sec_Stack (Current_Scope);
+                  end if;
+
+                  Append_Item
+                    (Make_Assignment_Statement (Loc,
+                       Name       => New_Occurrence_Of (Temp_Id, Loc),
+                       Expression => Allocator),
+                     Is_Eval_Stmt => True);
+               end;
+            end Declare_Indirect_Temp_Via_Allocation;
+
+         begin
+            Indirect_Temp := Temp_Id;
+
+            if Is_Anonymous_Access_Type (Prefix_Type) then
+               --  In the anonymous access type case, we do not want a level
+               --  indirection (which would result in declaring an
+               --  access-to-access type); that would result in correctness
+               --  problems - the accessibility level of the type of the
+               --  'Old constant would be wrong (See 6.1.1.). So in that case,
+               --  we do not generate an allocator. Instead we generate
+               --     Temp : access Designated := null;
+               --  which is unconditionally elaborated and then
+               --     Temp := <attribute prefix>;
+               --  which is conditionally executed.
+
+               declare
+                  Temp_Decl : constant Node_Id :=
+                    Make_Object_Declaration (Loc,
+                      Defining_Identifier => Temp_Id,
+                      Object_Definition   =>
+                        Make_Access_Definition
+                          (Loc,
+                           Constant_Present =>
+                             Is_Access_Constant (Prefix_Type),
+                           Subtype_Mark =>
+                             New_Occurrence_Of
+                               (Designated_Type (Prefix_Type), Loc)));
+               begin
+                  Append_Item (Temp_Decl, Is_Eval_Stmt => False);
+                  Analyze (Temp_Decl);
+                  Append_Item
+                    (Make_Assignment_Statement (Loc,
+                       Name       => New_Occurrence_Of (Temp_Id, Loc),
+                       Expression => Attr_Prefix),
+                     Is_Eval_Stmt => True);
+               end;
+            else
+               --  the usual case
+               Declare_Indirect_Temp_Via_Allocation;
+            end if;
+         end Declare_Indirect_Temp;
+
+         -------------------------
+         -- Indirect_Temp_Value --
+         -------------------------
+
+         function Indirect_Temp_Value
+           (Temp : Entity_Id;
+            Typ  : Entity_Id;
+            Loc  : Source_Ptr) return Node_Id
+         is
+            Result : Node_Id;
+         begin
+            if Is_Anonymous_Access_Type (Typ) then
+               --  No indirection in this case; just evaluate the temp.
+               Result := New_Occurrence_Of (Temp, Loc);
+               Set_Etype (Result, Etype (Temp));
+
+            else
+               Result := Make_Explicit_Dereference (Loc,
+                                     New_Occurrence_Of (Temp, Loc));
+
+               Set_Etype (Result, Designated_Type (Etype (Temp)));
+
+               if Is_Specific_Tagged_Type (Typ) then
+                  --  The designated type of the access type is class-wide, so
+                  --  convert to the specific type.
+
+                  Result :=
+                    Make_Type_Conversion (Loc,
+                      Subtype_Mark => New_Occurrence_Of (Typ, Loc),
+                      Expression   => Result);
+
+                  Set_Etype (Result, Typ);
+               end if;
+            end if;
+
+            return Result;
+         end Indirect_Temp_Value;
+
+         function Is_Access_Type_For_Indirect_Temp
+           (T : Entity_Id) return Boolean is
+         begin
+            if Is_Access_Type (T)
+               and then not Comes_From_Source (T)
+               and then Is_Internal_Name (Chars (T))
+               and then Nkind (Scope (T)) in N_Entity
+               and then Ekind (Scope (T))
+                 in E_Entry | E_Entry_Family | E_Function | E_Procedure
+               and then
+                 (Present (Postconditions_Proc (Scope (T)))
+                  or else Present (Contract (Scope (T))))
+            then
+               --  ??? Should define a flag for this. We could incorrectly
+               --  return True if other clients of Make_Temporary happen to
+               --  pass in the same character.
+               declare
+                  Name : constant String := Get_Name_String (Chars (T));
+               begin
+                  if Name (Name'First) = Indirect_Temp_Access_Type_Char then
+                     return True;
+                  end if;
+               end;
+            end if;
+            return False;
+         end Is_Access_Type_For_Indirect_Temp;
+
+      end Indirect_Temps;
+   end Old_Attr_Util;
 begin
    Erroutc.Subprogram_Name_Ptr := Subprogram_Name'Access;
 end Sem_Util;
index 73c7d2b..1d6794c 100644 (file)
@@ -3205,11 +3205,97 @@ package Sem_Util is
       --  correctly for real types with static predicates, we may need
       --  an analogous Real_Interval_List type. Most of the language
       --  rules that reference "is statically compatible" pertain to
-      --  discriminants and therefore do require support for real types;
+      --  discriminants and therefore do not require support for real types;
       --  the exception is 12.5.1(8).
 
       Intervals_Error : exception;
       --  Raised when the list of non-empty pair-wise disjoint intervals cannot
       --  be built.
    end Interval_Lists;
+
+   package Old_Attr_Util is
+      --  Operations related to 'Old attribute evaluation. This
+      --  includes cases where a level of indirection is needed due to
+      --  conditional evaluation as well as support for the
+      --  "known on entry" rules.
+
+      package Conditional_Evaluation is
+         function Eligible_For_Conditional_Evaluation
+           (Expr : Node_Id) return Boolean;
+         --  Given a subexpression of a Postcondition expression
+         --  (typically a 'Old attribute reference), returns True if
+         --     - the expression is conditionally evaluated; and
+         --     - its determining expressions are all known on entry; and
+         --     - Ada_Version >= Ada_2020.
+         --  See RM 6.1.1 for definitions of these terms.
+         --
+         --  Also returns True if Expr is of an anonymous access type;
+         --  this is just because we want the code that knows how to build
+         --  'Old temps in that case to reside in only one place.
+
+         function Conditional_Evaluation_Condition
+           (Expr : Node_Id) return Node_Id;
+         --  Given an expression which is eligible for conditional evaluation,
+         --  build a Boolean expression whose value indicates whether the
+         --  expression should be evaluated.
+      end Conditional_Evaluation;
+
+      package Indirect_Temps is
+         generic
+            with procedure Append_Item (N : Node_Id; Is_Eval_Stmt : Boolean);
+            --  If Is_Eval_Stmt is True, then N is a statement that should
+            --  only be executed in the case where the 'Old prefix is to be
+            --  evaluated. If Is_Eval_Stmt is False, then N is a declaration
+            --  which should be elaborated unconditionally.
+            --  Client is responsible for ensuring that any appended
+            --  Eval_Stmt nodes are eventually analyzed.
+
+            Append_Decls_In_Reverse_Order : Boolean := False;
+            --  This parameter is for the convenience of exp_prag.adb, where we
+            --  want to Prepend rather than Append so it is better to get the
+            --  Append calls in reverse order.
+
+         procedure Declare_Indirect_Temp
+           (Attr_Prefix   : Node_Id; -- prefix of 'Old attribute (or similar?)
+            Indirect_Temp : out Entity_Id);
+         --  Indirect_Temp is of an access type; it is unconditionally
+         --  declared but only conditionally initialized to reference the
+         --  saved value of Attr_Prefix.
+
+         function Indirect_Temp_Needed (Typ : Entity_Id) return Boolean;
+         --  Returns True for a specific tagged type because the temp must
+         --  be of the class-wide type in order to preserve the underlying tag.
+         --
+         --  Also returns True in the case of an anonymous access type
+         --  because we want the code that knows how to deal with
+         --  this case to reside in only one place.
+         --
+         --  For an unconstrained-but-definite discriminated subtype, returns
+         --  True if the potential difference in size between an
+         --  unconstrained object and a constrained object is large.
+         --  [This part is not implemented yet.]
+         --
+         --  Otherwise, returns False if a declaration of the form
+         --     Temp : Typ;
+         --  is legal and side-effect-free (assuming that default
+         --  initialization is suppressed). For example, returns True if Typ is
+         --  indefinite, or if Typ has a controlled part.
+         --
+
+         function Indirect_Temp_Value
+           (Temp : Entity_Id;
+            Typ  : Entity_Id;
+            Loc  : Source_Ptr) return Node_Id;
+         --  Evaluate a temp declared by Declare_Indirect_Temp.
+
+         function Is_Access_Type_For_Indirect_Temp
+           (T : Entity_Id) return Boolean;
+         --  True for an access type that was declared via a call
+         --  to Declare_Indirect_Temp.
+         --  Indicates that the given access type should be treated
+         --  the same with respect to finalization as a
+         --  user-defined "comes from source" access type.
+
+      end Indirect_Temps;
+   end Old_Attr_Util;
 end Sem_Util;