>From 06eae5b08d777e5df026db6aeffa3216dfe99a29 Mon Sep 17 00:00:00 2001
From: Robert Dubner <[email protected]>
Date: Wed, 10 Jun 2026 23:42:20 -0400
Subject: [PATCH] cobol: Improve execution speed of MOVE NumericDisplay TO
 COMP-3

gcc/cobol/ChangeLog:

        * move.cc (mh_packed_to_packed): Moved.
        (mh_numdisp_to_packed): New function.
        (move_helper): Use mh_numdisp_to_packed().
        (hex_msg): New debugging function.
        * parse.y: Enable __gg__char_national().

libgcobol/ChangeLog:

        * intrinsic.cc (__gg__char_national): New function.

gcc/testsuite/ChangeLog:

        * cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.cob: New test.
        * cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.out: New test.
---
 gcc/cobol/move.cc                             | 801 ++++++++++++------
 gcc/cobol/parse.y                             |   4 +-
 .../group2/MOVE_NumericDisplay_to_COMP-3.cob  |  32 +
 .../group2/MOVE_NumericDisplay_to_COMP-3.out  |  13 +
 libgcobol/intrinsic.cc                        |  34 +
 5 files changed, 627 insertions(+), 257 deletions(-)
 create mode 100644
gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.cob
 create mode 100644
gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.out

diff --git a/gcc/cobol/move.cc b/gcc/cobol/move.cc
index 8c445cb789d..677b5b0ffb3 100644
--- a/gcc/cobol/move.cc
+++ b/gcc/cobol/move.cc
@@ -1994,9 +1994,307 @@ mh_alpha_to_alpha(const cbl_refer_t &destref,
   }
 
 static bool
