From: Robert Dubner <[email protected]>
Date: Tue, 29 Sep 2026 22:20:20 -0400
Subject: [PATCH] cobol: Decrease middle-end processing time with better
GENERIC.
Diving into the GIMPLE dumps revealed a lot of time was spent processing
COBOL variable initialization. One set of these changes collect
variable initialization into table-driven calls to the library. Another
set combines GENERIC nodes into batches that are processed as groups.
The results reduce middle-end time by factors of three to five, although
it remains highly dependent on the source code being processed.
Assisted by ChatGPT 6 Astra
gcc/cobol/ChangeLog:
* genapi.cc (propogate_linkage_offsets): Spelling fixed.
(collect_linkage_offsets): Use tables of offsets into COBOL
hierarchical variables.
(propagate_linkage_offsets): Likewise.
(establish_using): Use corrected spelling.
(parser_set_pointers): Likewise.
* gengen.cc (struct gg_init_batch_stats): Batch adjacent clean
initializations without moving them across executable statements.
(gg_init_batch_candidate): Likewise.
(gg_init_batch_static_decl): Likewise.
(gg_init_batch_call): Likewise.
(gg_batch_initializations): Likewise.
(gg_finalize_function): Likewise.
* move.cc (mh_alpha_to_alpha): Use runtime memset() instead of
using
build_string_literal on strings of spaces.
libgcobol/ChangeLog:
* libgcobol.cc (__gg__propagate_linkage_offsets): New routine for
applying the tables of offsets.
---
gcc/cobol/genapi.cc | 130 +++++++++++++++-----
gcc/cobol/gengen.cc | 261 +++++++++++++++++++++++++++++++++++++++++
gcc/cobol/move.cc | 159 ++++++++++++++++---------
libgcobol/libgcobol.cc | 22 ++++
4 files changed, 486 insertions(+), 86 deletions(-)
diff --git a/gcc/cobol/genapi.cc b/gcc/cobol/genapi.cc
index c0f2b448964..5c14b9b9865 100644
--- a/gcc/cobol/genapi.cc
+++ b/gcc/cobol/genapi.cc
@@ -6323,60 +6323,134 @@ parser_arith_error_end(cbl_label_t
*arithmetic_label)
gg_append_statement( arithmetic_label->structs.arith_error->over.label
);
}
+// Preserve the existing traversal, including repeated entries from
REDEFINES.
static void
-propogate_linkage_offsets(cbl_field_t *field, tree base)
+collect_linkage_offsets(cbl_field_t *field,
+ tree base,
+ std::vector<cbl_field_t *> &fields)
{
if( field->level == LEVEL01 || field->level == LEVEL77 )
{
field->data_decl_node = base;
- symbol_elem_t *e = symbol_at(field_index(field));
- // We already updated the data pointer of the first element:
- e += 1;
+ symbol_elem_t *e = symbol_at(field_index(field)) + 1;
while( e < symbols_end() )
{
- symbol_elem_t& element = *e++;
+ symbol_elem_t &element = *e++;
if( element.type == SymField )
{
cbl_field_t *this_one = cbl_field_of(&element);
if( this_one->level == LEVEL01 || this_one->level == LEVEL77 )
{
- // We have encountered another level 01/77. If this LEVEL 01
had a
- // parent, then we have to assume that this is a redefines of
another
- // level 01/77.
if( this_one->parent )
{
- // And, gloriously and frighteningly, it can be handled by
- // recursion:
- propogate_linkage_offsets(this_one, base);
+ collect_linkage_offsets(this_one, base, fields);
}
else
{
- // Having encountered the next 01 or 77, we are done
break;
}
}
if( this_one->level == 00 )
{
- // Ignore LEVEL00 "INDEXED BY" variables
continue;
}
- tree offset = gg_define_variable(SIZE_T);
- IF( base, eq_op, gg_cast(UCHAR_P, null_pointer_node) )
- {
- gg_assign(offset, size_t_zero_node);
- }
- ELSE
- {
- gg_assign(offset, member(this_one, "offset"));
- }
- ENDIF
this_one->data_decl_node = base;
- member( this_one,
- "data",
- gg_add(base, offset));
+ fields.push_back(this_one);
+ }
+ }
+ }
+ }
+
+static void
+propagate_linkage_offsets(cbl_field_t *field, tree base)
+ {
+ std::vector<cbl_field_t *> fields;
+ collect_linkage_offsets(field, base, fields);
+ if( fields.empty() )
+ {
+ return;
+ }
+
+ // Both current callers supply an addressable pointer: a local variable
+ // for USING, or a descriptor's data member for SET ADDRESS OF.
+ // Retain the old emission for other expressions or nonstatic
descriptors.
+ bool batchable = TREE_TYPE(base) == UCHAR_P
+ && !TREE_THIS_VOLATILE(base)
+ && (TREE_CODE(base) == VAR_DECL
+ || TREE_CODE(base) == PARM_DECL
+ || TREE_CODE(base) == COMPONENT_REF);
+ for( size_t i = 0; i < fields.size(); ++i )
+ {
+ tree decl = fields[i]->var_decl_node;
+ if( !decl
+ || TREE_CODE(decl) != VAR_DECL
+ || !TREE_STATIC(decl)
+ || DECL_EXTERNAL(decl)
+ || DECL_WEAK(decl)
+ || DECL_THREAD_LOCAL_P(decl)
+ || DECL_HAS_VALUE_EXPR_P(decl)
+ || TREE_THIS_VOLATILE(decl) )
+ {
+ batchable = false;
+ break;
+ }
+ }
+
+ if( !batchable )
+ {
+ for( size_t i = 0; i < fields.size(); ++i )
+ {
+ cbl_field_t *this_one = fields[i];
+ tree offset = gg_define_variable(SIZE_T);
+ IF( base, eq_op, gg_cast(UCHAR_P, null_pointer_node) )
+ {
+ gg_assign(offset, size_t_zero_node);
+ }
+ ELSE
+ {
+ gg_assign(offset, member(this_one, "offset"));
}
+ ENDIF
+ member(this_one, "data", gg_add(base, offset));
}
+ return;
}
+
+ static size_t table_number = 0;
+ char name[96];
+ snprintf(name, sizeof(name),
+ "..linkage_offsets_" HOST_SIZE_T_PRINT_DEC,
+ (fmt_size_t)table_number++);
+
+ tree array_type = build_array_type_nelts(VOID_P, fields.size());
+ vec<constructor_elt, va_gc> *elts = NULL;
+ for( size_t i = 0; i < fields.size(); ++i )
+ {
+ CONSTRUCTOR_APPEND_ELT(
+ elts,
+ build_int_cst_type(SIZE_T, i),
+ gg_cast(VOID_P, gg_get_address_of(fields[i]->var_decl_node)));
+ }
+ tree initializer = build_constructor(array_type, elts);
+ gcc_assert(TREE_CONSTANT(initializer));
+ TREE_STATIC(initializer) = 1;
+
+ tree table = gg_declare_variable(array_type, name, NULL_TREE,
+ vs_file_static);
+ DECL_INITIAL(table) = initializer;
+ TREE_READONLY(table) = 1;
+ DECL_ARTIFICIAL(table) = 1;
+ DECL_IGNORED_P(table) = 1;
+ gg_define_from_declaration(table);
+
+ // Pass the location of the base, not a snapshot of its value. This
+ // preserves repeated reads if a preceding descriptor store changes it.
+ gg_call(VOID,
+ "__gg__propagate_linkage_offsets",
+ gg_pointer_to_array(table),
+ gg_get_address_of(base),
+ build_int_cst_type(SIZE_T, fields.size()),
+ NULL_TREE);
}
static bool initialized_data = false;
@@ -6663,7 +6737,7 @@ establish_using(size_t nusing,
// We need to apply reference + offset to the LINKAGE variable
// and all of its children
- propogate_linkage_offsets( args[i].field(), reference );
+ propagate_linkage_offsets( args[i].field(), reference );
}
if( crv == by_value_e )
@@ -13790,7 +13864,7 @@ parser_set_pointers( size_t ntgt, cbl_refer_t
*tgts, cbl_refer_t source )
// When SET ADDRESS OF TARGET TO ..., the library call sets
// tgts[i].field->data. We need to propagate the data+offset
// through the level01 variable's children:
- propogate_linkage_offsets(tgts[i].field,
+ propagate_linkage_offsets(tgts[i].field,
member(tgts[i].field->var_decl_node,
"data"));
}
}
diff --git a/gcc/cobol/gengen.cc b/gcc/cobol/gengen.cc
index 479e0e5ba35..bfbe022c7e1 100644
--- a/gcc/cobol/gengen.cc
+++ b/gcc/cobol/gengen.cc
@@ -2875,6 +2875,251 @@ gg_modify_function_type(tree function_decl, tree
return_type)
DECL_RESULT (function_decl) = resdecl;
}
+/* Batch adjacent clean initializations without moving them across
executable
+ statements. This runs on completed GENERIC, not on the emission
stacks. */
+struct gg_init_batch_stats
+ {
+ unsigned long eligible = 0;
+ unsigned long batches = 0;
+ unsigned long eliminated = 0;
+ };
+
+static bool
+gg_init_batch_candidate(tree stmt, tree &field, tree &flags)
+ {
+ if( TREE_CODE(stmt) != CALL_EXPR || call_expr_nargs(stmt) != 2 )
+ {
+ return false;
+ }
+
+ tree callee = CALL_EXPR_FN(stmt);
+ STRIP_NOPS(callee);
+ if( TREE_CODE(callee) != ADDR_EXPR )
+ {
+ return false;
+ }
+ callee = TREE_OPERAND(callee, 0);
+ if( TREE_CODE(callee) != FUNCTION_DECL
+ || !DECL_NAME(callee)
+ || strcmp(IDENTIFIER_POINTER(DECL_NAME(callee)),
+ "__gg__initialize_variable_clean") != 0 )
+ {
+ return false;
+ }
+
+ flags = CALL_EXPR_ARG(stmt, 1);
+ if( TREE_CODE(flags) != INTEGER_CST )
+ {
+ return false;
+ }
+
+ tree address = CALL_EXPR_ARG(stmt, 0);
+ STRIP_NOPS(address);
+ if( TREE_CODE(address) != ADDR_EXPR )
+ {
+ return false;
+ }
+ field = TREE_OPERAND(address, 0);
+ if( TREE_CODE(field) != VAR_DECL
+ || !TREE_STATIC(field)
+ || DECL_EXTERNAL(field)
+ || DECL_WEAK(field)
+ || DECL_THREAD_LOCAL_P(field)
+ || DECL_HAS_VALUE_EXPR_P(field) )
+ {
+ return false;
+ }
+ return true;
+ }
+
+static bool
+gg_init_batch_static_decl(tree stmt)
+ {
+ if( TREE_CODE(stmt) != DECL_EXPR )
+ {
+ return false;
+ }
+ tree decl = DECL_EXPR_DECL(stmt);
+ if( TREE_CODE(decl) != VAR_DECL
+ || !TREE_STATIC(decl)
+ || DECL_EXTERNAL(decl)
+ || DECL_THREAD_LOCAL_P(decl)
+ || DECL_HAS_VALUE_EXPR_P(decl) )
+ {
+ return false;
+ }
+ tree initial = DECL_INITIAL(decl);
+ return !initial
+ || (initial != error_mark_node
+ && TREE_CONSTANT(initial)
+ && !TREE_SIDE_EFFECTS(initial));
+ }
+
+static tree
+gg_init_batch_call(const std::vector<tree> &fields,
+ tree flags, location_t location)
+ {
+ static unsigned long serial = 0;
+ char name[80];
+ snprintf(name, sizeof(name), "..initialization_batch_%lu", ++serial);
+
+ /* Match the existing parser_init_list's array of VOID_P. Only
addresses
+ of non-TLS static objects are accepted above, so this needs no
runtime
+ table construction. Keep the null terminator required by the
helper. */
+ tree array_type = build_array_type_nelts(VOID_P, fields.size() + 1);
+ tree initial = make_node(CONSTRUCTOR);
+ TREE_TYPE(initial) = array_type;
+ TREE_CONSTANT(initial) = 1;
+ TREE_STATIC(initial) = 1;
+ for( size_t i = 0; i < fields.size(); ++i )
+ {
+ tree address = build1(ADDR_EXPR,
+ build_pointer_type(TREE_TYPE(fields[i])),
+ fields[i]);
+ TREE_ADDRESSABLE(fields[i]) = 1;
+ CONSTRUCTOR_APPEND_ELT(CONSTRUCTOR_ELTS(initial),
+ build_int_cst_type(SIZE_T, i),
+ fold_convert(VOID_P, address));
+ }
+ CONSTRUCTOR_APPEND_ELT(CONSTRUCTOR_ELTS(initial),
+ build_int_cst_type(SIZE_T, fields.size()),
+ build_int_cst(VOID_P, 0));
+
+ /* File scope is intentional: the function's context stacks are already
+ closed. Register the fully initialized declaration through the
usual
+ file-scope machinery, which does not append a runtime statement. */
+ tree table = gg_declare_variable(array_type, name, initial,
vs_file_static);
+ TREE_READONLY(table) = 1;
+ TREE_ADDRESSABLE(table) = 1;
+ DECL_ARTIFICIAL(table) = 1;
+ DECL_IGNORED_P(table) = 1;
+ gg_define_from_declaration(table);
+
+ tree address = build1(ADDR_EXPR, build_pointer_type(array_type),
table);
+ tree pointer = fold_convert(build_pointer_type(VOID_P), address);
+ tree call = gg_call_expr(VOID, "__gg__variables_to_init",
+ pointer, flags, NULL_TREE);
+ SET_EXPR_LOCATION(call, location);
+ return call;
+ }
+
+static void
+gg_batch_initializations(tree &node, gg_init_batch_stats &stats)
+ {
+ if( !node )
+ {
+ return;
+ }
+ switch( TREE_CODE(node) )
+ {
+ case STATEMENT_LIST:
+ {
+ tree result = alloc_stmt_list();
+ std::vector<tree> pending;
+ std::vector<tree> fields;
+ tree run_flags = NULL_TREE;
+ location_t location = UNKNOWN_LOCATION;
+
+ auto flush = [&]()
+ {
+ if( fields.size() < 2 )
+ {
+ for( tree stmt : pending )
+ {
+ append_to_statement_list_force(stmt, &result);
+ }
+ }
+ else
+ {
+ /* Retain static declarations in their original order. They
have
+ no runtime effects; emit the batch after these declarations.
*/
+ for( tree stmt : pending )
+ {
+ if( TREE_CODE(stmt) == DECL_EXPR )
+ {
+ append_to_statement_list_force(stmt, &result);
+ }
+ }
+ append_to_statement_list_force(
+ gg_init_batch_call(fields, run_flags, location), &result);
+ ++stats.batches;
+ stats.eliminated += fields.size() - 1;
+ }
+ pending.clear();
+ fields.clear();
+ run_flags = NULL_TREE;
+ };
+
+ for( tree_stmt_iterator it = tsi_start(node);
+ !tsi_end_p(it); tsi_next(&it) )
+ {
+ tree stmt = tsi_stmt(it);
+ tree field = NULL_TREE;
+ tree flags = NULL_TREE;
+ if( gg_init_batch_candidate(stmt, field, flags) )
+ {
+ ++stats.eligible;
+ if( run_flags
+ && (TREE_TYPE(run_flags) != TREE_TYPE(flags)
+ || !tree_int_cst_equal(run_flags, flags)) )
+ {
+ flush();
+ }
+ if( fields.empty() )
+ {
+ run_flags = flags;
+ location = EXPR_LOCATION(stmt);
+ }
+ pending.push_back(stmt);
+ fields.push_back(field);
+ }
+ else if( !fields.empty() && gg_init_batch_static_decl(stmt) )
+ {
+ pending.push_back(stmt);
+ }
+ else
+ {
+ flush();
+ gg_batch_initializations(stmt, stats);
+ append_to_statement_list_force(stmt, &result);
+ }
+ }
+ flush();
+ node = result;
+ break;
+ }
+
+ case BIND_EXPR:
+ gg_batch_initializations(BIND_EXPR_BODY(node), stats);
+ break;
+
+ case COND_EXPR:
+ /* Never batch across the condition or between its alternatives.
*/
+ gg_batch_initializations(TREE_OPERAND(node, 1), stats);
+ gg_batch_initializations(TREE_OPERAND(node, 2), stats);
+ break;
+
+ case LOOP_EXPR:
+ gg_batch_initializations(LOOP_EXPR_BODY(node), stats);
+ break;
+
+ case SWITCH_EXPR:
+ gg_batch_initializations(SWITCH_BODY(node), stats);
+ break;
+
+ case TRY_FINALLY_EXPR:
+ case TRY_CATCH_EXPR:
+ gg_batch_initializations(TREE_OPERAND(node, 0), stats);
+ gg_batch_initializations(TREE_OPERAND(node, 1), stats);
+ break;
+
+ default:
+ /* In particular, do not walk declarations, initializers, arbitrary
+ value expressions, or nested function declarations. */
+ break;
+ }
+ }
+
void
gg_finalize_function()
{
@@ -2887,6 +3132,22 @@ gg_finalize_function()
// Finish off the context
gg_pop_context();
+ /* Work on the completed body. Do not use gg_append_statement here:
+ gg_pop_context has closed the emission stacks. */
+ if( !getenv("GCOBOL_INIT_BATCH_DISABLE") )
+ {
+ gg_init_batch_stats stats;
+ gg_batch_initializations(
+ DECL_SAVED_TREE(current_function->function_decl), stats);
+ if( getenv("GCOBOL_INIT_BATCH_REPORT") )
+ {
+ fprintf(stderr,
+ "gcobol initialization batching: eligible=%lu batches=%lu "
+ "eliminated=%lu\n",
+ stats.eligible, stats.batches, stats.eliminated);
+ }
+ }
+
/* Because COBOL functions can be misleadingly referenced before they
defined, and because our compiler is single pass, we need to defer
actually passing the function_decls to the middle end until we are
diff --git a/gcc/cobol/move.cc b/gcc/cobol/move.cc
index 685fee28705..088be4233ca 100644
--- a/gcc/cobol/move.cc
+++ b/gcc/cobol/move.cc
@@ -2359,6 +2359,10 @@ mh_alpha_to_alpha(const cbl_refer_t &destref,
&& !sourceref.all
)
{
+ charmap_t *charmap
=__gg__get_charmap(destref.field->codeset.encoding);
+ cbl_char_t space = charmap->mapped_character(ascii_space);
+ int stride = charmap->stride();
+
void (*mover)(tree, tree, tree); // dest, source, count
mover = have_common_parent(destref, sourceref) ? gg_memmove :
gg_memcpy;
@@ -2378,26 +2382,41 @@ mh_alpha_to_alpha(const cbl_refer_t &destref,
{
// This is a tad more complicated. The source is too short, so
we need
// to copy over what we can...
- mover(member( destref.field->var_decl_node, "data"),
- member(sourceref.field->var_decl_node, "data"),
- build_int_cst_type(SIZE_T,
sourceref.field->data.capacity()));
+ tree source_len = build_int_cst_type(SIZE_T,
+
sourceref.field->data.capacity());
+ mover(member(destref.field->var_decl_node, "data"),
+ member(sourceref.field->var_decl_node, "data"),
+ source_len);
+
// And then space-fill the rest:
- size_t fill_bytes =
- destref.field->data.capacity() -
sourceref.field->data.capacity();
-
- // ...and then create a memory area with the fill spaces...
- char *spaces = static_cast<char *>(xmalloc(fill_bytes));
- charmap_t *charmap
=__gg__get_charmap(destref.field->codeset.encoding);
- charmap->memset(spaces,
- charmap->mapped_character(ascii_space),
- fill_bytes);
- // ...and then copy those spaces into place.
- mover(
- gg_add(member(destref.field->var_decl_node, "data"),
- build_int_cst_type(SIZE_T,
sourceref.field->data.capacity())),
- build_string_literal(fill_bytes, spaces),
- build_int_cst_type(SIZE_T, fill_bytes));
- free(spaces);
+ if( stride == 1 )
+ {
+ tree dest_len = build_int_cst_type(SIZE_T,
+
destref.field->data.capacity());
+ gg_memset(gg_add(member(destref.field->var_decl_node, "data"),
+ source_len),
+ build_int_cst_type(INT, space),
+ gg_subtract(dest_len, source_len));
+ }
+ else
+ {
+ // And then space-fill the rest:
+ size_t fill_bytes =
+ destref.field->data.capacity() -
sourceref.field->data.capacity();
+
+ // ...and then create a memory area with the fill spaces...
+ char *spaces = static_cast<char *>(xmalloc(fill_bytes));
+ charmap->memset(spaces,
+ space,
+ fill_bytes);
+ // ...and then copy those spaces into place.
+ mover(
+ gg_add(member(destref.field->var_decl_node, "data"),
+ build_int_cst_type(SIZE_T,
sourceref.field->data.capacity())),
+ build_string_literal(fill_bytes, spaces),
+ build_int_cst_type(SIZE_T, fill_bytes));
+ free(spaces);
+ }
moved = true;
}
}
@@ -2428,19 +2447,27 @@ mh_alpha_to_alpha(const cbl_refer_t &destref,
// The source data is too short. We need to copy over what we
have...
mover(dest_data, source_data, source_len);
- // And then right-fill the remainder with spaces. Create a buffer
with
- // more than enough spaces for our purposes:
- size_t fill_bytes = destref.field->data.capacity();
- char *spaces = static_cast<char *>(xmalloc(fill_bytes));
- charmap_t *charmap
=__gg__get_charmap(destref.field->codeset.encoding);
- charmap->memset(spaces,
- charmap->mapped_character(ascii_space),
- fill_bytes);
- // And then copy enough of those spaces into place.
- mover(gg_add(dest_data, source_len),
- build_string_literal(fill_bytes, spaces),
- gg_subtract(dest_len, source_len));
- free(spaces);
+ // And then right-fill the remainder with spaces.
+ if( stride == 1 )
+ {
+ gg_memset(gg_add(dest_data, source_len),
+ build_int_cst_type(INT, space),
+ gg_subtract(dest_len, source_len));
+ }
+ else
+ {
+ // Create a buffer with more than enough spaces for our
purposes:
+ size_t fill_bytes = destref.field->data.capacity();
+ char *spaces = static_cast<char *>(xmalloc(fill_bytes));
+ charmap->memset(spaces,
+ space,
+ fill_bytes);
+ // And then copy enough of those spaces into place.
+ mover(gg_add(dest_data, source_len),
+ build_string_literal(fill_bytes, spaces),
+ gg_subtract(dest_len, source_len));
+ free(spaces);
+ }
}
ENDIF
moved = true;
@@ -2470,19 +2497,27 @@ mh_alpha_to_alpha(const cbl_refer_t &destref,
// The source data is too short. We need to copy over what we
have...
mover(dest_data, source_data, source_len);
- // And then right-fill the remainder with spaces. Create a buffer
with
- // more than enough spaces for our purposes:
- size_t fill_bytes = destref.field->data.capacity();
- char *spaces = static_cast<char *>(xmalloc(fill_bytes));
- charmap_t *charmap
=__gg__get_charmap(destref.field->codeset.encoding);
- charmap->memset(spaces,
- charmap->mapped_character(ascii_space),
- fill_bytes);
- // And then copy enough of those spaces into place.
- mover(gg_add(dest_data, source_len),
- build_string_literal(fill_bytes, spaces),
- gg_subtract(dest_len, source_len));
- free(spaces);
+ // And then right-fill the remainder with spaces.
+ if( stride == 1 )
+ {
+ gg_memset(gg_add(dest_data, source_len),
+ build_int_cst_type(INT, space),
+ gg_subtract(dest_len, source_len));
+ }
+ else
+ {
+ // Create a buffer with more than enough spaces for our
purposes:
+ size_t fill_bytes = destref.field->data.capacity();
+ char *spaces = static_cast<char *>(xmalloc(fill_bytes));
+ charmap->memset(spaces,
+ space,
+ fill_bytes);
+ // And then copy enough of those spaces into place.
+ mover(gg_add(dest_data, source_len),
+ build_string_literal(fill_bytes, spaces),
+ gg_subtract(dest_len, source_len));
+ free(spaces);
+ }
}
ENDIF
@@ -2516,19 +2551,27 @@ mh_alpha_to_alpha(const cbl_refer_t &destref,
// The source data is too short. We need to copy over what we
have...
mover(dest_data, source_data, source_len);
- // And then right-fill the remainder with spaces. Create a buffer
with
- // more than enough spaces for our purposes:
- size_t fill_bytes = destref.field->data.capacity();
- char *spaces = static_cast<char *>(xmalloc(fill_bytes));
- charmap_t *charmap
=__gg__get_charmap(destref.field->codeset.encoding);
- charmap->memset(spaces,
- charmap->mapped_character(ascii_space),
- fill_bytes);
- // And then copy enough of those spaces into place.
- mover(gg_add(dest_data, source_len),
- build_string_literal(fill_bytes, spaces),
- gg_subtract(dest_len, source_len));
- free(spaces);
+ // And then right-fill the remainder with spaces.
+ if( stride == 1 )
+ {
+ gg_memset(gg_add(dest_data, source_len),
+ build_int_cst_type(INT, space),
+ gg_subtract(dest_len, source_len));
+ }
+ else
+ {
+ // Create a buffer with more than enough spaces for our
purposes:
+ size_t fill_bytes = destref.field->data.capacity();
+ char *spaces = static_cast<char *>(xmalloc(fill_bytes));
+ charmap->memset(spaces,
+ space,
+ fill_bytes);
+ // And then copy enough of those spaces into place.
+ mover(gg_add(dest_data, source_len),
+ build_string_literal(fill_bytes, spaces),
+ gg_subtract(dest_len, source_len));
+ free(spaces);
+ }
}
ENDIF
diff --git a/libgcobol/libgcobol.cc b/libgcobol/libgcobol.cc
index c0924a81a2e..ca3adf82bc6 100644
--- a/libgcobol/libgcobol.cc
+++ b/libgcobol/libgcobol.cc
@@ -11522,6 +11522,28 @@ __gg__function_handle_from_name(int
program_id,
return retval;
}
+extern "C"
+void
+__gg__propagate_linkage_offsets(void * const fields[],
+ unsigned char * const *base_address,
+ size_t count)
+ {
+ for( size_t i = 0; i < count; ++i )
+ {
+ cblc_field_t *field = static_cast<cblc_field_t *>(fields[i]);
+ unsigned char *base = *base_address;
+ if( base )
+ {
+ field->data = base + field->offset;
+ }
+ else
+ {
+ // Do not read offset, or perform pointer arithmetic, for a null
base.
+ field->data = nullptr;
+ }
+ }
+ }
+
extern "C"
void
__gg__variables_to_init(cblc_field_t *array[], const int flag_bits)
--
2.34.1