Signed-off-by: Jose E. Marchesi <[email protected]>
gcc/ChangeLog
* algol68/a68-low-ranges.cc: New file.
---
gcc/algol68/a68-low-ranges.cc | 697 ++++++++++++++++++++++++++++++++++
1 file changed, 697 insertions(+)
create mode 100644 gcc/algol68/a68-low-ranges.cc
diff --git a/gcc/algol68/a68-low-ranges.cc b/gcc/algol68/a68-low-ranges.cc
new file mode 100644
index 00000000000..8b9a802b249
--- /dev/null
+++ b/gcc/algol68/a68-low-ranges.cc
@@ -0,0 +1,697 @@
+/* Management of ranges in the Algol 68 front-end.
+ Copyright (C) 2025 Jose E. Marchesi.
+
+ Written by Jose E. Marchesi.
+
+ GCC is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GCC is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GCC; see the file COPYING3. If not see
+ <http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+
+#include "tree.h"
+#include "fold-const.h"
+#include "langhooks.h"
+#include "tm.h"
+#include "function.h"
+#include "cgraph.h"
+#include "toplev.h"
+#include "varasm.h"
+#include "predict.h"
+#include "stor-layout.h"
+#include "tree-iterator.h"
+#include "stringpool.h"
+#include "print-tree.h"
+#include "gimplify.h"
+#include "dumpfile.h"
+#include "convert.h"
+#include "tree-nested.h"
+
+#include "a68.h"
+
+/* Many Algol 68 constructions introduce a new range of definitions. This is
+ the case of clauses and of routine definitions. The stack of ranges at any
+ point in the program determines the "nest" of the constructions declared in
+ the program. This nest carries a record of all the declarations forming the
+ environment in which that construct is to be interpreted.
+
+ This file contains a manager of ranges of which we allocate one for each
+ range inducing construct. The top-level range corresponds to the primal
+ environment.
+
+ The ranges are used by the lowering code in order to create GCC tree BLOCK
+ nodes, and also to keep track of the set of declarations and of statements
+ being added by the current serial clause. */
+
+struct GTY (()) range
+{
+ /* Whether this range entry doesn't introduce a lexical frame. Declarations
+ and decl_exprs get added to the nearst enclosing range that is not
+ frameless. */
+ bool frameless;
+
+ /* A chain of _DECL nodes for all variables, constants, functions,
+ and typedef types. These are in the reverse of the order supplied. */
+ tree names;
+
+ /* A statements list of DECL_EXPR nodes for all the declarations in the
+ range. These are prepended to the statements list when the range is
+ closed. */
+ tree decl_exprs;
+
+ /* The context of the range, either a function declaration or a translation
+ unit. */
+ tree context;
+
+ /* The range below this one. */
+ struct range *next;
+
+ /* Statement list. */
+ tree stmt_list;
+
+ /* List of blocks to which the block created for this range is the
+ superblock. */
+ tree blocks;
+
+ /* Mode associated with the range. For serial clauses, this is the mode of
+ the value yielded by the clause. */
+ MOID_T *mode;
+
+ /* If not TREE_NULL, then the range corresponds to a function, which can be
+ either nested or defined at top-level. */
+ tree fndecl;
+ bool top_level_function;
+
+ /* The following fields are used by ranges introduced by serial
+ clauses. */
+ bool save_restore_stack;
+ bool has_completers;
+ tree clause_result_decl;
+ tree clause_exit_label_decl;
+ tree clause_stack_save_decl;
+};
+
+/* Global and current ranges. */
+
+static GTY (()) struct range *global_range;
+static GTY (()) struct range *current_range;
+
+/* Create a new range and push it in the list. */
+
+static struct range *
+new_range (void)
+{
+ struct range *range = ggc_alloc<struct range> ();
+
+ range->frameless = false;
+ range->names = NULL;
+ range->decl_exprs = alloc_stmt_list ();
+ range->context = NULL;
+ range->next = NULL;
+ range->blocks = NULL_TREE;
+ range->stmt_list = alloc_stmt_list ();
+ range->fndecl = NULL_TREE;
+ range->top_level_function = false;
+ range->save_restore_stack = false;
+ range->has_completers = false;
+ range->clause_result_decl = NULL_TREE;
+ range->clause_exit_label_decl = NULL_TREE;
+ range->clause_stack_save_decl = NULL_TREE;
+ range->mode = NO_MOID;
+ return range;
+}
+
+/* Push a new frameless range. */
+
+void
+a68_push_stmt_list (MOID_T *mode)
+{
+ a68_push_range (mode);
+ current_range->frameless = true;
+}
+
+/* Pop a frameless range. */
+
+tree
+a68_pop_stmt_list (void)
+{
+ /* This will result into a stmt list. */
+ tree res = a68_pop_range ();
+ gcc_assert (TREE_CODE (res) == STATEMENT_LIST);
+ return res;
+}
+
+/* Push a new range. */
+
+void
+a68_push_range (MOID_T *mode)
+{
+ struct range *range = new_range ();
+ if (current_range)
+ range->context = current_range->context;
+ range->next = current_range;
+ range->mode = mode;
+ current_range = range;
+}
+
+/* Pop a range, with a finalizer.
+
+ Return a BIND_EXPR, a statement list or a TRY_FINALLY_EXPR. */
+
+tree
+a68_pop_range_with_finalizer (tree finalizer)
+{
+ tree range = a68_pop_range ();
+ return fold_build2 (TRY_FINALLY_EXPR, TREE_TYPE (range),
+ range, finalizer);
+}
+
+/* Pop a range. Return either a BIND_EXPR or a statements list. */
+
+tree
+a68_pop_range (void)
+{
+ struct range *range = current_range;
+ current_range = range->next;
+ tree type = (range->mode == NULL ? void_type_node : CTYPE (range->mode));
+
+ /* If TYPE is a pointer type and the last expression in the statement list is
+ a variable of the type pointed by TYPE then take its address. */
+ tree_stmt_iterator i = tsi_last (range->stmt_list);
+ if (POINTER_TYPE_P (type) && TREE_TYPE (type) == TREE_TYPE (tsi_stmt (i)))
+ {
+ append_to_statement_list_force (a68_consolidate_ref (range->mode,
tsi_stmt (i)),
+ &range->stmt_list);
+ tsi_delink (&i);
+ }
+
+ tree clause = NULL_TREE;
+ if (range->frameless)
+ clause = range->stmt_list;
+ else
+ {
+ /* Create a block and set its declarations and supercontext. */
+ tree block = make_node (BLOCK);
+ BLOCK_VARS (block) = range->names;
+ BLOCK_SUBBLOCKS (block) = range->blocks;
+
+ /* In each subblock, record that this is its superior. */
+ for (tree t = range->blocks; t; t = BLOCK_CHAIN (t))
+ BLOCK_SUPERCONTEXT (t) = block;
+
+ if (range->fndecl)
+ {
+ BLOCK_SUPERCONTEXT (block) = range->fndecl;
+ DECL_INITIAL (range->fndecl) = block;
+ }
+ else
+ {
+ current_range->blocks
+ = block_chainon (current_range->blocks, block);
+ }
+
+ TREE_USED (block) = true;
+
+ /* Create a BIND if the range contains declarations. Otherwise just
+ use the statements list. */
+ clause = range->stmt_list;
+ if (range->names != NULL_TREE)
+ {
+ clause = build3 (BIND_EXPR,
+ type,
+ range->names,
+ range->stmt_list,
+ block);
+ TREE_SIDE_EFFECTS (clause) = 1;
+ BIND_EXPR_VARS (clause) = BLOCK_VARS (block);
+ }
+
+ /* Prepend the decl_exprs to the range's statements list. */
+ tree_stmt_iterator q = tsi_start (range->stmt_list);
+ tsi_link_before (&q, range->decl_exprs, TSI_SAME_STMT);
+ }
+
+ /* Set the type of the stmt_list. */
+ TREE_TYPE (range->stmt_list) = type;
+ TREE_SIDE_EFFECTS (range->stmt_list) = 1;
+
+ return clause;
+}
+
+/* Add a new expression to the current range. */
+
+void
+a68_add_stmt (tree exp)
+{
+ if (exp == void_node)
+ /* This may result from a mode declaration. */
+ return;
+ gcc_assert (current_range != NULL);
+ append_to_statement_list_force (exp,
+ ¤t_range->stmt_list);
+}
+
+/* Add a new declaration to the current range. */
+
+void
+a68_add_decl (tree decl)
+{
+ gcc_assert (current_range != NULL);
+ struct range *range = current_range;
+
+ /* Search for the right frame where to add the declaration. */
+ while (range->frameless)
+ {
+ gcc_assert (range->next != NULL);
+ range = range->next;
+ }
+
+ tree n = range->names;
+ while (n != decl && n != NULL)
+ n = TREE_CHAIN (n);
+ if (n != decl)
+ {
+ if (decl != current_function_decl)
+ DECL_CONTEXT (decl) = range->context;
+ /* Note this list needs to be in reverse order for compatibility with
+ GCC. */
+ TREE_CHAIN (decl) = range->names;
+ range->names = decl;
+ }
+}
+
+/* Add a new declaration expr to the current range. */
+
+void
+a68_add_decl_expr (tree decl_expr)
+{
+ gcc_assert (current_range != NULL);
+ struct range *range = current_range;
+
+ /* Search for the right frame where to add the declaration expr. */
+ while (range->frameless)
+ {
+ gcc_assert (range->next != NULL);
+ range = range->next;
+ }
+
+ append_to_statement_list_force (decl_expr, &range->decl_exprs);
+}
+
+/* Add a completer in the current range. */
+
+void
+a68_add_completer (void)
+{
+ struct range *range = current_range;
+
+ /* The last statement in the statements list is either a single unit or a
+ labeled unit, i.e a COMPOUND_EXPR whose first expression is a label and
+ second expression is the unit. Consolidate the unit within the labeled
+ unit to a ref. */
+ tree_stmt_iterator i = tsi_last (range->stmt_list);
+ tree last_expr = tsi_stmt (i);
+
+ if (TREE_CODE (last_expr) == COMPOUND_EXPR
+ && TREE_CODE (TREE_OPERAND (last_expr, 0)) == LABEL_EXPR)
+ {
+ TREE_OPERAND (last_expr, 1) = a68_consolidate_ref (range->mode,
+ TREE_OPERAND
(last_expr, 1));
+ TREE_TYPE (last_expr) = TREE_TYPE (TREE_OPERAND (last_expr, 1));
+ }
+ else
+ last_expr = a68_consolidate_ref (range->mode, last_expr);
+
+ /* Now assign the labeled unit to the clause result decl then jump to the end
+ of the serial clause. */
+ append_to_statement_list_force (fold_build2 (MODIFY_EXPR,
+ void_type_node,
+ range->clause_result_decl,
+ last_expr),
+ &range->stmt_list);
+ tsi_delink (&i);
+ append_to_statement_list_force (fold_build1 (GOTO_EXPR, void_type_node,
+ range->clause_exit_label_decl),
+ &range->stmt_list);
+ range->has_completers = true;
+}
+
+/* Get the context of the current range. */
+
+tree
+a68_range_context (void)
+{
+ gcc_assert (current_range != NULL);
+ return current_range->context;
+}
+
+/* Get the list of declarations in the current range. */
+
+tree
+a68_range_names (void)
+{
+ struct range *range = current_range;
+
+ while (range->frameless && range->next != NULL)
+ range = range->next;
+
+ if (range != NULL)
+ return range->names;
+ else
+ return NULL_TREE;
+}
+
+/* Get the statements list of the current range. */
+
+tree
+a68_range_stmt_list (void)
+{
+ gcc_assert (current_range != NULL);
+ return current_range->stmt_list;
+}
+
+/* Push a range for a function. */
+
+void
+a68_push_function_range (tree fndecl, tree result_type,
+ bool top_level)
+{
+ a68_push_range (NULL /* VOID */);
+ current_range->fndecl = fndecl;
+ current_range->top_level_function = top_level;
+ current_range->context = fndecl;
+
+ /* Setup the result declaration. */
+ tree resdecl = build_decl (UNKNOWN_LOCATION,
+ RESULT_DECL,
+ get_identifier ("resdecl%"),
+ result_type);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = fndecl;
+ DECL_RESULT (fndecl) = resdecl;
+ rest_of_decl_compilation (fndecl, 1, 0);
+ make_decl_rtl (fndecl);
+ allocate_struct_function (fndecl, false);
+
+ /* Let GCC know the current scope is this function. */
+ current_function_decl = fndecl;
+}
+
+/* Pop a range for a function. */
+
+void
+a68_pop_function_range (tree body)
+{
+ tree fndecl = current_range->fndecl;
+ bool top_level = current_range->top_level_function;
+
+ if (TREE_TYPE (DECL_RESULT (fndecl)) == void_type_node)
+ {
+ a68_add_stmt (body);
+ }
+ else
+ {
+ /* Append the return statement.
+ Note that this does the copy of the returned value. */
+ tree return_stmt = fold_build1 (RETURN_EXPR,
+ void_type_node,
+ fold_build2 (MODIFY_EXPR,
+ TREE_TYPE (DECL_RESULT
(fndecl)),
+ DECL_RESULT (fndecl),
+ a68_low_dup (body, true /*
use_heap */)));
+ a68_add_stmt (return_stmt);
+ }
+
+ /* Set the body of the function. */
+ DECL_SAVED_TREE (fndecl) = a68_pop_range ();
+
+ /* Output the GENERIC tree for the function.. */
+ dump_function (TDI_original, fndecl);
+ /* This compiles the function all the way to assembler language output.
+ Nested functions are finalized when the containing top-level function is
+ finalized. */
+ if (top_level || a68_in_global_range ())
+ cgraph_node::finalize_function (fndecl, true);
+ else
+ /* Register this function with cgraph just far enough to get it added to
+ our parent's nested function list. */
+ (void) cgraph_node::get_create (fndecl);
+
+ /* Let GCC know the current scope has changed. */
+ current_function_decl = NULL_TREE;
+ for (struct range *r = current_range; r; r = r->next)
+ {
+ if (r->fndecl != NULL_TREE)
+ current_function_decl = r->fndecl;
+ }
+}
+
+/* Push a range for a serial clause.
+
+ label1: BIND_EXPR_BODY (STATEMENT_LIST (
+ expr1; label1,
+ expr2 expr1,
+ exit label2: clause_result = expr2,
+ expr3; goto exit_label,
+ expr4 label2,
+ exit label3: expr3,
+ expr5 clause_result = expr4,
+ goto exit_label,
+ label3,
+ clause_result = expr5,
+ exit_label,
+ clause_result)) */
+
+void
+a68_push_serial_clause_range (MOID_T *clause_mode,
+ bool save_restore_stack)
+{
+ /* Get the type of the enclosing clause. */
+ tree clause_type = CTYPE (clause_mode);
+
+ /* If the serial clause has declarations that involve dynamic allocation, and
+ the environ it establishes is local, then save the stack pointer. */
+ if (save_restore_stack)
+ {
+ a68_push_range (clause_mode);
+ current_range->save_restore_stack = true;
+
+ tree outer_clause_result_decl = build_decl (UNKNOWN_LOCATION,
+ VAR_DECL,
+ NULL, /* Set below. */
+ clause_type);
+ char *outer_clause_result_name = xasprintf ("outer_clause_result%d%%",
+ DECL_UID
(outer_clause_result_decl));
+ DECL_NAME (outer_clause_result_decl) = get_identifier
(outer_clause_result_name);
+ free (outer_clause_result_name);
+ current_range->clause_result_decl = outer_clause_result_decl;
+ a68_add_decl (outer_clause_result_decl);
+
+ /* Variable used to save the stack pointer. */
+ tree stack_save_decl = build_decl (UNKNOWN_LOCATION,
+ VAR_DECL,
+ get_identifier ("stack_save%"),
+ build_pointer_type (char_type_node));
+ current_range->clause_stack_save_decl = stack_save_decl;
+ a68_add_decl (stack_save_decl);
+ a68_add_stmt (fold_build1 (DECL_EXPR,
+ TREE_TYPE (stack_save_decl),
+ stack_save_decl));
+
+ /* Save stack pointer. */
+ tree call = builtin_decl_implicit (BUILT_IN_STACK_SAVE);
+ call = build_call_expr_loc (UNKNOWN_LOCATION, call, 0);
+ a68_add_stmt (fold_build2 (MODIFY_EXPR, void_type_node,
+ stack_save_decl, call));
+ }
+
+ /* Push a new range. */
+ a68_push_range (clause_mode);
+ current_range->save_restore_stack = save_restore_stack;
+
+ /* Create a decl for clause_result with the right type and add it to the
+ block's declaration list. */
+ tree clause_result_decl = build_decl (UNKNOWN_LOCATION,
+ VAR_DECL,
+ NULL, /* Set below. */
+ clause_type);
+ char *clause_result_name = xasprintf ("clause_result%d%%", DECL_UID
(clause_result_decl));
+ DECL_NAME (clause_result_decl) = get_identifier (clause_result_name);
+ free (clause_result_name);
+ DECL_INITIAL (clause_result_decl) = a68_get_skip_tree (clause_mode);
+ DECL_CONTEXT (clause_result_decl) = current_range->context;
+ current_range->clause_result_decl = clause_result_decl;
+
+ /* Create a decl for the clause's exit label. */
+ tree clause_exit_label_decl = build_decl (UNKNOWN_LOCATION,
+ LABEL_DECL,
+ NULL, /* Set below. */
+ void_type_node);
+ char *exit_label_name = xasprintf ("clause_exit_label%d%%", DECL_UID
(clause_exit_label_decl));
+ DECL_NAME (clause_exit_label_decl) = get_identifier (exit_label_name);
+ free (exit_label_name);
+ DECL_CONTEXT (clause_exit_label_decl) = current_range->context;
+ current_range->clause_exit_label_decl = clause_exit_label_decl;
+}
+
+/* Pop a range for a serial clause and return the resulting bind
+ expression. */
+
+tree
+a68_pop_serial_clause_range (void)
+{
+ struct range *range = current_range;
+ MOID_T *clause_mode = range->mode;
+ tree clause_type = CTYPE (clause_mode);
+
+ /* The last expression in the statements list is either a single unit or a
+ labeled unit. Consolidate it to a ref if required by the mode of the
+ serial clause. */
+ {
+ tree_stmt_iterator si = tsi_last (range->stmt_list);
+ tree last_expr = tsi_stmt (si);
+ if (TREE_CODE (last_expr) == COMPOUND_EXPR
+ && TREE_CODE (TREE_OPERAND (last_expr, 0)) == LABEL_EXPR)
+ {
+ TREE_OPERAND (last_expr, 1) = a68_consolidate_ref (range->mode,
+ TREE_OPERAND
(last_expr, 1));
+ TREE_TYPE (last_expr) = TREE_TYPE (TREE_OPERAND (last_expr, 1));
+ }
+ else
+ last_expr = a68_consolidate_ref (range->mode, last_expr);
+ a68_add_stmt (last_expr);
+ tsi_delink (&si);
+ }
+
+ /* If the serial clause has completers, we have to make use of the
+ clause_result% and clause_exit_label% mechanism to assure the statements
+ list has a single exit at the end. */
+ if (range->has_completers)
+ {
+ /* First prepend EXPR_DECL expressions for clause_result% and
+ clause_exit_label% */
+ {
+ tree_stmt_iterator si = tsi_start (range->stmt_list);
+ tsi_link_before (&si,
+ fold_build1 (DECL_EXPR,
+ TREE_TYPE (range->clause_result_decl),
+ range->clause_result_decl),
+ TSI_CONTINUE_LINKING);
+ tsi_link_before (&si,
+ fold_build1 (DECL_EXPR,
+ TREE_TYPE (range->clause_exit_label_decl),
+ range->clause_exit_label_decl),
+ TSI_CONTINUE_LINKING);
+ }
+
+ /* Then turn the last expression in stmt_list to an assignment to
+ clause_result_decl%, but don't bother if it has been voided. */
+ if (clause_type != a68_void_type)
+ {
+ tree_stmt_iterator si = tsi_last (range->stmt_list);
+ tree last_expr = tsi_stmt (si);
+
+ a68_add_stmt (build2 (MODIFY_EXPR,
+ clause_type,
+ range->clause_result_decl,
+ last_expr));
+ tsi_delink (&si);
+ }
+
+ a68_add_decl (range->clause_result_decl);
+ a68_add_decl (range->clause_exit_label_decl);
+
+ /* Finally append the exit label and last expression with
+ result_decl. */
+ a68_add_stmt (build1 (LABEL_EXPR, void_type_node,
range->clause_exit_label_decl));
+ a68_add_stmt (build1 (NON_LVALUE_EXPR, clause_type,
range->clause_result_decl));
+ }
+
+ /* Check that the type of the last statement in the statements list is the
+ same than the type corresponding to the clause mode. */
+ {
+ tree_stmt_iterator si = tsi_last (range->stmt_list);
+ if (TREE_TYPE (tsi_stmt (si)) != clause_type
+ /* But NIL can appear in a context expecting VOID with no widening. */
+ && !(clause_type == a68_void_type
+ && POINTER_TYPE_P (TREE_TYPE (tsi_stmt (si)))
+ && TREE_CODE (tsi_stmt (si)) == INTEGER_CST
+ && tree_to_shwi (tsi_stmt (si)) == 0)
+ /* And any row type is valid when M_ROWS is expected. */
+ && !(A68_ROWS_TYPE_P (clause_type)
+ && A68_ROWS_TYPE_P (TREE_TYPE (tsi_stmt (si))))
+ /* Do not rely on comparing pointer types, as the equality fails in
+ that case. We need a better way of comparing types, either using
+ TYPE_CANONICAL or caching. */
+ && !(POINTER_TYPE_P (TREE_TYPE (tsi_stmt (si))) && POINTER_TYPE_P
(clause_type)))
+ {
+ printf ("last statement:\n");
+ debug_tree (tsi_stmt (si));
+ printf ("expected type:\n");
+ debug_tree (clause_type);
+ gcc_unreachable ();
+ }
+ }
+
+ /* If the serial clause has declarations that involve dynamic allocation, and
+ the environ it establishes is local, then restore the stack pointer. */
+ if (range->save_restore_stack)
+ {
+ /* Turn last expression of inner clause into a modify statement. This
+ may involve a copy. This can be omitted if the serial clause yields
+ void. */
+ if (clause_type != a68_void_type)
+ {
+ tree_stmt_iterator si = tsi_last (range->stmt_list);
+ tree last_expr = tsi_stmt (si);
+
+ a68_add_stmt (build2 (MODIFY_EXPR,
+ clause_type,
+ range->next->clause_result_decl,
+ a68_low_dup (last_expr)));
+ tsi_delink (&si);
+ }
+
+ /* Finish inner clause, restoring stack pointer on finalizing. */
+ tree restore_sp = builtin_decl_implicit (BUILT_IN_STACK_RESTORE);
+ restore_sp = build_call_expr_loc (UNKNOWN_LOCATION, restore_sp, 1,
+
current_range->next->clause_stack_save_decl);
+ a68_add_stmt (a68_pop_range_with_finalizer (restore_sp));
+ /* The result value is now in clause_result_decl. */
+ a68_add_stmt (build1 (NON_LVALUE_EXPR, clause_type,
+ current_range->clause_result_decl));
+ }
+
+ return a68_pop_range ();
+}
+
+/* Whether the current range is the global range. */
+
+bool
+a68_in_global_range (void)
+{
+ return current_range == global_range;
+}
+
+/* Initialize ranges. */
+
+void
+a68_init_ranges (void)
+{
+ global_range = new_range ();
+ global_range->context = build_translation_unit_decl (NULL);
+ current_range = global_range;
+}
--
2.30.2