-mh_packed_to_packed(const cbl_refer_t &destref,
+mh_numdisp_to_packed(const cbl_refer_t &destref,
                     const cbl_refer_t &sourceref);
 
+static bool
+mh_packed_to_packed(const cbl_refer_t &destref,
+                    const cbl_refer_t &sourceref)
+  {
+  if(    (destref.field->type   != FldPacked        )
+      || (sourceref.field->type != FldPacked        )
+      || (destref.field->attr    & scaled_e         )
+      || (sourceref.field->attr  & scaled_e         )
+      || (destref.field->attr    & packed_no_sign_e )
+      || (sourceref.field->attr  & packed_no_sign_e ) )
+    {
+    return false;
+    }
+  // Arriving here means both are packed, neither is scaled, and neither
is
+  // COMP-6 or PACKED NO SIGN.
+
+  // We are going to move source to the dest doing the absolute minimum
number
+  // of operations.  We are thus going to use memcpy (with constant
lengths)
+  // as much as we can, and use conditionals and nybble operations as
little
+  // little as possible.
+
+  // There are two broad cases.  The more straightforward case is where
source
+  // rdigits and dest rdigits are both even, or both odd.  When that is
the
+  // case, the source and destination decimal places are "in phase"
somewhere
+  // inside both the dest and the source.  Once we figure out the right
+  // offsets, we can memcpy the "inside" of the source to the correct
location
+  // in the dest.  We fiddle with the leading digits, the trailing
digits, and
+  // the sign nybble as necessary.
+
+  tree source_location = gg_define_variable(UCHAR_P);
+  tree dest_location   = gg_define_variable(UCHAR_P);
+  tree source_sign     = gg_define_variable(UCHAR_P);
+  tree dest_sign       = gg_define_variable(UCHAR_P);
+  tree temp;
+
+  get_location(temp, destref);
+  gg_assign(dest_location, temp);
+
+  get_location(temp, sourceref);
+  gg_assign(source_location, temp);
+
+  int      source_digits   = sourceref.field->data.digits;
+  int      source_rdigits  = sourceref.field->data.rdigits;
+  size_t   source_capacity = source_digits/2 + 1;
+  if( ((destref.field->data.rdigits ^ source_rdigits) & 1) )
+    {
+    /* This is an "out-of-phase" move, e.g., MOVE 999v99 to 999v9.  The
code
+       below handles in-phase moves, so we handle this by making a
left-shifted
+       copy of the source side.  By left-shifting it one nybble,
incrementing
+       the source_rdigits, and changing the location to the shifted
version, we
+       turn the out-of-phase problem into an in-phase problem.  */
+    size_t shifted_size;
+    if( source_digits & 1 )
+      {
+      // The source, plus the sign nybble, fills an even number of
nybbles, and
+      // so the shift requires an addition byte on the left.
+      shifted_size = source_capacity + 1;
+      }
+    else
+      {
+      // The highest-order source nybble is a zero, so the shift will
fill it
+      // without any additional storage needed.
+      shifted_size = source_capacity;
+      }
+    // Allocate storage for the shifted version:
+    tree shifted_type = build_array_type_nelts(UCHAR, shifted_size);
+    tree shifted = gg_define_variable(shifted_type);
+    TREE_ADDRESSABLE(shifted) = 1;
+    tree source_p        = gg_define_variable(UCHAR_P);
+    tree shifted_p_left  = gg_define_variable(UCHAR_P);
+    tree shifted_p_right = gg_define_variable(UCHAR_P);
+    tree carry      = gg_define_variable(UCHAR);
+    tree carry_next = gg_define_variable(UCHAR);
+    gg_assign(source_p,
+              gg_add(source_location,
+                     build_int_cst_type(SIZE_T,
+                                        source_capacity-1)));
+    gg_assign(shifted_p_left, gg_pointer_to_array(shifted));
+    gg_assign(shifted_p_right,
+              gg_add(shifted_p_left,
+                     build_int_cst_type(SIZE_T, shifted_size-1)));
+    // Start with the right side.
+    // Pick up the carry, which is the left side of the rightmost byte
+    gg_assign(carry,
+              gg_rshift(gg_indirect(source_p),
+                        build_int_cst_type(SIZE_T, 4)));
+    // Keep the sign nybble in place, but with a zero to its left
+    gg_assign(gg_indirect(shifted_p_right),
+              gg_bitwise_and(gg_indirect(source_p),
+                             build_int_cst_type(UCHAR, 0x0F)));
+
+    gg_decrement(source_p);
+    gg_decrement(shifted_p_right);
+    WHILE(shifted_p_right, gt_op, shifted_p_left)
+      {
+      gg_assign(carry_next,
+                gg_rshift(gg_indirect(source_p),
+                          build_int_cst_type(SIZE_T, 4)));
+      gg_assign(gg_indirect(shifted_p_right),
+                gg_bitwise_or(gg_lshift(gg_indirect(source_p),
+                                        build_int_cst_type(SIZE_T, 4)),
+                              carry));
+      gg_assign(carry, carry_next);
+      gg_decrement(source_p);
+      gg_decrement(shifted_p_right);
+      }
+    WEND
+    // At this point, shifted_p_right equals shifted_p_left
+    if( source_digits & 1 )
+      {
+      // The source, plus the sign nybble, fills an even number of
nybbles, and
+      // so the shift requires an addition byte on the left.
+      gg_assign(gg_indirect(shifted_p_left), carry);
+      }
+    else
+      {
+      // The highest-order source nybble is a zero, so the shift will
fill it
+      // without any additional storage needed.
+      gg_assign(gg_indirect(shifted_p_left),
+                gg_bitwise_or(gg_lshift(gg_indirect(source_p),
+                                        build_int_cst_type(SIZE_T, 4)),
+                              carry));
+      }
+
+    // We now have the left-shifted source in 'shifted'.
+    source_digits  += 1;
+    source_rdigits += 1;
+    source_capacity = source_digits/2 + 1;
+    gg_assign(source_location, shifted_p_left);
+    }
+  gg_assign(source_sign,
+            gg_add(source_location,
+                   build_int_cst_type(SIZE_T,
+                                      source_capacity-1)));
+  gg_assign(dest_sign,
+            gg_add(dest_location,
+                   build_int_cst_type(SIZE_T,
+
destref.field->data.capacity()-1)));
+
+  // This is the straightforward case, where the dest and source decimal
+  // places are in phase, which means that the middles of the values can
+  // simply be moved.
+  int dest_rbytes  = destref.field->data.rdigits/2 + 1;
+  int dest_lbytes  = destref.field->data.capacity() - dest_rbytes;
+
+  int source_rbytes = source_rdigits/2 + 1;
+  int source_lbytes = source_capacity - source_rbytes;
+
+  uint32_t dest_bytes = destref.field->data.capacity();
+
+  if( source_lbytes > dest_lbytes )
+    {
+    // There are too many source lbytes.   We just skip those extra
bytes,
+    // truncating off those high-order digits.
+    gg_assign(source_location,
+              gg_add(source_location,
+                     build_int_cst_type(SIZE_T,
+                                        source_lbytes-dest_lbytes)));
+    }
+  else if( source_lbytes < dest_lbytes )
+    {
+    // There are too few source lbytes.  We zero out the extra bytes on
the
+    // left side of the destination.
+    gg_memset(dest_location,
+              integer_zero_node,
+              build_int_cst_type(SIZE_T, dest_lbytes-source_lbytes));
+    gg_assign(dest_location,
+              gg_add(dest_location,
+                     build_int_cst_type(SIZE_T,
dest_lbytes-source_lbytes)));
+    // And reduce the total number to move by the number we zeroed:
+    dest_bytes -= dest_lbytes-source_lbytes;
+    }
+  source_lbytes = dest_lbytes;
+  // We have lined up source_location and dest_location so that the
source
+  // lbytes will go into the correct location in the destination
+
+  // We copy over as many bytes as we have in the source that can fit
into
+  // the destination:
+  size_t bytes_to_copy =
+            std::min(static_cast<uint32_t>(source_lbytes) +
source_rbytes,
+                                           dest_bytes);
+  gg_memcpy(dest_location,
+            source_location,
+            build_int_cst_type(SIZE_T, bytes_to_copy));
+
+  // We make sure the final sign nybble is correct.
+
+  if( source_rbytes == dest_rbytes )
+    {
+    // The sign nybble from the source is now in the destination.  It
might
+    // need to be changed
+    if(    !(sourceref.field->attr  & signable_e)
+        &&  (destref.field->attr    & signable_e) )
+      {
+      // The unsignable source has an 0xF sign nybble, so we need to turn
+      // that into an positive 0xC in the signable destination:
+      gg_assign(gg_indirect(dest_sign),
+                gg_bitwise_and(gg_indirect(dest_sign),
+                               build_int_cst_type(UCHAR, 0xFC)));
+      }
+    else if(    (sourceref.field->attr  & signable_e)
+            && !(destref.field->attr & signable_e) )
+      {
+      // The signable source has an 0xC or 0xD sign nybble, so we need to
+      // turn that into an 0xF in the unsignable destination:
+      gg_assign(gg_indirect(dest_sign),
+                gg_bitwise_or(gg_indirect(dest_sign),
+                              build_int_cst_type(UCHAR, 0x0F)));
+      }
+    }
+  else
+    {
+    // There is mismatch between source and dest rdigits:
+    if( source_rbytes < dest_rbytes )
+      {
+      // The source was too short to fill the destination, which means we
+      // currently have a source's sign nybble sitting in the middle of
the
+      // destination.  We need to zero out that nybble
+      gg_assign(gg_indirect(dest_location,
+                            build_int_cst_type(SIZE_T,
+                                               bytes_to_copy-1)),
+                gg_bitwise_and(gg_indirect(dest_location,
+                               build_int_cst_type(SIZE_T,
+                                                  bytes_to_copy-1)),
+                                           build_int_cst_type(UCHAR,
0xF0)));
+      // And then we need to zero out the remaining dest_rbytes:
+      int remaining_rbytes = dest_rbytes - source_rbytes;
+      if( remaining_rbytes > 1 )
+        {
+        gg_memset(gg_add(dest_location,
+                         build_int_cst_type(SIZE_T, bytes_to_copy)),
+                  integer_zero_node,
+                  build_int_cst_type(SIZE_T,
+                           destref.field->data.capacity() -
bytes_to_copy));
+        }
+      // And now we have to adjust the final nybble:
+
+      if(    !(sourceref.field->attr  & signable_e)
+          &&  (destref.field->attr & signable_e) )
+        {
+        // The source is unsignable, so we turn that into an positive 0xC
in
+        // the signable destination:
+        gg_assign(gg_indirect(dest_sign), build_int_cst_type(UCHAR,
0x0C));
+        }
+      else if(    (sourceref.field->attr  & signable_e)
+              && !(destref.field->attr & signable_e) )
+        {
+        gg_assign(gg_indirect(dest_sign), build_int_cst_type(UCHAR,
0x0F));
+        }
+      else
+        {
+        // The source and the destination are either both signable, or
+        // both unsignable.  We copy the source's sign nybble to the
dest.
+        gg_assign(gg_indirect(dest_sign),
+                  gg_bitwise_or(gg_indirect(dest_sign),
+                                gg_bitwise_and(gg_indirect(source_sign),
+                                               build_int_cst_type(UCHAR,
+
0x0F))));
+        }
+      }
+    else // source_rbytes > dest_rbytes
+      {
+      // There were more source_rbytes than we needed, which means the
final
+      // nybble of the destination is a digit that needs to be truncated
+      // away and replaced with the correct sign nybble.
+      if(    !(sourceref.field->attr  & signable_e)
+          &&  (destref.field->attr & signable_e) )
+        {
+        // The source was unsignable, so we set the sign nybble to a
+        // a positive 0x0C
+        gg_assign(gg_indirect(dest_sign),
+                  gg_bitwise_or(gg_bitwise_and(gg_indirect(dest_sign),
+                                     build_int_cst_type(UCHAR, 0xF0)),
+                                     build_int_cst_type(UCHAR, 0x0C)));
+        }
+      else if(    (sourceref.field->attr  & signable_e)
+              && !(destref.field->attr & signable_e) )
+        {
+        // The dest is unsignable; turn the final nybble into an 0xFo
+        gg_assign(gg_indirect(dest_sign),
+                  gg_bitwise_or(gg_indirect(dest_sign),
+                                build_int_cst_type(UCHAR, 0x0F)));
+        }
+      else
+        {
+        // The source and the destination are either both signable, or
+        // both unsignable.  We copy the source's sign nybble to the
dest.
+        gg_assign(gg_indirect(dest_sign),
+                  gg_bitwise_or(gg_bitwise_and(gg_indirect(dest_sign),
+                                build_int_cst_type(UCHAR, 0xF0)),
+                                gg_bitwise_and(gg_indirect(source_sign),
+                                build_int_cst_type(UCHAR, 0x0F))));
+        }
+      }
+    }
+  return true;
+  }
+
 void
 move_helper(tree size_error,        // This is an INT
             cbl_refer_t destref,
@@ -2053,6 +2351,18 @@ move_helper(tree size_error,        // This is an
INT
     moved = mh_identical(destref, sourceref);
     }
 
+  if( !moved )
+    {
+    moved = mh_packed_to_packed(destref,
+                                sourceref);
+    }
+
+  if( !moved )
+    {
+    moved = mh_numdisp_to_packed(destref,
+                                sourceref);
+    }
+
   if( !moved )
     {
     moved = mh_source_is_literalN(destref,
@@ -2104,12 +2414,6 @@ move_helper(tree size_error,        // This is an
INT
                               size_error);
     }
 
-  if( !moved )
-    {
-    moved = mh_packed_to_packed(destref,
-                                sourceref);
-    }
-
   if( !moved )
     {
     SHOW_PARSE1
@@ -2430,302 +2734,291 @@ hex_of(tree location, size_t bytes)
     gg_printf("%2.2X", gg_indirect_i(gg_cast(UCHAR_P, location), i),
NULL_TREE);
     }
   }
+
+static void
+hex_msg(const char *msg, tree location, size_t bytes)
+  {
+  gg_printf("%s ", gg_string_literal(msg), NULL_TREE);
+  hex_of(location, bytes);
+  gg_printf("\n", NULL_TREE);
+  }
+
 #endif
 
 static bool
-mh_packed_to_packed(const cbl_refer_t &destref,
+mh_numdisp_to_packed(const cbl_refer_t &destref,
                     const cbl_refer_t &sourceref)
   {
-  if(    (destref.field->type   != FldPacked        )
-      || (sourceref.field->type != FldPacked        )
-      || (destref.field->attr    & scaled_e         )
-      || (sourceref.field->attr  & scaled_e         )
-      || (destref.field->attr    & packed_no_sign_e )
-      || (sourceref.field->attr  & packed_no_sign_e ) )
+  const charmap_t *charmap =
+                   __gg__get_charmap(sourceref.field->codeset.encoding);
+  if(    (destref.field->type   != FldPacked         )
+      || (sourceref.field->type != FldNumericDisplay )
+      || (charmap->stride()     != 1                 )
+      || (destref.field->attr    & scaled_e          )
+      || (sourceref.field->attr  & scaled_e          )
+      || (destref.field->attr    & packed_no_sign_e  )
+      || (sourceref.field->attr  & leading_e         )
+      || (sourceref.field->attr  & separate_e        ) )
     {
     return false;
     }
-  // Arriving here means both are packed, neither is scaled, and neither
is
-  // COMP-6 or PACKED NO SIGN.
-
-  // We are going to move source to the dest doing the absolute minimum
number
-  // of operations.  We are thus going to use memcpy (with constant
lengths)
-  // as much as we can, and use conditionals and nybble operations as
little
-  // little as possible.
-
-  // There are two broad cases.  The more straightforward case is where
source
-  // rdigits and dest rdigits are both even, or both odd.  When that is
the
-  // case, the source and destination decimal places are "in phase"
somewhere
-  // inside both the dest and the source.  Once we figure out the right
-  // offsets, we can memcpy the "inside" of the source to the correct
location
-  // in the dest.  We fiddle with the leading digits, the trailing
digits, and
-  // the sign nybble as necessary.
+  /* Source is NumericDisplay, dest is packed, neither are scaled, the
+     packed destination has a sign nybble, and the numeric source has an
+     ordinarysign bit encoded in the final digit.  */
+  tree uzero = build_int_cst_type(UCHAR,    0);
+  tree umask = build_int_cst_type(UCHAR, 0x0F);
+  tree ufour = build_int_cst_type(SIZE_T,   4);
 
   tree source_location = gg_define_variable(UCHAR_P);
   tree dest_location   = gg_define_variable(UCHAR_P);
-  tree source_sign     = gg_define_variable(UCHAR_P);
-  tree dest_sign       = gg_define_variable(UCHAR_P);
+  tree dest_p          = gg_define_variable(UCHAR_P);
+  tree source_p        = gg_define_variable(UCHAR_P);
+
   tree temp;
 
-  get_location(temp, destref);
-  gg_assign(dest_location, temp);
+  get_location(temp, destref);
+  gg_assign(dest_location, temp);
+  gg_assign(dest_p, dest_location);
+  get_location(temp, sourceref);
+  gg_assign(source_location, temp);
+
+  int source_digits   = sourceref.field->data.digits;
+  int source_rdigits  = sourceref.field->data.rdigits;
+  int source_ldigits  = source_digits - source_rdigits;
+  int dest_digits     = destref.field->data.digits;
+  int dest_rdigits    = destref.field->data.rdigits;
+  int dest_ldigits    = dest_digits - dest_rdigits;
+
+  int truncate_ldigits = std::max(0, source_ldigits-dest_ldigits);
+  int truncate_rdigits = std::max(0, source_rdigits-dest_rdigits);
+  int leading_zeroes   = std::max(0, dest_ldigits-source_ldigits);
+  int trailing_zeroes  = std::max(0, dest_rdigits-source_rdigits);
+
+  int zero_pairs;
+  int digit_pairs;
+  int source_remaining;
+
+  if( truncate_ldigits )
+    {
+    // We handle truncation of digits on the left by moving the starting
line.
+    gg_assign(source_p,
+              gg_add(source_location,
+                     build_int_cst_type(SIZE_T, truncate_ldigits)));
+    source_digits  -= truncate_ldigits;
+    source_ldigits -= truncate_ldigits;
+    }
+  else
+    {
+    gg_assign(source_p, source_location);
+    }
+
+  if( truncate_rdigits )
+    {
+    // We handle truncation of digits on the right by moving the finish
line.
+    source_digits  -= truncate_rdigits;
+    source_ldigits -= truncate_rdigits;
+    }
+
+  if( !source_digits )
+    {
+    // When source_digits is zero, it means that some pervert of a COBOL
+    // programmer told us to MOVE 999V TO V999.  The result has to be
zero,
+    // and our life down below will be easier when we know that there is
at
+    // least one digit that needs to be moved from the source to the
+    // destination.
+    gg_memset(dest_p,
+              integer_zero_node,
+              build_int_cst_type(SIZE_T,
destref.field->data.capacity()));
+    goto adjust_sign;
+    }
+
+  source_remaining = source_digits;
 
-  get_location(temp, sourceref);
-  gg_assign(source_location, temp);
+  // The first thing we need to do is adjust the first byte of the
destination
+  // so that we know where we are in left-nybble/right-nybble space.
Let's
+  // call the digit at source_p "N".  (That digit might be a leading
zero.)
+  // When dest_digits is an even number, it means the final result is
something
+  // like 0N.23.4s.  So, when dest_digits is even, we have to start
things off
+  // with "0N".
 
-  int      source_digits   = sourceref.field->data.digits;
-  int      source_rdigits  = sourceref.field->data.rdigits;
-  size_t   source_capacity = source_digits/2 + 1;
-  if( ((destref.field->data.rdigits ^ source_rdigits) & 1) )
+  if( !(dest_digits & 0x01) )
     {
-    /* This is an "out-of-phase" move, e.g., MOVE 999v99 to 999v9.  The
code
-       below handles in-phase moves, so we handle this by making a
left-shifted
-       copy of the source side.  By left-shifting it one nybble,
incrementing
-       the source_rdigits, and changing the location to the shifted
version, we
-       turn the out-of-phase problem into an in-phase problem.  */
-    size_t shifted_size;
-    if( source_digits & 1 )
+    // dest_digits is an even number.
+    if( leading_zeroes )
       {
-      // The source, plus the sign nybble, fills an even number of
nybbles, and
-      // so the shift requires an addition byte on the left.
-      shifted_size = source_capacity + 1;
+      // The first byte is "0N", but N is zero:
+      gg_assign(gg_indirect(dest_p), uzero);
+      leading_zeroes -= 1;
       }
     else
       {
-      // The highest-order source nybble is a zero, so the shift will
fill it
-      // without any additional storage needed.
-      shifted_size = source_capacity;
+      // The first byte is "0N", where N is the value from the first
character
+      // of the source.  We know that source_remaining is at least one at
this
+      // point.
+      gg_assign(gg_indirect(dest_p),
+                gg_bitwise_and(gg_indirect(source_p), umask));
+      gg_increment(source_p);
+      source_remaining -= 1;
       }
-    // Allocate storage for the shifted version:
-    tree shifted_type = build_array_type_nelts(UCHAR, shifted_size);
-    tree shifted = gg_define_variable(shifted_type);
-    TREE_ADDRESSABLE(shifted) = 1;
-    tree source_p        = gg_define_variable(UCHAR_P);
-    tree shifted_p_left  = gg_define_variable(UCHAR_P);
-    tree shifted_p_right = gg_define_variable(UCHAR_P);
-    tree carry      = gg_define_variable(UCHAR);
-    tree carry_next = gg_define_variable(UCHAR);
-    gg_assign(source_p,
-              gg_add(source_location,
-                     build_int_cst_type(SIZE_T,
-                                        source_capacity-1)));
-    gg_assign(shifted_p_left, gg_pointer_to_array(shifted));
-    gg_assign(shifted_p_right,
-              gg_add(shifted_p_left,
-                     build_int_cst_type(SIZE_T, shifted_size-1)));
-    // Start with the right side.
-    // Pick up the carry, which is the left side of the rightmost byte
-    gg_assign(carry,
-              gg_rshift(gg_indirect(source_p),
-                        build_int_cst_type(SIZE_T, 4)));
-    // Keep the sign nybble in place, but with a zero to its left
-    gg_assign(gg_indirect(shifted_p_right),
-              gg_bitwise_and(gg_indirect(source_p),
-                             build_int_cst_type(UCHAR, 0x0F)));
+    gg_increment(dest_p);
+    }
 
-    gg_decrement(source_p);
-    gg_decrement(shifted_p_right);
-    WHILE(shifted_p_right, gt_op, shifted_p_left)
-      {
-      gg_assign(carry_next, 
-                gg_rshift(gg_indirect(source_p),
-                          build_int_cst_type(SIZE_T, 4)));
-      gg_assign(gg_indirect(shifted_p_right),
-                gg_bitwise_or(gg_lshift(gg_indirect(source_p),
-                                        build_int_cst_type(SIZE_T, 4)),
-                              carry));
-      gg_assign(carry, carry_next);
-      gg_decrement(source_p);
-      gg_decrement(shifted_p_right);
-      }
-    WEND
-    // At this point, shifted_p_right equals shifted_p_left
-    if( source_digits & 1 )
-      {
-      // The source, plus the sign nybble, fills an even number of
nybbles, and
-      // so the shift requires an addition byte on the left.
-      gg_assign(gg_indirect(shifted_p_left), carry);
-      }
-    else
-      {
-      // The highest-order source nybble is a zero, so the shift will
fill it
-      // without any additional storage needed.
-      gg_assign(gg_indirect(shifted_p_left),
-                gg_bitwise_or(gg_lshift(gg_indirect(source_p),
-                                        build_int_cst_type(SIZE_T, 4)),
-                              carry));
-      }
+  // At this point, we know that leading + source + trailing is an odd
+  // number.
 
-    // We now have the left-shifted source in 'shifted'.
-    source_digits  += 1;
-    source_rdigits += 1;
-    source_capacity = source_digits/2 + 1;
-    gg_assign(source_location, shifted_p_left);
+  // We know that dest_p is set up to accept a left/right pair next.
Let's
+  // see if we have enough leading_zeroes to warrant using memset:
+  zero_pairs = leading_zeroes/2;
+  if( zero_pairs )
+    {
+    // We can use memset to handle left-side zero-fill:
+    tree tpairs = build_int_cst_type(SIZE_T, zero_pairs);
+    gg_memset(dest_p, integer_zero_node, tpairs);
+    gg_assign(dest_p, gg_add(dest_p, tpairs));
+    leading_zeroes -= 2 * zero_pairs;
     }
-  gg_assign(source_sign,
-            gg_add(source_location,
-                   build_int_cst_type(SIZE_T,
-                                      source_capacity-1)));
-  gg_assign(dest_sign,
-            gg_add(dest_location,
-                   build_int_cst_type(SIZE_T,
-
destref.field->data.capacity()-1)));
 
-  // This is the straightforward case, where the dest and source decimal
-  // places are in phase, which means that the middles of the values can
-  // simply be moved.
-  int dest_rbytes  = destref.field->data.rdigits/2 + 1;
-  int dest_lbytes  = destref.field->data.capacity() - dest_rbytes;
+  // dest-p is still set up for a left/right pair.
+  if( leading_zeroes )
+    {
+    // But we still have one leading zero left.  We know at this point
that
+    // there is at least one source digit left, so build the byte using
+    // zero/*source_p
+    gg_assign(gg_indirect(dest_p),
+              gg_bitwise_and(gg_indirect(source_p), umask));
+    //leading_zeroes   -= 1;
+    source_remaining -= 1;
+    gg_increment(source_p);
+    gg_increment(dest_p);
+    }
 
-  int source_rbytes = source_rdigits/2 + 1;
-  int source_lbytes = source_capacity - source_rbytes;
+  // At this point, we know that leading_zeroes is zero.  We know that
+  // source_remaining + trailing_zeroes is an odd number.  We
+  // currently have dest_p lined up on a left-right boundary.
 
-  uint32_t dest_bytes = destref.field->data.capacity();
+  // We are going to transfer as many pairs of source_remaining digits as
we
+  // can.
 
-  if( source_lbytes > dest_lbytes )
+  digit_pairs = source_remaining/2;
+  if( digit_pairs )
     {
-    // There are too many source lbytes.   We just skip those extra
bytes,
-    // truncating off those high-order digits.
-    gg_assign(source_location,
-              gg_add(source_location,
-                     build_int_cst_type(SIZE_T,
-                                        source_lbytes-dest_lbytes)));
+    tree dest_end = gg_define_variable(UCHAR_P);
+    gg_assign(dest_end,
+              gg_add(dest_p,
+                     build_int_cst_type(SIZE_T, digit_pairs)));
+    WHILE( dest_p, lt_op, dest_end )
+      {
+      tree left_nybble  = gg_lshift(gg_indirect(source_p), ufour);
+      tree right_nybble = gg_bitwise_and(gg_indirect(source_p,
+                                                     integer_one_node),
+                                         umask);
+      gg_assign(gg_indirect(dest_p),
+                gg_bitwise_or(left_nybble, right_nybble));
+      gg_increment(dest_p);
+      gg_assign(source_p,
+                gg_add(source_p, build_int_cst_type(SIZE_T, 2)));
+      }
+    WEND
+    source_remaining -= 2 * digit_pairs;
     }
-  else if( source_lbytes < dest_lbytes )
+
+  // At this point, source_remaining is zero or one
+
+  if( source_remaining )
     {
-    // There are too few source lbytes.  We zero out the extra bytes on
the
-    // left side of the destination.
-    gg_memset(dest_location,
-              integer_zero_node,
-              build_int_cst_type(SIZE_T, dest_lbytes-source_lbytes));
-    gg_assign(dest_location,
-              gg_add(dest_location,
-                     build_int_cst_type(SIZE_T,
dest_lbytes-source_lbytes)));
-    // And reduce the total number to move by the number we zeroed:
-    dest_bytes -= dest_lbytes-source_lbytes;
+    gg_assign(gg_indirect(dest_p),
+              gg_lshift(gg_indirect(source_p), ufour));
+    gg_increment(dest_p);
+    //source_remaining -= 1;
+    if( trailing_zeroes )
+      {
+      trailing_zeroes -= 1;
+      }
     }
-  source_lbytes = dest_lbytes;
-  // We have lined up source_location and dest_location so that the
source
-  // lbytes will go into the correct location in the destination
+  // At this point, we know trailing_zeroes has to be an even number, and
we
+  // need to zero out that many nybbles:
 
-  // We copy over as many bytes as we have in the source that can fit
into
-  // the destination:
-  size_t bytes_to_copy =
-            std::min(static_cast<uint32_t>(source_lbytes) +
source_rbytes,
-                                           dest_bytes);
-  gg_memcpy(dest_location,
-            source_location,
-            build_int_cst_type(SIZE_T, bytes_to_copy));
+  if( trailing_zeroes >= 2 )
+    {
+    zero_pairs = trailing_zeroes/2;
+    // We can use memset to handle left-side zero-fill:
+    tree tpairs = build_int_cst_type(SIZE_T, zero_pairs);
+    gg_memset(dest_p, integer_zero_node, tpairs);
+    gg_assign(dest_p, gg_add(dest_p, tpairs));
+    trailing_zeroes -= 2 * zero_pairs;
+    }
 
-  // We make sure the final sign nybble is correct.
+  if( trailing_zeroes )
+    {
+    // There is one trailing zero left
+    gg_assign(gg_indirect(dest_p), uzero);
+    gg_increment(dest_p);
+    //trailing_zeroes -= 1;
+    }
 
-  if( source_rbytes == dest_rbytes )
+  adjust_sign:
+  gg_assign(dest_p, gg_add(dest_location,
+                           build_int_cst_type(SIZE_T,
+
destref.field->data.capacity()-1)));
+
+  if( !(destref.field->attr & signable_e) )
     {
-    // The sign nybble from the source is now in the destination.  It
might
-    // need to be changed
-    if(    !(sourceref.field->attr  & signable_e)
-        &&  (destref.field->attr    & signable_e) )
-      {
-      // The unsignable source has an 0xF sign nybble, so we need to turn
-      // that into an positive 0xC in the signable destination:
-      gg_assign(gg_indirect(dest_sign),
-                gg_bitwise_and(gg_indirect(dest_sign),
-                               build_int_cst_type(UCHAR, 0xFC)));
-      }
-    else if(    (sourceref.field->attr  & signable_e)
-            && !(destref.field->attr & signable_e) )
-      {
-      // The signable source has an 0xC or 0xD sign nybble, so we need to
-      // turn that into an 0xF in the unsignable destination:
-      gg_assign(gg_indirect(dest_sign),
-                gg_bitwise_or(gg_indirect(dest_sign),
-                              build_int_cst_type(UCHAR, 0x0F)));
-      }
+    // The destination is not signable
+    gg_assign(gg_indirect(dest_p),
+              gg_bitwise_or(gg_indirect(dest_p), umask));
     }
   else
     {
-    // There is mismatch between source and dest rdigits:
-    if( source_rbytes < dest_rbytes )
+    if( sourceref.field->attr & signable_e )
       {
-      // The source was too short to fill the destination, which means we
-      // currently have a source's sign nybble sitting in the middle of
the
-      // destination.  We need to zero out that nybble
-      gg_assign(gg_indirect(dest_location,
-                            build_int_cst_type(SIZE_T,
-                                               bytes_to_copy-1)),
-                gg_bitwise_and(gg_indirect(dest_location,
+    // This is the location of the character with the sign flag.
+      gg_assign(source_p, gg_add(source_location,
                                build_int_cst_type(SIZE_T,
-                                                  bytes_to_copy-1)),
-                                           build_int_cst_type(UCHAR,
0xF0)));
-      // And then we need to zero out the remaining dest_rbytes:
-      int remaining_rbytes = dest_rbytes - source_rbytes;
-      if( remaining_rbytes > 1 )
-        {
-        gg_memset(gg_add(dest_location,
-                         build_int_cst_type(SIZE_T, bytes_to_copy)),
-                  integer_zero_node,
-                  build_int_cst_type(SIZE_T,
-                           destref.field->data.capacity() -
bytes_to_copy));
-        }
-      // And now we have to adjust the final nybble:
-
-      if(    !(sourceref.field->attr  & signable_e)
-          &&  (destref.field->attr & signable_e) )
-        {
-        // The source is unsignable, so we turn that into an positive 0xC
in
-        // the signable destination:
-        gg_assign(gg_indirect(dest_sign), build_int_cst_type(UCHAR,
0x0C));
-        }
-      else if(    (sourceref.field->attr  & signable_e)
-              && !(destref.field->attr & signable_e) )
+
sourceref.field->data.capacity()-1)));
+      if( charmap->is_like_ebcdic() )
         {
-        gg_assign(gg_indirect(dest_sign), build_int_cst_type(UCHAR,
0x0F));
+        // EBCDIC digits are 0xF0 through 0xF9; negative is flagged by
+        // 0xD0 through 0xD9
+        IF( gg_indirect(source_p), lt_op, build_int_cst_type(UCHAR, 0xF0)
)
+          {
+          gg_assign(gg_indirect(dest_p),
+                    gg_bitwise_or(gg_indirect(dest_p),
+                                  build_int_cst_type(UCHAR, 0x0D)));
+          }
+        ELSE
+          {
+          gg_assign(gg_indirect(dest_p),
+                    gg_bitwise_or(gg_indirect(dest_p),
+                                  build_int_cst_type(UCHAR, 0x0C)));
+          }
+        ENDIF
         }
       else
         {
-        // The source and the destination are either both signable, or
-        // both unsignable.  We copy the source's sign nybble to the
dest.
-        gg_assign(gg_indirect(dest_sign),
-                  gg_bitwise_or(gg_indirect(dest_sign),
-                                gg_bitwise_and(gg_indirect(source_sign),
-                                               build_int_cst_type(UCHAR,
-
0x0F))));
+        // EBCDIC digits are 0x30 through 0x39; negative is flagged by
+        // 0x70 through 0x79
+        IF( gg_indirect(source_p), ge_op, build_int_cst_type(UCHAR, 0x70)
)
+          {
+          gg_assign(gg_indirect(dest_p),
+                    gg_bitwise_or(gg_indirect(dest_p),
+                                  build_int_cst_type(UCHAR, 0x0D)));
+          }
+        ELSE
+          {
+          gg_assign(gg_indirect(dest_p),
+                    gg_bitwise_or(gg_indirect(dest_p),
+                                  build_int_cst_type(UCHAR, 0x0C)));
+          }
+        ENDIF
         }
       }
-    else // source_rbytes > dest_rbytes
+    else
       {
-      // There were more source_rbytes than we needed, which means the
final
-      // nybble of the destination is a digit that needs to be truncated
-      // away and replaced with the correct sign nybble.
-      if(    !(sourceref.field->attr  & signable_e)
-          &&  (destref.field->attr & signable_e) )
-        {
-        // The source was unsignable, so we set the sign nybble to a
-        // a positive 0x0C
-        gg_assign(gg_indirect(dest_sign),
-                  gg_bitwise_or(gg_bitwise_and(gg_indirect(dest_sign),
-                                     build_int_cst_type(UCHAR, 0xF0)),
-                                     build_int_cst_type(UCHAR, 0x0C)));
-        }
-      else if(    (sourceref.field->attr  & signable_e)
-              && !(destref.field->attr & signable_e) )
-        {
-        // The dest is unsignable; turn the final nybble into an 0xFo
-        gg_assign(gg_indirect(dest_sign),
-                  gg_bitwise_or(gg_indirect(dest_sign),
-                                build_int_cst_type(UCHAR, 0x0F)));
-        }
-      else
-        {
-        // The source and the destination are either both signable, or
-        // both unsignable.  We copy the source's sign nybble to the
dest.
-        gg_assign(gg_indirect(dest_sign),
-                  gg_bitwise_or(gg_bitwise_and(gg_indirect(dest_sign),
-                                build_int_cst_type(UCHAR, 0xF0)),
-                                gg_bitwise_and(gg_indirect(source_sign),
-                                build_int_cst_type(UCHAR, 0x0F))));
-        }
+      gg_assign(gg_indirect(dest_p),
+                gg_bitwise_or(gg_indirect(dest_p),
+                              build_int_cst_type(UCHAR, 0x0C)));
       }
     }
