blob: 59470fdd0f15796d30df8ee47549c71c90bae6d5 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT COMPILER COMPONENTS --
-- --
-- V A S T --
-- --
-- B o d y --
-- --
-- Copyright (C) 2020-2025, Free Software Foundation, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 3, or (at your option) any later ver- --
-- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
-- for more details. You should have received a copy of the GNU General --
-- Public License distributed with GNAT; see file COPYING3. If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc. --
-- --
------------------------------------------------------------------------------
pragma Unsuppress (All_Checks);
pragma Assertion_Policy (Check);
-- Enable checking. This isn't really necessary, but it might come in handy if
-- we want to run VAST with a compiler built without checks. Anyway, it's
-- harmless, because VAST is not run by default.
with Ada.Unchecked_Deallocation;
with System.Case_Util;
with Atree; use Atree;
with Debug;
with Einfo.Entities; use Einfo.Entities;
with Lib; use Lib;
with Namet; use Namet;
with Nlists; use Nlists;
with Opt; use Opt;
with Output;
with Sinfo.Nodes; use Sinfo.Nodes;
with Sinput;
with Table;
with Types; use Types;
package body VAST is
-- ???Basic tree properties not yet checked:
-- - No dangling trees. Every node that is reachable at all is reachable
-- by some syntactic path.
-- - Basic properties of Nlists/Elists (next/prev pointers make sense,
-- for example).
Force_Enable_VAST : constant Boolean := False;
-- Normally, VAST is enabled by the the -gnatd_V switch.
-- To force it to be enabled independent of any switches,
-- set this to True.
type Check_Enum is
(Check_Other,
Check_Sloc,
Check_Analyzed,
Check_Error_Nodes,
Check_Sharing,
Check_Parent_Present,
Check_Parent_Correct);
type Check_Status is
-- Action in case of check failure:
(Disabled, -- Do nothing
Enabled, -- Print messages, and raise an exception
Print_And_Continue); -- Print a message
pragma Warnings (Off, "Status*could be declared constant");
Status : array (Check_Enum) of Check_Status :=
(Check_Other => Enabled,
Check_Sloc => Disabled,
Check_Analyzed => Disabled,
Check_Error_Nodes => Print_And_Continue,
Check_Sharing => Disabled,
Check_Parent_Present => Print_And_Continue,
Check_Parent_Correct => Disabled);
-- others => Print_And_Continue);
-- others => Enabled);
-- others => Disabled);
-- Passing checks are Check_Other, which should always be Enabled.
-- Currently-failing checks are different enumerals in Check_Enum,
-- which can be disabled individually until we fix the bugs, or enabled
-- when debugging particular bugs. Pass a nondefault Check_Enum to
-- Assert in order to deal with bugs we have not yet fixed,
-- and play around with the value of Status above for
-- testing and debugging.
--
-- Note: Once a bug is fixed, and the check passes reliably, we may choose
-- to remove that check from Check_Enum and use Check_Other instead.
type Node_Stack_Index is new Pos;
subtype Node_Stack_Count is
Node_Stack_Index'Base range 0 .. Node_Stack_Index'Last;
package Node_Stack is new Table.Table
(Table_Component_Type => Node_Id,
Table_Index_Type => Node_Stack_Index'Base,
Table_Low_Bound => 1,
Table_Initial => 1,
Table_Increment => 100,
Table_Name => "Node_Stack");
procedure Assert
(Condition : Boolean;
Check : Check_Enum := Check_Other;
Detail : String := "");
-- Check that the Condition is True. Status determines action on failure.
function To_Mixed (A : String) return String;
-- Copied from System.Case_Util; old versions of that package do not have
-- this function, so this is needed for bootstrapping.
function Image (Kind : Node_Kind) return String is (To_Mixed (Kind'Img));
function Image (Kind : Entity_Kind) return String is (To_Mixed (Kind'Img));
procedure Put (S : String);
procedure Put_Line (S : String);
procedure Put_Node (N : Node_Id);
procedure Put_Node_Stack;
-- Output routines; print only if -gnatd_W (VAST in verbose mode) is
-- enabled.
procedure Put_Indentation;
-- Print spaces to indicate nesting depth of Node_Stack
procedure Enter_Node (N : Node_Id);
procedure Leave_Node (N : Node_Id);
-- Called for each node while walking the tree.
-- Push/pop N to/from Node_Stack.
-- Print enter/leave debugging messages.
-- ???Possible improvements to messages:
-- Walk subtrees in a better order.
-- Print field names.
-- Don't print boring fields (such as N_Empty nodes).
-- Print more info (value of literals, "A.B.C" for expanded names, etc.).
-- Share some code with Treepr.
procedure Do_Tree (N : Node_Id);
-- Do VAST checking on a tree of nodes
function Has_Subtrees (N : Node_Id) return Boolean;
-- True if N has one or more syntactic fields
procedure Do_Subtrees (N : Node_Id);
-- Call Do_Tree on all the subtrees (i.e. syntactic fields) of N
procedure Do_List (L : List_Id);
-- Call Do_Tree on the list elements
procedure Do_Unit (U : Unit_Number_Type);
-- Call Do_Tree on the root node of a compilation unit
function Ancestor_Node (Count : Node_Stack_Count) return Node_Id;
-- Nth ancestor on the Node_Stack. Ancestor_Node(0) is the current node,
-- Ancestor_Node(1) is its parent, Ancestor_Node(2) is its grandparent,
-- and so on.
function Top_Node return Node_Id is (Ancestor_Node (0));
type Node_Set is array (Node_Id range <>) of Boolean;
pragma Pack (Node_Set);
type Node_Set_Ptr is access all Node_Set;
procedure Free is new Ada.Unchecked_Deallocation (Node_Set, Node_Set_Ptr);
Visited : Node_Set_Ptr;
-- Giant array of Booleans; Visited (N) is True if and only if we have
-- visited N in the tree walk. Used to detect incorrect sharing of subtrees
-- or (worse) cycles. We don't allocate the set on the stack, for fear of
-- Storage_Error.
function Get_Node_Field_Union is new
Atree.Atree_Private_Part.Get_32_Bit_Field (Union_Id) with Inline;
--------------
-- To_Mixed --
--------------
function To_Mixed (A : String) return String is
Result : String := A;
begin
System.Case_Util.To_Mixed (Result);
return Result;
end To_Mixed;
---------
-- Put --
---------
procedure Put (S : String) is
begin
if Debug.Debug_Flag_Underscore_WW then
Output.Write_Str (S);
end if;
end Put;
--------------
-- Put_Line --
--------------
procedure Put_Line (S : String) is
begin
if Debug.Debug_Flag_Underscore_WW then
Output.Write_Line (S);
end if;
end Put_Line;
--------------
-- Put_Node --
--------------
procedure Put_Node (N : Node_Id) is
begin
if Debug.Debug_Flag_Underscore_WW then
if Nkind (N) in N_Entity then
Put (Image (Ekind (N)));
else
Put (Image (Nkind (N)));
end if;
Put (N'Img & " ");
Sinput.Write_Location (Sloc (N));
if Comes_From_Source (N) then
Put (" (s)");
end if;
case Nkind (N) is
when N_Has_Chars =>
Put (" ");
Write_Name_For_Debug (Chars (N), Quote => """");
when others => null;
end case;
end if;
end Put_Node;
---------------------
-- Put_Indentation --
---------------------
procedure Put_Indentation is
begin
Put (String'(Natural (Node_Stack.First) ..
Natural (Node_Stack.Last) * 2 => ' '));
end Put_Indentation;
----------------
-- Enter_Node --
----------------
procedure Enter_Node (N : Node_Id) is
begin
Node_Stack.Append (N); -- push
if Has_Subtrees (N) then
Put ("-->");
else
-- If no subtrees, just print one line for enter/leave
Put (" ");
end if;
Put_Indentation;
Put_Node (N);
Put_Line ("");
end Enter_Node;
----------------
-- Leave_Node --
----------------
procedure Leave_Node (N : Node_Id) is
begin
if Has_Subtrees (N) then
Put ("<--");
Put_Indentation;
Put_Node (N);
Put_Line ("");
end if;
Node_Stack.Decrement_Last; -- pop
end Leave_Node;
--------------------
-- Put_Node_Stack --
--------------------
procedure Put_Node_Stack is
begin
for J in reverse Node_Stack.First .. Node_Stack.Last loop
Put_Node (Node_Stack.Table (J));
Put_Line ("");
end loop;
end Put_Node_Stack;
-------------------
-- Ancestor_Node --
-------------------
function Ancestor_Node (Count : Node_Stack_Count) return Node_Id is
begin
return Node_Stack.Table (Node_Stack.Last - Count);
end Ancestor_Node;
------------
-- Assert --
------------
VAST_Failure : exception;
procedure Assert
(Condition : Boolean;
Check : Check_Enum := Check_Other;
Detail : String := "")
is
begin
if not Condition then
declare
Part1 : constant String := "VAST fail";
Part2 : constant String :=
(if Check = Check_Other then ""
else ": " & To_Mixed (Check'Img));
Part3 : constant String :=
(if Detail = "" then "" else " -- " & Detail);
Message : constant String := Part1 & Part2 & Part3;
Save : constant Boolean := Debug.Debug_Flag_Underscore_WW;
begin
case Status (Check) is
when Disabled => null;
when Enabled | Print_And_Continue =>
Debug.Debug_Flag_Underscore_WW := True;
-- ???We should probably avoid changing the debug flag here
Put (Message & ": ");
Put_Node (Top_Node);
Put_Line ("");
if Status (Check) = Enabled then
Put_Node_Stack;
raise VAST_Failure with Message;
end if;
Debug.Debug_Flag_Underscore_WW := Save;
end case;
end;
end if;
end Assert;
-------------
-- Do_Tree --
-------------
procedure Do_Tree (N : Node_Id) is
begin
Enter_Node (N);
-- Skip the rest if empty. Check Sloc:
case Nkind (N) is
when N_Empty =>
Assert (No (Sloc (N)));
goto Done; -- -------------->
-- Don't do any further checks on Empty
-- ???Some nodes, including exception handlers, have no Sloc;
-- it's unclear why.
when N_Exception_Handler =>
Assert (if Comes_From_Source (N) then Present (Sloc (N)));
when others =>
Assert (Present (Sloc (N)), Check_Sloc);
end case;
-- All reachable nodes should have been analyzed by the time we get
-- here:
Assert (Analyzed (N), Check_Analyzed);
-- If we visit the same node more than once, then there are shared
-- nodes; the "tree" is not a tree:
Assert (not Visited (N), Check_Sharing);
Visited (N) := True;
-- Misc checks based on node/entity kind:
case Nkind (N) is
when N_Unused_At_Start | N_Unused_At_End =>
Assert (False);
when N_Error =>
-- VAST doesn't do anything when Serious_Errors_Detected > 0 (at
-- least for now), so we shouldn't encounter any N_Error nodes.
Assert (False, Check_Error_Nodes);
when N_Entity =>
case Ekind (N) is
when others =>
null; -- more to be done here
end case;
when others =>
null; -- more to be done here
end case;
-- Check that N has a Parent, except in certain cases:
case Nkind (N) is
when N_Empty =>
raise Program_Error; -- can't get here
when N_Error =>
Assert (False, Check_Error_Nodes);
-- The error node has no parent, but we shouldn't even be seeing
-- error nodes in VAST at all. See earlier "when N_Error".
when N_Compilation_Unit =>
Assert (No (Parent (N)));
-- The parent of the root of each unit is empty.
when N_Entity =>
if not Is_Itype (N) then
-- An Itype might or might not have a parent
Assert
(Present (Parent (N)), Detail => "missing parent of entity");
Assert (Parent (N) = Ancestor_Node (1), Check_Parent_Correct);
end if;
when others =>
Assert (Present (Parent (N)), Check_Parent_Present);
-- All other nodes should have a parent
if Status (Check_Parent_Present) = Enabled then
Assert (Parent (N) = Ancestor_Node (1), Check_Parent_Correct);
end if;
end case;
Do_Subtrees (N);
<<Done>>
Leave_Node (N);
end Do_Tree;
-----------------
-- Has_Subtrees --
-----------------
function Has_Subtrees (N : Node_Id) return Boolean is
Offsets : Traversed_Offset_Array renames
Traversed_Fields (Nkind (N));
begin
-- True if sentinel comes first
return Offsets (Offsets'First) /= No_Field_Offset;
end Has_Subtrees;
-----------------
-- Do_Subtrees --
-----------------
procedure Do_Subtrees (N : Node_Id) is
-- ???Do we need tail recursion elimination here,
-- as in Atree.Traverse_Func?
Offsets : Traversed_Offset_Array renames
Traversed_Fields (Nkind (N));
begin
for Cur_Field in Offset_Array_Index loop
exit when Offsets (Cur_Field) = No_Field_Offset;
declare
F : constant Union_Id :=
Get_Node_Field_Union (N, Offsets (Cur_Field));
begin
if F in Node_Range then
Do_Tree (Node_Id (F));
elsif F in List_Range then
Do_List (List_Id (F));
else
raise Program_Error;
end if;
end;
end loop;
end Do_Subtrees;
-------------
-- Do_List --
-------------
procedure Do_List (L : List_Id) is
Elmt : Node_Id := First (L);
Len : constant String := List_Length (L)'Img;
begin
if Is_Non_Empty_List (L) then
Put ("-->");
Put_Indentation;
Put_Line ("list len=" & Len);
while Present (Elmt) loop
Do_Tree (Elmt);
Next (Elmt);
end loop;
Put ("<--");
Put_Indentation;
Put_Line ("list len=" & Len);
end if;
end Do_List;
-------------
-- Do_Unit --
-------------
procedure Do_Unit (U : Unit_Number_Type) is
U_Name : constant Unit_Name_Type := Unit_Name (U);
U_Name_S : constant String :=
(if U_Name = No_Unit_Name then "<No_Unit_Name>"
else Get_Name_String (U_Name));
Predef : constant String :=
(if Is_Predefined_Unit (U) then " (predef)"
elsif Is_Internal_Unit (U) then " (gnat)"
else "");
Is_Main : constant String :=
(if U = Main_Unit then " (main unit)" else "");
Msg : constant String :=
"VAST for unit" & U'Img & " " & U_Name_S & Predef & Is_Main;
Is_Preprocessing_Dependency : constant Boolean :=
U_Name = No_Unit_Name;
-- True if this is a bogus unit added by Add_Preprocessing_Dependency.
-- ???Not sure what that's about, but these units have no name and
-- no associated tree, so we had better not try to walk those trees.
Root : constant Node_Id := Cunit (U);
begin
pragma Assert (Node_Stack.Last = 0);
Assert (No (Root) = Is_Preprocessing_Dependency);
-- All compilation units except these bogus ones should have a Cunit.
Put_Line (Msg);
if Is_Preprocessing_Dependency then
Put_Line ("Skipping preprocessing dependency");
return;
end if;
Assert (Present (Root));
Do_Tree (Root);
Put_Line (Msg & " (done)");
pragma Assert (Node_Stack.Last = 0);
end Do_Unit;
----------
-- VAST --
----------
procedure VAST is
pragma Assert (Expander_Active = (Operating_Mode = Generate_Code));
-- ???So why do we need both Operating_Mode and Expander_Active?
use Debug;
begin
-- Do nothing if we're not calling the back end; the main point of VAST
-- is to protect against code-generation bugs. This includes the
-- case where legality errors were detected; the tree is known to be
-- malformed in some error cases.
if Operating_Mode /= Generate_Code then
return;
end if;
-- If -gnatd_W (VAST in verbose mode) is enabled, then that should imply
-- -gnatd_V (enable VAST).
if Debug_Flag_Underscore_WW then
Debug_Flag_Underscore_VV := True;
end if;
-- Do nothing if VAST is disabled
if not (Debug_Flag_Underscore_VV or Force_Enable_VAST) then
return;
end if;
-- Turn off output unless verbose mode is enabled
Put_Line ("VAST");
-- Operating_Mode = Generate_Code implies there are no legality errors:
Assert (Serious_Errors_Detected = 0);
Put_Line ("VAST checking" & Last_Unit'Img & " units");
declare
use Atree_Private_Part;
Last_Node : constant Node_Id := Node_Offsets.Last;
begin
pragma Assert (Visited = null);
Visited := new Node_Set'(Node_Id'First .. Last_Node => False);
for U in Main_Unit .. Last_Unit loop
-- Main_Unit is the one passed to the back end, but here we are
-- walking all the units.
Do_Unit (U);
end loop;
-- We shouldn't have allocated any new nodes during VAST:
pragma Assert (Node_Offsets.Last = Last_Node);
Free (Visited);
end;
Put_Line ("VAST done.");
end VAST;
end VAST;