[PATCH 2/2] fortran: honour ALIGN in ALLOCATE and add ALLOCATE ALIGN directive

Magnus Weinmueller <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.fortran
Message-ID <[email protected]>
Make ALLOCATE honour an alignment request by calling aligned_alloc
instead of malloc, rounding the size up to a multiple of the alignment
as C11 requires.  The request may come from !GCC$ ATTRIBUTES ALIGN(n)
on the allocate-object or from the new per-statement directive

  !GCC$ ALLOCATE (var-list) ALIGN(n)

which follows the !GCC$ UNROLL pattern and applies to the immediately
following ALLOCATE statement only.  Where both apply to the same
object, the larger alignment wins.

gcc/fortran/ChangeLog:

	* gfortran.h (struct gfc_alloc): Add align field.
	(struct gfc_gcc_alloc_align): New.
	(directive_allocate_align): Declare.
	(gfc_free_gcc_alloc_align): Declare.
	* decl.cc (directive_allocate_align): Define.
	(gfc_free_gcc_alloc_align): New function.
	(gfc_match_gcc_allocate): New function.
	* match.h (gfc_match_gcc_allocate): Declare.
	* parse.cc (decode_gcc_attribute): Recognise the ALLOCATE
	directive.
	(parse_executable): Diagnose a directive that is not followed by
	an ALLOCATE statement.
	* match.cc (gfc_match_allocate): Apply a pending directive to the
	matching allocate-objects.
	* f95-lang.cc (gfc_init_builtin_functions): Declare aligned_alloc.
	* trans.h (gfc_allocate_using_malloc): Add alignment argument.
	(gfc_allocate_allocatable): Likewise.
	* trans.cc (gfc_allocate_using_malloc): Use aligned_alloc when an
	alignment is requested.
	(gfc_allocate_allocatable): Pass the alignment through.
	* trans-array.h (gfc_array_allocate): Add alignment argument.
	* trans-array.cc (gfc_array_allocate): Pass the alignment through.
	* trans-stmt.cc (gfc_trans_allocate): Compute the requested
	alignment per allocate-object.

gcc/testsuite/ChangeLog:

	* gfortran.dg/align_3.f90: New test.
	* gfortran.dg/align_4.f90: New test.
---
 gcc/fortran/decl.cc                   | 90 +++++++++++++++++++++++++++
 gcc/fortran/f95-lang.cc               |  5 ++
 gcc/fortran/gfortran.h                | 13 ++++
 gcc/fortran/match.cc                  | 22 +++++++
 gcc/fortran/match.h                   |  1 +
 gcc/fortran/parse.cc                  |  8 +++
 gcc/fortran/trans-array.cc            |  9 ++-
 gcc/fortran/trans-array.h             |  3 +-
 gcc/fortran/trans-stmt.cc             | 31 ++++++++-
 gcc/fortran/trans.cc                  | 32 ++++++++--
 gcc/fortran/trans.h                   |  4 +-
 gcc/testsuite/gfortran.dg/align_3.f90 | 41 ++++++++++++
 gcc/testsuite/gfortran.dg/align_4.f90 | 23 +++++++
 13 files changed, 268 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/align_3.f90
 create mode 100644 gcc/testsuite/gfortran.dg/align_4.f90

diff --git a/gcc/fortran/decl.cc b/gcc/fortran/decl.cc
index c6472cc14..efe6c2837 100644
--- a/gcc/fortran/decl.cc
+++ b/gcc/fortran/decl.cc
@@ -13023,6 +13023,96 @@ gfc_match_gcc_unroll (void)
   return MATCH_ERROR;
 }
 