+
   return true;
   }
diff --git a/gcc/cobol/parse.y b/gcc/cobol/parse.y
index dfd5bbf0a90..419f08dddaf 100644
--- a/gcc/cobol/parse.y
+++ b/gcc/cobol/parse.y
@@ -11918,9 +11918,7 @@ intrinsic0:     CURRENT_DATE {
 intrinsic_I:    BOOLEAN_OF_INTEGER     { $$ = BOOLEAN_OF_INTEGER;
                  cbl_unimplemented("BOOLEAN-OF-INTEGER");
                }
-        |       CHAR_NATIONAL          { $$ = CHAR_NATIONAL;
-                   cbl_unimplemented("CHAR-NATIONAL");
-               }
+        |       CHAR_NATIONAL          { $$ = CHAR_NATIONAL; }
         |       DATE_OF_INTEGER        { $$ = DATE_OF_INTEGER; }
         |       DAY_OF_INTEGER         { $$ = DAY_OF_INTEGER; }
         |       FACTORIAL              { $$ = FACTORIAL; }
diff --git
a/gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.cob
b/gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.cob
new file mode 100644
index 00000000000..0cdb0664aef
--- /dev/null
+++ b/gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.cob
@@ -0,0 +1,32 @@
+       *> { dg-do run }
+       *> { dg-output-file "group2/MOVE_NumericDisplay_to_COMP-3.out" }
+        identification              division.
+        program-id.                 prog.
+        data                        division.
+        working-storage             section.
+        01 var11    pic     9     value 1     .
+        01 var12    pic     99    value 12    .
+        01 var13    pic     999   value 123   .
+        01 var14    pic     9999  value 1234  .
+        01 var1t    pic     999v999 COMP-3.
+        01 var21    pic        s9v9  value   -1.2  .
+        01 var22    pic       s99v9  value   -12.3  .
+        01 var23    pic      s999v9  value   -123.4  .
+        01 var24    pic     s9999v9  value   -1234.5  .
+        01 var2t    pic      s999v999 COMP-3.
+        procedure                   division.
+            move var11 to var1t display var1t
+            move var12 to var1t display var1t
+            move var13 to var1t display var1t
+            move var14 to var1t display var1t
+            move var21 to var2t display var2t
+            move var22 to var2t display var2t
+            move var23 to var2t display var2t
+            move var24 to var2t display var2t
+            move var11 to var2t display var2t
+            move var12 to var2t display var2t
+            move var13 to var2t display var2t
+            move var14 to var2t display var2t
+            goback.
+        end program                 prog.
+
diff --git
a/gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.out
b/gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.out
new file mode 100644
index 00000000000..499809f93a2
--- /dev/null
+++ b/gcc/testsuite/cobol.dg/group2/MOVE_NumericDisplay_to_COMP-3.out
@@ -0,0 +1,13 @@
+001.000
+012.000
+123.000
+234.000
+-001.200
+-012.300
+-123.400
+-234.500
++001.000
++012.000
++123.000
++234.000
+
diff --git a/libgcobol/intrinsic.cc b/libgcobol/intrinsic.cc
index e9577f46bf5..9e6660d97ab 100644
--- a/libgcobol/intrinsic.cc
+++ b/libgcobol/intrinsic.cc
@@ -1210,6 +1210,40 @@ __gg__char( cblc_field_t *dest,
     }
   }
 
