This patch modifies the transient scope mechanism to prevent secondary stack leaks during object initialization. The modifications are as follows:
1) Prior to this change, the secondary stack was never managed within type initialization procedures, for reasons unknown. It is speculated that the controlled type model used at that time may have influenced this decision. The secondary stack is now managed within type initialization procedures in order to recover the memory once individual components or whole objects are initialized.
2) A transient scope now delegates the secondary stack management to an enclosing scope if there is no suitable context to wrap. This ensures that the requirement to manage the secondary stack is not lost when the scope was established for that purpose in mind.
3) A previous mechanism which examined the definition of a type (recursively) to determine whether the type will involve the secondary stack was removed because a) the mechanism could not detect this need with certainty, and b) the trigger for secondary stack usage is now moved to the resolution of function calls, which is always accurate.
-- Source --
with Ada.Finalization; use Ada.Finalization;
package Types is type Ctrl is new Controlled with record Id : Integer; end record;
procedure Initialize (Obj : in out Ctrl);
function Make_Ctrl return Ctrl; function Make_Ctrl_From (Obj : Ctrl) return Ctrl;
type Constr is array (1 .. 3) of Ctrl; type Unconstr is array (Integer range <>) of Ctrl;
function Make_Constr return Constr; function Make_Unconstr (Low : Integer; High : Integer) return Unconstr;
type Rec_1 is new Controlled with record Comp : Ctrl := Make_Ctrl; end record;
type Rec_2 is new Controlled with record Comp : Ctrl := Make_Ctrl_From (Make_Ctrl); end record;
type Rec_3 is new Controlled with record Comp : Constr := Make_Constr; end record;
type Rec_4 is new Controlled with record Comp : Unconstr (1 .. 3) := Make_Unconstr (1, 3); end record;
type Rec_5 is record Comp : Integer := 1 + Make_Ctrl.Id; end record;
type Rec_6 is record Comp : Boolean := (for all X in 1 .. Make_Ctrl.Id => X = Make_Ctrl.Id); end record; end Types;
package body Types is Id_Gen : Integer := 0;
procedure Initialize (Obj : in out Ctrl) is begin Id_Gen := Id_Gen + 1; Obj.Id := Id_Gen; end Initialize;
function Make_Constr return Constr is Result : constant Constr := (others => Make_Ctrl); begin return Result; end Make_Constr;
function Make_Ctrl return Ctrl is Result : Ctrl; begin return Result; end Make_Ctrl;
function Make_Ctrl_From (Obj : Ctrl) return Ctrl is Result : Ctrl; begin Result.Id := Obj.Id; return Result; end Make_Ctrl_From;
function Make_Unconstr (Low : Integer; High : Integer) return Unconstr is Result : constant Unconstr (Low .. High) := (others => Make_Ctrl); begin return Result; end Make_Unconstr; end Types;
generic type Obj_Typ is private; procedure Maker (Count : Positive);
procedure Maker (Count : Positive) is procedure Create is Obj : Obj_Typ; pragma Warnings (Off, Obj); begin null; end Create;
begin for Iter in 1 .. Count loop Create; end loop; end Maker;
with Maker; with Types; use Types;
with Maker; with Types; use Types;
procedure Leaks is procedure Make_1 is new Maker (Rec_1); procedure Make_2 is new Maker (Rec_2); procedure Make_3 is new Maker (Rec_3); procedure Make_4 is new Maker (Rec_4); procedure Make_5 is new Maker (Rec_5); procedure Make_6 is new Maker (Rec_6);
begin Make_1 (5_000); Make_2 (5_000); Make_3 (5_000); Make_4 (5_000); Make_5 (5_000); Make_6 (5_000); end Leaks;
-- Compilation and output --
$ gnatmake -q leaks.adb $ valgrind ./leaks > leaks.txt 2>&1 $ grep -c "still reachable" leaks.txt 0
2018-01-11 Hristian Kirtchev
- exp_aggr.adb (Convert_Aggr_In_Object_Decl): Update the call to Establish_Transient_Scope. (Convert_To_Assignments): Update the call to Establish_Transient_Scope. (Expand_Array_Aggregate): Update the call to Establish_Transient_Scope.
- exp_ch6.adb (Expand_Call_Helper): Update the call to Establish_Transient_Scope. (Make_Build_In_Place_Call_In_Object_Declaration): Update the call to Establish_Transient_Scope.
- exp_ch7.adb (Establish_Transient_Scope): Restructured. Delegate the management of the secondary stack to an enclosing scope if there is no suitable construct to wrap, and the transient scope was intended to manage the secondary stack. (Find_Node_To_Be_Wrapped): Restructured. A case_statement_alternative is a valid boundary for a transient expression which comes from the statements of the alternative, otherwise alternatives cannot be wrapped. Assignments of controlled objects which have controlled actions suppressed now stop the traversal as there is no point in looking for an enclosing construct. Add several N_xxx_Body choices to the termination conditions for completeness.
- exp_ch7.ads (Establish_Transient_Scope): Update the parameter profile and the associated comment on usage.
- exp_smem.adb (Add_Shared_Var_Lock_Procs): Update the call to Establish_Transient_Scope. (Add_Write_After): Update the call to Establish_Transient_Scope.
- sem_res.adb (Check_Initialization_Call): Removed. (Resolve_Actuals): Account for additional cases where finalization actions are required by utilizing predicate Needs_Finalization rather than Is_Controlled. (Resolve_Call): Type initialization procedures can now utilize transient scopes to manage the secondary stack, thus preventing leaks during initialization. Remove the previous kludgy algorithm which attempts to manage the secondary stack at the object creation site.
2149b10c2bd [Ada] Secondary stack leaks during object initialization
gcc/ada/ChangeLog | 35 ++++
gcc/ada/exp_aggr.adb | 21 +--
gcc/ada/exp_ch6.adb | 10 +-
gcc/ada/exp_ch7.adb | 453 ++++++++++++++++++++++++++++++++-------------------
gcc/ada/exp_ch7.ads | 12 +-
gcc/ada/exp_smem.adb | 10 +-
gcc/ada/sem_res.adb | 156 +++---------------
7 files changed, 375 insertions(+), 322 deletions(-)