+gfc_gcc_alloc_align *directive_allocate_align = NULL;
+
+void
+gfc_free_gcc_alloc_align (void)
+{
+  gfc_gcc_alloc_align *p, *q;
+  for (p = directive_allocate_align; p; p = q)
+    {
+      q = p->next;
+      free (p);
+    }
+  directive_allocate_align = NULL;
+}
+
+/* Match a !GCC$ ALLOCATE statement of the form:
+      !GCC$ ALLOCATE (var [, var]...) ALIGN(n)
+
+   The named objects are aligned to n bytes by the ALLOCATE that follows.
+   n must be a positive power of two.  */
+match
+gfc_match_gcc_allocate (void)
+{
+  char name[GFC_MAX_SYMBOL_LEN + 1];
+  gfc_gcc_alloc_align *head = NULL, *tail = NULL;
+  int align;
+
+  /* Drop a previous directive that was never consumed.  */
+  gfc_free_gcc_alloc_align ();
+
+  if (gfc_match_char ('(') != MATCH_YES)
+    goto syntax;
+
+  for (;;)
+    {
+      if (gfc_match_name (name) != MATCH_YES)
+	goto syntax;
+
+      gfc_gcc_alloc_align *n = XCNEW (gfc_gcc_alloc_align);
+      strcpy (n->name, name);
+      if (head == NULL)
+	head = tail = n;
+      else
+	{
+	  tail->next = n;
+	  tail = n;
+	}
+
+      if (gfc_match_char (',') == MATCH_YES)
+	continue;
+      if (gfc_match_char (')') == MATCH_YES)
+	break;
+      goto syntax;
+    }
+
+  if (gfc_match (" align (") != MATCH_YES)
+    goto syntax;
+  if (gfc_match_small_int (&align) != MATCH_YES)
+    goto syntax;
+  if (gfc_match_char (')') != MATCH_YES)
+    goto syntax;
+  if (gfc_match_eos () != MATCH_YES)
+    goto syntax;
+
+  if (align < 1 || (align & (align - 1)) != 0)
+    {
+      gfc_error ("ALIGN value (%d) in !GCC$ ALLOCATE directive at %C must be "
+		 "a positive power of two", align);
+      goto cleanup;
+    }
+
+  for (tail = head; tail; tail = tail->next)
+    tail->align = (unsigned) align;
+
+  directive_allocate_align = head;
+  return MATCH_YES;
+
+syntax:
+  gfc_error ("Syntax error in !GCC$ ALLOCATE directive at %C");
+cleanup:
+  {
+    gfc_gcc_alloc_align *p, *q;
+    for (p = head; p; p = q)
+      {
+	q = p->next;
+	free (p);
+      }
+  }
+  return MATCH_ERROR;
+}
+
 /* Match a !GCC$ builtin (b) attributes simd flags if('target') form:
 
    The parameter b is name of a middle-end built-in.
diff --git a/gcc/fortran/f95-lang.cc b/gcc/fortran/f95-lang.cc
index 45aab3486..1335278b9 100644
--- a/gcc/fortran/f95-lang.cc
+++ b/gcc/fortran/f95-lang.cc
@@ -1031,6 +1031,11 @@ gfc_init_builtin_functions (void)
 		      "calloc", ATTR_NOTHROW_LEAF_MALLOC_LIST);
   DECL_IS_MALLOC (builtin_decl_explicit (BUILT_IN_CALLOC)) = 1;
 
+  gfc_define_builtin ("__builtin_aligned_alloc", ftype,
+		      BUILT_IN_ALIGNED_ALLOC,
+		      "aligned_alloc", ATTR_NOTHROW_LEAF_MALLOC_LIST);
+  DECL_IS_MALLOC (builtin_decl_explicit (BUILT_IN_ALIGNED_ALLOC)) = 1;
+
   ftype = build_function_type_list (pvoid_type_node, pvoid_type_node,
 				    size_type_node, NULL_TREE);
   gfc_define_builtin ("__builtin_realloc", ftype, BUILT_IN_REALLOC,
diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
index 561d73e04..10f407846 100644
--- a/gcc/fortran/gfortran.h
+++ b/gcc/fortran/gfortran.h
@@ -3045,6 +3045,8 @@ gfc_iterator;
 typedef struct gfc_alloc
 {
   gfc_expr *expr;
+  /* From !GCC$ ALLOCATE ALIGN(n), in bytes; 0 if none.  */
+  unsigned align;
   struct gfc_alloc *next;
 }
 gfc_alloc;
@@ -3466,6 +3468,17 @@ extern bool directive_ivdep;
 extern bool directive_vector;
 extern bool directive_novector;
 