+extern "C"
+void
+__gg__char_national(cblc_field_t *dest,
+              const cblc_field_t *source,
+                    size_t        source_offset,
+                    size_t        source_size )
+  {
+  // Since we haven't tried to implement collation sequences for
National,
+  // this whole subroutine is a Hail Mary play.  I frankly don't even
know
+  // why we tried.
+  int rdigits;
+
+  // The CHAR function takes an integer, the ordinal position.  It
+  // returns a single-character string, which is the character at that
+  // ordinal position in the DISPLAY collation.
+
+  // 'A', with the ascii value of 65, is at the ordinal position 66
+  // in the default collation.
+
+  int ordinal = (int)(__gg__binary_value_from_qualified_field(&rdigits,
+                                                              source,
+
source_offset,
+
source_size));
+  ordinal /= __gg__power_of_ten(rdigits);
+  ordinal -= 1;
+
+  // We need to convert the ch character to the destination encoding.
+  // THIS IS A KLUDGE UNTIL WE MAKE THE CURRENT_COLLATION TO BE A MAP OF
+  // WIDE CHARACTERS!
+  const charmap_t *charmap_dest = __gg__get_charmap(dest->encoding);
+
+  memcpy(dest->data, &ordinal, charmap_dest->stride());
+  }
+
 extern "C"
 void
 __gg__combined_datetime(cblc_field_t *dest,
-- 
2.34.1


Reply via email to