+/* Set by !GCC$ ALLOCATE ALIGN(n), used by the next ALLOCATE.  */
+typedef struct gfc_gcc_alloc_align
+{
+  char name[GFC_MAX_SYMBOL_LEN + 1];
+  unsigned align;
+  struct gfc_gcc_alloc_align *next;
+}
+gfc_gcc_alloc_align;
+extern gfc_gcc_alloc_align *directive_allocate_align;
+void gfc_free_gcc_alloc_align (void);
+
 /* SIMD clause enum.  */
 enum gfc_simd_clause
 {
diff --git a/gcc/fortran/match.cc b/gcc/fortran/match.cc
index 62bdb8687..8b1d9c86c 100644
--- a/gcc/fortran/match.cc
+++ b/gcc/fortran/match.cc
@@ -5416,6 +5416,28 @@ alloc_opt_list:
   new_st.ext.alloc.list = head;
   new_st.ext.alloc.ts = ts;
 
+  /* Apply a preceding !GCC$ ALLOCATE ALIGN directive.  */
+  if (directive_allocate_align)
+    {
+      gfc_gcc_alloc_align *d;
+      for (d = directive_allocate_align; d; d = d->next)
+	{
+	  gfc_alloc *a;
+	  bool found = false;
+	  for (a = head; a; a = a->next)
+	    if (a->expr->symtree
+		&& strcmp (a->expr->symtree->n.sym->name, d->name) == 0)
+	      {
+		a->align = d->align;
+		found = true;
+	      }
+	  if (!found)
+	    gfc_warning (0, "Object %qs in !GCC$ ALLOCATE ALIGN directive does "
+			 "not appear in the ALLOCATE statement at %C", d->name);
+	}
+      gfc_free_gcc_alloc_align ();
+    }
+
   if (type_param_spec_list)
     gfc_free_actual_arglist (type_param_spec_list);
 
diff --git a/gcc/fortran/match.h b/gcc/fortran/match.h
index 52cb2f0cd..0b4d2632d 100644
--- a/gcc/fortran/match.h
+++ b/gcc/fortran/match.h
@@ -278,6 +278,7 @@ match gfc_match_contiguous (void);
 match gfc_match_dimension (void);
 match gfc_match_external (void);
 match gfc_match_gcc_attributes (void);
+match gfc_match_gcc_allocate (void);
 match gfc_match_gcc_builtin (void);
 match gfc_match_gcc_ivdep (void);
 match gfc_match_gcc_novector (void);
diff --git a/gcc/fortran/parse.cc b/gcc/fortran/parse.cc
index a41bf090c..fca0e850a 100644
--- a/gcc/fortran/parse.cc
+++ b/gcc/fortran/parse.cc
@@ -1483,6 +1483,7 @@ decode_gcc_attribute (void)
   old_locus = gfc_current_locus;
 
   match ("attributes", gfc_match_gcc_attributes, ST_ATTR_DECL);
+  match ("allocate", gfc_match_gcc_allocate, ST_NONE);
   match ("unroll", gfc_match_gcc_unroll, ST_NONE);
   match ("builtin", gfc_match_gcc_builtin, ST_NONE);
   match ("ivdep", gfc_match_gcc_ivdep, ST_NONE);
@@ -6848,6 +6849,13 @@ parse_executable (gfc_statement st)
 	gfc_error ("%<GCC novector%> "
 		   "directive not at the start of a loop at %C");
 
+      if (directive_allocate_align)
+	{
+	  gfc_error ("%<GCC allocate%> directive not immediately before an "
+		     "ALLOCATE statement at %C");
+	  gfc_free_gcc_alloc_align ();
+	}
+
       st = next_statement ();
     }
 }
diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index b57c18aba..64fd44950 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -6781,7 +6781,8 @@ bool
 gfc_array_allocate (gfc_se * se, gfc_expr * expr, tree status, tree errmsg,
 		    tree errlen, tree label_finish, tree expr3_elem_size,
 		    gfc_expr *expr3, tree e3_arr_desc, bool e3_has_nodescriptor,
-		    gfc_omp_namelist *omp_alloc, bool explicit_ts)
+		    gfc_omp_namelist *omp_alloc, bool explicit_ts,
+		    unsigned user_align)
 {
   tree tmp;
   tree pointer;
@@ -7026,7 +7027,8 @@ gfc_array_allocate (gfc_se * se, gfc_expr * expr, tree status, tree errmsg,
     gfc_allocate_allocatable (&elseblock, pointer, size, token,
 			      status, errmsg, errlen, label_finish, expr,
 			      coref != NULL ? coref->u.ar.as->corank : 0,
-			      omp_cond, omp_alt_alloc, succ_add_expr);
+			      omp_cond, omp_alt_alloc, succ_add_expr,
+			      user_align);
   else if (non_ulimate_coarray_ptr_comp && token)
     /* The token is set only for GFC_FCOARRAY_LIB mode.  */
     gfc_allocate_using_caf_lib (&elseblock, pointer, size, token, status,
@@ -7034,7 +7036,8 @@ gfc_array_allocate (gfc_se * se, gfc_expr * expr, tree status, tree errmsg,
 				GFC_CAF_COARRAY_ALLOC_ALLOCATE_ONLY);
   else
     gfc_allocate_using_malloc (&elseblock, pointer, size, status,
-			       omp_cond, omp_alt_alloc, succ_add_expr);
+			       omp_cond, omp_alt_alloc, succ_add_expr,
+			       user_align);
 
   if (dimension)
     {
diff --git a/gcc/fortran/trans-array.h b/gcc/fortran/trans-array.h
index 4b51e5469..eaa83fac4 100644
--- a/gcc/fortran/trans-array.h
+++ b/gcc/fortran/trans-array.h
@@ -21,7 +21,8 @@ along with GCC; see the file COPYING3.  If not see
 /* Generate code to initialize and allocate an array.  Statements are added to
    se, which should contain an expression for the array descriptor.  */
 bool gfc_array_allocate (gfc_se *, gfc_expr *, tree, tree, tree, tree, tree,
-			 gfc_expr *, tree, bool, gfc_omp_namelist *, bool);
+			 gfc_expr *, tree, bool, gfc_omp_namelist *, bool,
+			 unsigned = 0);
 
 /* Allow the bounds of a loop to be set from a callee's array spec.  */
 void gfc_set_loop_bounds_from_array_spec (gfc_interface_mapping *,
diff --git a/gcc/fortran/trans-stmt.cc b/gcc/fortran/trans-stmt.cc
index 49f8cd8d7..b2d0ddeb5 100644
--- a/gcc/fortran/trans-stmt.cc
+++ b/gcc/fortran/trans-stmt.cc
@@ -6703,6 +6703,9 @@ gfc_trans_allocate (gfc_code * code, gfc_omp_namelist *omp_allocate)
   if (!code->ext.alloc.list)
     return NULL_TREE;
 
+  /* Requested alignment of the current object, in bytes; 0 if none.  */
+  unsigned user_align = 0;
+
   stat = tmp = memsz = al_vptr = al_len = NULL_TREE;
   expr3 = expr3_vptr = expr3_len = expr3_esize = NULL_TREE;
   label_errmsg = label_finish = errmsg = errlen = NULL_TREE;
@@ -7171,6 +7174,25 @@ gfc_trans_allocate (gfc_code * code, gfc_omp_namelist *omp_allocate)
   for (al = code->ext.alloc.list; al != NULL; al = al->next)
     {
       expr = gfc_copy_expr (al->expr);
+
+      /* Take the larger of the ALLOCATE directive and the symbol's
+	 attribute.  The attribute does not apply to component refs.  */
+      user_align = al->align;
+      if (expr->symtree
+	  && (expr->symtree->n.sym->attr.ext_attr & (1 << EXT_ATTR_ALIGN)))
+	{
+	  bool comp_ref = false;
+	  for (gfc_ref *r = expr->ref; r; r = r->next)
+	    if (r->type == REF_COMPONENT)
+	      {
+		comp_ref = true;
+		break;
+	      }
+	  if (!comp_ref)
+	    user_align = MAX (user_align,
+			      expr->symtree->n.sym->attr.ext_align);
+	}
+
       /* UNLIMITED_POLY () needs the _data component to be set, when
 	 expr is a unlimited polymorphic object.  But the _data component
 	 has not been set yet, so check the derived type's attr for the
@@ -7304,7 +7326,8 @@ gfc_trans_allocate (gfc_code * code, gfc_omp_namelist *omp_allocate)
 			       tmp, e3rhs ? e3rhs : code->expr3,
 			       e3_is == E3_DESC ? expr3 : NULL_TREE,
 			       e3_has_nodescriptor, omp_alloc_item,
-			       code->ext.alloc.ts.type != BT_UNKNOWN))
+			       code->ext.alloc.ts.type != BT_UNKNOWN,
+			       user_align))
 	{
 	  /* A scalar or derived type.  First compute the size to
 	     allocate.
@@ -7476,10 +7499,12 @@ gfc_trans_allocate (gfc_code * code, gfc_omp_namelist *omp_allocate)
 	    gfc_allocate_allocatable (&se.pre, se.expr, memsz,
 				      NULL_TREE, stat, errmsg, errlen,
 				      label_finish, expr, 0,
-				      omp_cond, omp_alt_alloc, succ_add_expr);
+				      omp_cond, omp_alt_alloc, succ_add_expr,
+				      user_align);
 	  else
 	    gfc_allocate_using_malloc (&se.pre, se.expr, memsz, stat,
-				      omp_cond, omp_alt_alloc, succ_add_expr);
+				      omp_cond, omp_alt_alloc, succ_add_expr,
+				      user_align);
 	}
       else
 	{
diff --git a/gcc/fortran/trans.cc b/gcc/fortran/trans.cc
index 1d7006a69..21c3d4ea9 100644
--- a/gcc/fortran/trans.cc
+++ b/gcc/fortran/trans.cc
@@ -819,7 +819,7 @@ gfc_call_malloc (stmtblock_t * block, tree type, tree size)
 void
 gfc_allocate_using_malloc (stmtblock_t * block, tree pointer,
 			   tree size, tree status, tree cond, tree alt_alloc,
-			   tree extra_success_expr)
+			   tree extra_success_expr, unsigned align)
 {
   tree tmp, error_cond;
   stmtblock_t on_error;
@@ -838,8 +838,29 @@ gfc_allocate_using_malloc (stmtblock_t * block, tree pointer,
 			 size, build_int_cst (size_type_node, 1));
 
   if (!cond_is_true)
-    tmp = build_call_expr_loc (input_location,
-			       builtin_decl_explicit (BUILT_IN_MALLOC), 1, tmp);
+    {
+      if (align > 0)
+	{
+	  /* aligned_alloc wants the size to be a multiple of the
+	     alignment, so round it up.  */
+	  tree mask = build_int_cst (size_type_node, align - 1);
+	  tmp = fold_build2_loc (input_location, PLUS_EXPR, size_type_node,
+				 tmp, mask);
+	  tmp = fold_build2_loc (input_location, BIT_AND_EXPR,
+				 size_type_node, tmp,
+				 fold_build1_loc (input_location, BIT_NOT_EXPR,
+						  size_type_node, mask));
+	  tmp = build_call_expr_loc (input_location,
+				     builtin_decl_explicit
+				       (BUILT_IN_ALIGNED_ALLOC), 2,
+				     build_int_cst (size_type_node, align),
+				     tmp);
+	}
+      else
+	tmp = build_call_expr_loc (input_location,
+				   builtin_decl_explicit (BUILT_IN_MALLOC),
+				   1, tmp);
+    }
   else
     tmp = alt_alloc;
 
@@ -960,7 +981,8 @@ void
 gfc_allocate_allocatable (stmtblock_t * block, tree mem, tree size,
 			  tree token, tree status, tree errmsg, tree errlen,
 			  tree label_finish, gfc_expr* expr, int corank,
-			  tree cond, tree alt_alloc, tree extra_success_expr)
+			  tree cond, tree alt_alloc, tree extra_success_expr,
+			  unsigned align)
 {
   stmtblock_t alloc_block;
   tree tmp, null_mem, alloc, error;
@@ -1059,7 +1081,7 @@ gfc_allocate_allocatable (stmtblock_t * block, tree mem, tree size,
     }
   else
     gfc_allocate_using_malloc (&alloc_block, mem, size, status,
-			       cond, alt_alloc, extra_success_expr);
+			       cond, alt_alloc, extra_success_expr, align);
 
   alloc = gfc_finish_block (&alloc_block);
 
diff --git a/gcc/fortran/trans.h b/gcc/fortran/trans.h
index f97fefd2a..c310a650d 100644
--- a/gcc/fortran/trans.h
+++ b/gcc/fortran/trans.h
@@ -764,12 +764,12 @@ void gfc_allocate_using_caf_lib (stmtblock_t *, tree, tree, tree, tree, tree,
 void gfc_allocate_allocatable (stmtblock_t*, tree, tree, tree, tree,
 			       tree, tree, tree, gfc_expr*, int,
 			       tree = NULL_TREE, tree = NULL_TREE,
-			       tree = NULL_TREE);
+			       tree = NULL_TREE, unsigned = 0);
 
 /* Allocate memory, with optional status variable.  */
 void gfc_allocate_using_malloc (stmtblock_t *, tree, tree, tree,
 				tree = NULL_TREE, tree = NULL_TREE,
-				tree = NULL_TREE);
+				tree = NULL_TREE, unsigned = 0);
 
 /* Generate code to deallocate an array.  */
 tree gfc_deallocate_with_status (tree, tree, tree, tree, tree, bool, gfc_expr *,
diff --git a/gcc/testsuite/gfortran.dg/align_3.f90 b/gcc/testsuite/gfortran.dg/align_3.f90
new file mode 100644
index 000000000..f82682388
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/align_3.f90
@@ -0,0 +1,41 @@
+! { dg-do run }
+!
+! ALLOCATE honours ALIGN from both the attribute and the directive.
+! Repeated because plain malloc may return an aligned block by chance.
+
+program align_3
+  implicit none
+
+  real(kind=4), allocatable :: attr_arr(:)
+  !GCC$ ATTRIBUTES ALIGN(64) :: attr_arr
+
+  real(kind=4), allocatable :: attr_scalar
+  !GCC$ ATTRIBUTES ALIGN(64) :: attr_scalar
+
+  ! Aligned by the directive only.
+  real(kind=4), allocatable :: dir_arr(:)
+
+  ! Attribute says 32, directive below says 128; 128 must win.
+  real(kind=4), allocatable :: both(:)
+  !GCC$ ATTRIBUTES ALIGN(32) :: both
+
+  integer :: i
+
+  do i = 1, 8
+     allocate (attr_arr(13))
+     if (iand (loc (attr_arr), 63_8) /= 0) stop 1
+
+     allocate (attr_scalar)
+     if (iand (loc (attr_scalar), 63_8) /= 0) stop 2
+
+     !GCC$ ALLOCATE (dir_arr) ALIGN(128)
+     allocate (dir_arr(7))
+     if (iand (loc (dir_arr), 127_8) /= 0) stop 3
+
+     !GCC$ ALLOCATE (both) ALIGN(128)
+     allocate (both(9))
+     if (iand (loc (both), 127_8) /= 0) stop 4
+
+     deallocate (attr_arr, attr_scalar, dir_arr, both)
+  end do
+end program align_3
diff --git a/gcc/testsuite/gfortran.dg/align_4.f90 b/gcc/testsuite/gfortran.dg/align_4.f90
new file mode 100644
index 000000000..ead366536
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/align_4.f90
@@ -0,0 +1,23 @@
+! { dg-do compile }
+!
+! Invalid uses of the !GCC$ ALLOCATE (...) ALIGN(n) directive.
+
+subroutine bad_value
+  real, allocatable :: a(:)
+  !GCC$ ALLOCATE (a) ALIGN(24) ! { dg-error "must be a positive power of two" }
+  allocate (a(4))
+end subroutine bad_value
+
+subroutine bad_syntax
+  real, allocatable :: b(:)
+  !GCC$ ALLOCATE (b) ! { dg-error "Syntax error in !GCC\\\$ ALLOCATE directive" }
+  allocate (b(4))
+end subroutine bad_syntax
+
+subroutine not_before_allocate
+  real, allocatable :: c(:)
+  integer :: i
+  !GCC$ ALLOCATE (c) ALIGN(64)
+  i = 1 ! { dg-error "not immediately before an ALLOCATE statement" }
+  allocate (c(4))
+end subroutine not_before_allocate
-- 
2.50.1 (Apple Git-155)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.