blob: 3ad33442119c8fb505d553543ba4e7112302c2a2 [file] [log] [blame]
/*
* Copyright (c) 2021-2025 Symas Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of the Symas Corporation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* The compilation process consists of
1) lexing
2) parsing
3) generation of the GENERIC abstract syntax tree
4) reduction
5) generation of machine code
For your sins, you have wandered into the code that accepts information from
the parser about what the COBOL source code wants done.
Specifically, the routines in this module, which run at compile time, generate
the GENERIC tags that describe the equivalent of the COBOL. They are rathernnn
low level routines, ultimately used for pretty much everything. Specifically,
they run at compile-time, and they generate the GENERIC tags that control what
ultimately happens at run-time.
It *is* confusing.
I'll try to collect things in a logical way, and name them in a logical way,
and I'll try to comment them well enough so that you have some hope of
understanding what the heck is going on.
There is some information in the GCC internals document, but it was written by
people who live and breathe this stuff, and they don't remember what it was like
to know nothing.
I suspect that those who have tried and failed to create GCC front ends have foundered because
they just couldn't figure out what it was they needed to do. I certainly floundered
for several days before I hit on the means to figure it out. I created the
rjd_print_tree() routine, which spits out a text listing of all the nodes
connected to the specified starting node. (Keep in mind that the GENERIC graph
is cyclic, and consequently there is no real ordering, except that the starting
node you specify is NodeNumber0. rjd_print_tree follows all links, but it prints
out each unique node exactly once.)
I then built into GCC a call to rjd_print_tree right at the point where the GENERIC tree
is complete and about to be reduced.
And that gave me the ability to create simple C programs and see the resulting GENERIC
tree. It took a while to sort out what I was seeing, but ultimately things started
to make sense. The inherent difficulty may start to become clear when you realize that
the program
void foo()
{
}
is implemented by a GENERIC tree with fifty-six nodes.
I can't try to write a whole manual here. But hopefully there will be enough examples
throughout the code for you to learn how to do things on a highish level, and you can
look at the low -level routines to see how it is accomplished.
That said, I will try to comment things well enough to be meaningful at least to me
when I run across them at some time in the future. Because I fear that whatever
I do here, the world will little note, and *I* will not long remember, what it was!
*/
#include "cobol-system.h"
#include "coretypes.h"
#include "tree.h"
#include "tree-iterator.h"
#include "stringpool.h"
#include "cgraph.h"
#include "toplev.h"
#include "function.h"
#include "fold-const.h"
#include "../../libgcobol/ec.h"
#include "../../libgcobol/common-defs.h"
#include "util.h"
#include "cbldiag.h"
#include "symbols.h"
#include "gengen.h"
#include "dumpfile.h"
// We are limiting the programmer to functions with 512 or fewer arguments.
// Don't like it? Cry me a river.
static const int ARG_LIMIT = 512;
// These are globally useful constants
tree char_nodes[256];
tree pvoid_type_node;
tree integer_minusone_node;
tree integer_two_node;
tree integer_eight_node;
tree size_t_zero_node;
tree int128_zero_node;
tree int128_five_node;
tree int128_ten_node;
tree char_ptr_type_node;
tree uchar_ptr_type_node;
tree wchar_ptr_type_node;
tree long_double_ten_node;
tree sizeof_size_t;
tree sizeof_pointer;
tree bool_true_node;
tree bool_false_node;
// This is the global translation unit structure; it contains everything needed
// to compile one file that you might otherwise be tempted to instantiate as
// global variables:
struct cbl_translation_unit_t gg_trans_unit;
// This set is used to prevent duplicated top-level program names from breaking
// the compiler when a source code module makes that mistake.
static std::unordered_set<std::string> names_we_have_seen;
// This vector is used to process the function_decls at the point we leave
// the file.
static std::vector<tree> finalized_function_decls;
void
gg_build_translation_unit(const char *filename)
{
// The translation_unit_decl gets declared once for each processing source
// input file. It serves as an anchor for each function. And the
// block referred to by its "initial" member is the anchor for any
// variables whose scope is file.
gg_trans_unit.trans_unit_decl
= build_translation_unit_decl(get_identifier(filename));
gg_trans_unit.filename = filename;
tree tree_block = make_node(BLOCK);
BLOCK_SUPERCONTEXT(tree_block)
= gg_trans_unit.trans_unit_decl;
TREE_USED(tree_block) = 1;
DECL_INITIAL(gg_trans_unit.trans_unit_decl) = tree_block;
}
// Explanation of context. There is a plate of spaghetti that represents
// a chain of contexts.
// The deconstructed dinner: The function_decl "initial" points to a block
// The block points to the first of a chained set of var_decl, one for each
// variable in the block. The function "saved_tree" entry points to a
// bind_expr. The bind_expr vars member points to the same chain of var_decl.
// The bind_expr block member points to the block. And the bind_expr body
// member points to the statement_list for the context.
// Those four tags constitute the context. To push the context, a new block
// is chained to the first blocks SUBCHAIN member. A new bind_expr is created
// and put on the statement_list of the enclosing block. And a new list of
// var_decls is set up for the new block and the new bind_expr.
// And that's how subcontexts are made.
static void
gg_chain_onto_block_vars(tree block, tree var)
{
// In order to use a variable in a context, the var_decl has to go
// onto the chain that starts with the "vars" entry of a block
// Upon discovering that chainon has O(N-squared) complexity because it walks
// the entire chain looking for the final member, Dubner put in this map.
static std::unordered_map<tree, tree>blocks;
if( !BLOCK_VARS(block) )
{
// This is the first variable:
BLOCK_VARS(block) = var;
blocks[block] = var;
}
else
{
//chainon(BLOCK_VARS(block), var);
// What follows is the quicker equivalent of calling chainon()
TREE_CHAIN(blocks[block]) = var;
blocks[block] = var;
}
}
void
gg_append_var_decl(tree var_decl)
{
// The var_decl has to be chained onto the appropriate block.
if( SCOPE_FILE_SCOPE_P(DECL_CONTEXT(var_decl)) )
{
tree context = gg_trans_unit.trans_unit_decl;
tree block = DECL_INITIAL(context);
gg_chain_onto_block_vars(block, var_decl);
rest_of_decl_compilation (var_decl, true, false);
// With global variables, it is probably necessary to do something with
// wrapup_global_declarations. At this writing, I have not yet
// investigated that. The advice from gcc@gcc.gnu.org came from
// David Malcolm:
/*
You might find libgccjit's gcc/jit/jit-playback.cc helpful for this, as
it tends to contain minimal code to build trees (generally
simplified/reverse-engineered from the C frontend).
playback::context::global_new_decl makes the VAR_DECL node, and such
trees are added to the jit playback::context's m_globals.
In playback::context::replay, we have:
/ * Finalize globals. See how FORTRAN 95 does it in gfc_be_parse_file()
for a simple reference. * /
FOR_EACH_VEC_ELT (m_globals, i, global)
rest_of_decl_compilation (global, true, true);
wrapup_global_declarations (m_globals.address(), m_globals.length());
*/
// Stash this var_decl in a map so it can be found elsewhere:
//fprintf(stderr, "Stashing %s\n", IDENTIFIER_POINTER(DECL_NAME(var_decl)));
gg_trans_unit.trans_unit_var_decls
[IDENTIFIER_POINTER(DECL_NAME(var_decl))] = var_decl;
}
else
{
// For function-level variables, we use a stack of blocks to keep track
// of which block is active for the current context:
// fprintf(stderr, "%s(): %30s Function Scope\n", __func__, id_name);
tree bind_expr = current_function->bind_expr_stack.back();
tree block = BIND_EXPR_BLOCK(bind_expr);
gg_chain_onto_block_vars(block, var_decl);
// If saved_tree.bind_expr.vars is null, then var_decl is the very
// first variable in the block, and it must be set in bind_expr as well
if( !BIND_EXPR_VARS(bind_expr) )
{
BIND_EXPR_VARS(bind_expr) = var_decl;
}
}
}
void
gg_append_statement(tree stmt)
{
// Likewise, we have a stack of statement_lists, with the current one
// at the back. (The statement_list stack can get deeper than the block
// stack, because you can create a separate statement list for the insides
// of, say, a WHILE statement without creating a whole context for it)
// This statement list thing looks innocent enough, but it is the general
// way of actually having a GENERIC tree generate executing code. What goes
// onto a statement list is an expression. A = B is implemented with a
// modify_expr
// Actually instantiating a variable requires a var_expr
// A subroutine call is effected by putting a call_expr onto the statement
// list.
// It's not the only way; you can have a modify_expr that takes a var_decl
// as a destination, and uses a call_expr as a source. This requires that
// the type of the var_decl be the same as the type of the function being
// called.
// And so on. Just keep in mind that you have types, and declarations, and
// expressions, among other things.
// When trying to figure out location_t, take a look at
// ./libcpp/include/line-map.h
// ./libcpp/location-example.txt
gcc_assert( gg_trans_unit.function_stack.size() );
TREE_SIDE_EFFECTS(stmt) = 1; // If an expression has no side effects,
// // it won't generate code.
TREE_SIDE_EFFECTS(current_function->statement_list_stack.back()) = 1;
append_to_statement_list( stmt, &(current_function->statement_list_stack.back()) );
}
tree
gg_float(tree floating_type, tree integer_var)
{
// I don't know why, but this fails if 'var' is an INT128
return build1(FLOAT_EXPR, floating_type, integer_var);
}
tree
gg_trunc(tree integer_type, tree floating_var)
{
/* Conversion of real to fixed point by truncation. */
return build1(FIX_TRUNC_EXPR, integer_type, floating_var);
}
tree
gg_cast(tree type, tree var)
{
return fold_convert(type, var);
}
static bool saw_pointer;
static
tree
adjust_for_type(tree type)
{
tree retval;
switch( TREE_CODE(type) )
{
case POINTER_TYPE:
saw_pointer = true;
retval = adjust_for_type(TREE_TYPE(type));
break;
case COMPONENT_REF:
case ADDR_EXPR:
case ARRAY_TYPE:
case VAR_DECL:
case FUNCTION_TYPE:
retval = adjust_for_type(TREE_TYPE(type));
break;
case RECORD_TYPE:
default:
retval = type;
break;
}
return retval;
}
char *
gg_show_type(tree type)
{
if( !type )
{
cbl_internal_error("The given type is NULL, and that is just not fair");
}
if( DECL_P(type) )
{
type = TREE_TYPE(type);
}
if( !TYPE_P(type) )
{
cbl_internal_error("The given type is not a declaration or a TYPE");
}
static char ach[1100];
static char ach2[1024];
static char ach3[1024];
switch( TREE_CODE(type) )
{
case POINTER_TYPE:
strcpy(ach2, gg_show_type(TREE_TYPE(type)));
sprintf(ach, "POINTER to %s", ach2);
break;
case VOID_TYPE:
sprintf(ach, "VOID");
break;
case BOOLEAN_TYPE:
sprintf(ach, "BOOL");
break;
case RECORD_TYPE:
sprintf(ach, "RECORD");
break;
case REAL_TYPE:
sprintf(ach,
"%3" PRId64 "-bit REAL",
TREE_INT_CST_LOW(TYPE_SIZE(type)));
break;
case INTEGER_TYPE:
sprintf(ach,
"%3" PRId64 "-bit %s INT",
TREE_INT_CST_LOW(TYPE_SIZE(type)),
(TYPE_UNSIGNED(type) ? "unsigned" : " signed"));
break;
case FUNCTION_TYPE:
strcpy(ach3, gg_show_type(TREE_TYPE(type)));
sprintf(ach, "FUNCTION returning %s", ach3);
break;
default:
cbl_internal_error("Unknown type %d", TREE_CODE(type));
}
return ach;
}
tree
gg_assign(tree dest, const tree source)
{
// This does the equivalent of a C/C++ "dest = source". When X1 is set, it
// does some checking for conditions that can result in inefficient code, so
// that is useful during development when even an astute programmer might
// need an assist with keeping variable types straight.
// This routine also provides for the possibility that the assignment is
// for a source that is a function invocation, as in
// "dest = function_call()"
tree stmt = NULL_TREE;
saw_pointer = false;
tree dest_type = adjust_for_type(TREE_TYPE(dest));
saw_pointer = false;
tree source_type = adjust_for_type(TREE_TYPE(source));
bool p2 = saw_pointer;
bool okay = dest_type == source_type;
if( !okay )
{
if( TREE_CODE(dest_type) == INTEGER_TYPE
&& TREE_CODE(source_type) == INTEGER_TYPE
&& TREE_INT_CST_LOW(TYPE_SIZE(dest_type)) == TREE_INT_CST_LOW(TYPE_SIZE(source_type))
&& TYPE_UNSIGNED(dest_type) == TYPE_UNSIGNED(source_type) )
{
okay = true;
}
}
if( okay )
{
stmt = build2_loc(gg_token_location(),
MODIFY_EXPR,
TREE_TYPE(dest),
dest,
source);
gg_append_statement(stmt);
}
else
{
// We are doing an assignment where the left- and right-hand types are not
// the same. This is a compilation-time error, since we want the caller to
// have sorted the types out explicitly. If we don't throw an error here,
// the gimple reduction will do so. Better to do it here, when we know
// where we are.S
static const int debugging = 1;
if( debugging )
{
fprintf(stderr, "Inefficient assignment\n");
if(DECL_P(dest) && DECL_NAME(dest))
{
fprintf(stderr, " Destination is %s\n", IDENTIFIER_POINTER(DECL_NAME(dest)));
}
fprintf(stderr, " dest type is %s%s\n", gg_show_type(dest_type), p2 ? "_P" : "");
if(DECL_P(source) && DECL_NAME(source))
{
fprintf(stderr, " Source is %s\n", IDENTIFIER_POINTER(DECL_NAME(source)));
}
fprintf(stderr, " source type is %s%s\n", gg_show_type(source_type), p2 ? "_P" : "");
}
cbl_internal_error("Attempting an assignment of differing types.");
}
return stmt;
}
tree
gg_find_field_in_struct(const tree base, const char *field_name)
{
// Finds and returns the field_decl for the named member. 'base' can be
// a structure or a pointer to a structure.
tree type = TREE_TYPE(base);
tree rectype;
if( POINTER_TYPE_P (type) )
{
tree pointer_type = TREE_TYPE(base);
rectype = TREE_TYPE(pointer_type);
}
else
{
// Assuming a struct (or union), pick up the record_type
rectype = TREE_TYPE(base);
}
tree id_of_field = get_identifier(field_name);
tree field_decl = NULL_TREE;
tree next_value = TYPE_FIELDS(rectype);
// Look through the chain of fields for a match to ours. This is, in the
// limit, an O(N^2) computational burden. But structures usually small, so we
// probably don't have to figure out how to make it faster.
while( next_value )
{
if( DECL_NAME(next_value) == id_of_field )
{
field_decl = next_value;
break;
}
next_value = TREE_CHAIN(next_value);
}
if( !field_decl )
{
yywarn("Somebody asked for the field %s.%s, which does not exist",
IDENTIFIER_POINTER(DECL_NAME(base)),
field_name);
gcc_unreachable();
}
return field_decl;
}
static tree
gg_start_building_a_union(const char *type_name, tree type_context)
{
// type_context is current_function->function_decl for union local
// to a function.
// It is translation_unit_decl for unions common to all functions
// We want to return the type_decl for an empty union
// First, create the record_type whose values will eventually
// be the chain of of the struct's fields:
tree uniontype = make_node(UNION_TYPE);
TYPE_CONTEXT(uniontype) = type_context;
TYPE_SIZE_UNIT(uniontype) = integer_zero_node;
TYPE_SIZE(uniontype) = integer_zero_node;
TYPE_NAME(uniontype) = get_identifier(type_name);
TYPE_MODE_RAW(uniontype) = TYPE_MODE (intTI_type_node);
// We need a type_decl for the record_type:
tree typedecl = make_node(TYPE_DECL);
// The type of the type_decl is the record_type:
TREE_TYPE(typedecl) = uniontype;
SET_TYPE_ALIGN(uniontype, 16);
// The chain element of the record_type points back to the type_decl:
TREE_CHAIN(uniontype) = typedecl;
return typedecl;
}
static tree
gg_start_building_a_struct(const char *type_name, tree type_context)
{
// type_context is current_function->function_decl for structures local
// to a function.
// It is translation_unit_decl for structures common to all functions
// We want to return the type_decl for an empty struct
// First, create the record_type whose values will eventually
// be the chain of of the struct's fields:
tree recordtype = make_node(RECORD_TYPE);
TYPE_CONTEXT(recordtype) = type_context;
TYPE_SIZE_UNIT(recordtype) = integer_zero_node;
TYPE_SIZE(recordtype) = integer_zero_node;
TYPE_NAME(recordtype) = get_identifier(type_name);
TYPE_MODE_RAW(recordtype) = BLKmode;
// We need a type_decl for the record_type:
tree typedecl = make_node(TYPE_DECL);
// The type of the type_decl is the record_type:
TREE_TYPE(typedecl) = recordtype;
SET_TYPE_ALIGN(recordtype, 8);
// The chain element of the record_type points back to the type_decl:
TREE_CHAIN(recordtype) = typedecl;
return typedecl;
}
static void
gg_add_field_to_structure(const tree type_of_field, const char *name_of_field, tree struct_type_decl)
{
// We're given the struct_type_decl.
// Append the new field to that type_decl's record_type's chain:
tree struct_record_type = TREE_TYPE(struct_type_decl);
bool is_union = TREE_CODE((struct_record_type)) == UNION_TYPE;
tree id_of_field = get_identifier (name_of_field);
// Create the new field:
tree new_field_decl = build_decl( gg_token_location(),
FIELD_DECL,
id_of_field,
type_of_field);
// Establish the machine mode for the field_decl:
SET_DECL_MODE(new_field_decl, TYPE_MODE(type_of_field));
// Establish the context of the new field as being the record_type
DECL_CONTEXT (new_field_decl) = struct_record_type;
// Establish the size of the new field as being the same as its prototype:
DECL_SIZE(new_field_decl) = TYPE_SIZE(type_of_field); // This is in bits
DECL_SIZE_UNIT(new_field_decl) = TYPE_SIZE_UNIT(type_of_field); // This is in bytes
// We need to establish the offset and bit offset of the new node.
// Empirically, this seems to be done on 16-bit boundaries, with DECL_FIELD_OFFSET
// in units of N*16 bytes, and FIELD_BIT_OFFSET being offsets in bits from the DECL_FIELD_OFFSET
// We calculate our desired offset in bits:
// Pick up the current size, in bytes, of the record_type:
long offset_in_bytes = TREE_INT_CST_LOW(TYPE_SIZE_UNIT(struct_record_type));
static const int MAGIC_NUMBER_SIXTEEN = 16 ;
static const int BITS_IN_A_BYTE = 8 ;
// We know the offset_in_bytes, which is the size, of the structure with
// its current members.
//long type_size = TREE_INT_CST_LOW(TYPE_SIZE_UNIT(type_of_field));
long type_align_in_bits = TYPE_ALIGN(type_of_field);
long type_align_in_bytes = type_align_in_bits/BITS_IN_A_BYTE;
// As per the Amd64 ABI, we need to set the structure's type alignment to be
// that of most strictly aligned component:
// This is the current restriction:
long struct_align_in_bits = TYPE_ALIGN(TREE_TYPE(struct_type_decl));
if( type_align_in_bits > struct_align_in_bits )
{
// The new one is the new champion
SET_TYPE_ALIGN(TREE_TYPE(struct_type_decl), type_align_in_bits );
}
// We know struct_type_decl is a record_type, so we can sneak through this comparison
if( type_of_field == TREE_TYPE(struct_type_decl) )
{
printf(" It is a record_type\n");
}
// Bump up the offset until we are aligned:
while( offset_in_bytes % type_align_in_bytes)
{
offset_in_bytes += 1;
}
if( is_union )
{
// Turn that into the bytes/bits offsets of the new field:
DECL_FIELD_OFFSET(new_field_decl) = build_int_cst_type (SIZE_T, 0);
DECL_FIELD_BIT_OFFSET(new_field_decl) = build_int_cst_type (bitsizetype, 0);
// The size of a union is the size of its largest member:
offset_in_bytes = std::max(offset_in_bytes, (long)TREE_INT_CST_LOW(DECL_SIZE_UNIT(new_field_decl)));
}
else
{
// Turn that into the bytes/bits offsets of the new field:
long field_offset = (offset_in_bytes/MAGIC_NUMBER_SIXTEEN)*MAGIC_NUMBER_SIXTEEN;
long field_bit_offset = (offset_in_bytes - field_offset) * BITS_IN_A_BYTE;
DECL_FIELD_OFFSET(new_field_decl) = build_int_cst_type (SIZE_T, field_offset);;
DECL_FIELD_BIT_OFFSET(new_field_decl) = build_int_cst_type (bitsizetype, field_bit_offset);
// This was done empirically to make our generated code match that of a C program
SET_DECL_OFFSET_ALIGN(new_field_decl, 128);
// And now we need to update the size of the record type:
offset_in_bytes += TREE_INT_CST_LOW(DECL_SIZE_UNIT(new_field_decl));
}
TYPE_SIZE_UNIT(struct_record_type) = build_int_cst_type (SIZE_T, offset_in_bytes); // In bytes
TYPE_SIZE(struct_record_type) = build_int_cst_type (bitsizetype, offset_in_bytes*BITS_IN_A_BYTE); // In bits
if( !TYPE_FIELDS(struct_record_type) )
{
// This is the first variable of the chain:
TYPE_FIELDS(struct_record_type) = new_field_decl;
}
else
{
// We need to tack the new one onto an already existing chain:
chainon(TYPE_FIELDS(struct_record_type), new_field_decl);
}
}
void
gg_get_struct_type_decl(tree struct_type_decl, int count, va_list params)
{
while( count-- )
{
tree field_type = va_arg(params, tree);
const char *name = va_arg(params, const char *);
gg_add_field_to_structure(field_type, name, struct_type_decl);
}
// Note: On 2022-02-18 I removed the call to gg_append_var_decl, which
// chains the type_decl on the function block. I don't remember why I
// thought it was necessary. It makes no difference for COBOL compilations.
//
// But I must have copied it from a C compilation example.
//
// I removed it so that I could create type_decls outside of a function.
// I know not what the long-term implications might be.
//
// You have been served notice.
//
// struct_type_decl is the type_decl for our structure. We need to
// append it to the list of variables in order to use it:
// The following function call is misnamed. It can take struct type_decls
//gg_append_var_decl(struct_type_decl);
}
void
gg_get_union_type_decl(tree union_type_decl, int count, va_list params)
{
while( count-- )
{
tree field_type = va_arg(params, tree);
const char *name = va_arg(params, const char *);
gg_add_field_to_structure(field_type, name, union_type_decl);
}
}
tree
gg_get_local_struct_type_decl(const char *type_name, int count, ...)
{
tree struct_type_decl = gg_start_building_a_struct(type_name, current_function->function_decl);
va_list params;
va_start(params, count);
gg_get_struct_type_decl(struct_type_decl, count, params);
va_end(params);
// To use the struct_type_decl, you'll need to execute
// the following to turn it into a var_decl:
// tree var_decl = gg_define_variable( TREE_TYPE(struct_type_decl),
// var_name,
// vs_static);
return struct_type_decl;
}
tree
gg_get_filelevel_struct_type_decl(const char *type_name, int count, ...)
{
tree struct_type_decl = gg_start_building_a_struct(type_name, gg_trans_unit.trans_unit_decl);
va_list params;
va_start(params, count);
gg_get_struct_type_decl(struct_type_decl, count, params);
va_end(params);
// To use the struct_type_decl, you'll need to execute
// the following to turn it into a var_decl:
// tree var_decl = gg_define_variable( TREE_TYPE(struct_type_decl),
// var_name,
// vs_static);
return struct_type_decl;
}
tree
gg_get_filelevel_union_type_decl(const char *type_name, int count, ...)
{
tree struct_type_decl = gg_start_building_a_union(type_name, gg_trans_unit.trans_unit_decl);
va_list params;
va_start(params, count);
gg_get_union_type_decl(struct_type_decl, count, params);
va_end(params);
// To use the struct_type_decl, you'll need to execute
// the following to turn it into a var_decl:
// tree var_decl = gg_define_variable( TREE_TYPE(struct_type_decl),
// var_name,
// vs_static);
return struct_type_decl;
}
tree
gg_define_local_struct(const char *type_name, const char * var_name, int count, ...)
{
// Builds a structure, declares it as a static variable in the current function,
// and returns the var_decl for it.
tree struct_type_decl = gg_start_building_a_struct(type_name, current_function->function_decl);
va_list params;
va_start(params, count);
gg_get_struct_type_decl(struct_type_decl, count, params);
va_end(params);
// We now have a complete struct_type_decl, whose TREE_TYPE is the
// the type we need when declaring it.
// And with that done, we can actually define the storage:
tree var_decl = gg_define_variable( TREE_TYPE(struct_type_decl),
var_name,
vs_static);
return var_decl;
}
tree
gg_struct_field_ref(const tree base, const char *field)
{
tree retval;
tree type = TREE_TYPE(base);
if( POINTER_TYPE_P (type) )
{
tree pointer_type = TREE_TYPE(base);
tree base_pointer_type = TREE_TYPE(pointer_type);
// We need a COMPONENT_REF which is an INDIRECT_REF to a FIELD_DECL
tree field_decl = gg_find_field_in_struct(base, field);
tree indirect_ref = build1(INDIRECT_REF, base_pointer_type, base);
retval = build3(COMPONENT_REF,
TREE_TYPE(field_decl),
indirect_ref,
field_decl,
NULL_TREE);
}
else
{
// It's not a pointer, so presumably it's a structure
tree field_decl = gg_find_field_in_struct(base, field);
retval = build3(COMPONENT_REF,
TREE_TYPE(field_decl),
base,
field_decl,
NULL_TREE);
}
return retval;
}
tree
gg_assign_to_structure(tree var_decl_struct, const char *field, const tree source)
{
// The C equivalent: "struct.field = source"
tree component_ref = gg_struct_field_ref(var_decl_struct,field);
gg_assign(component_ref,source);
return component_ref;
}
tree
gg_assign_to_structure(tree var_decl_struct, const char *field, int N)
{
// The C equivalent: "struct.field = N"
tree component_ref = gg_struct_field_ref(var_decl_struct,field);
gg_assign(component_ref,build_int_cst(integer_type_node, N));
return component_ref;
}
static tree
gg_create_assembler_name(const char *cobol_name)
{
char *psz = cobol_name_mangler(cobol_name);
tree retval = get_identifier(psz);
free(psz);
return retval;
}
static char *
gg_unique_in_function(const char *var_name, gg_variable_scope_t vs_scope)
{
char *retval = static_cast<char *>(xmalloc(strlen(var_name)+32));
if( (vs_scope == vs_stack || vs_scope == vs_static) )
{
sprintf(retval, "%s." HOST_SIZE_T_PRINT_DEC, var_name,
(fmt_size_t)current_function->program_id_number);
}
else
{
strcpy(retval, var_name);
}
return retval;
}
tree
gg_declare_variable(tree type_decl,
const char *name,
tree initial_value,
gg_variable_scope_t vs_scope,
bool *already_defined)
{
// The C/C++ language provides the concept of a *declaration*, which is a
// prototype for a variable or function. "extern int global_var" is a
// declaration. Declarations let the compiler know what kind of variable it
// is looking for so that it can know what to do with it when it is found.
//
// A *definition* causes the assembler to actually create data storage for
// the specified var_decl.
//
// Be it hereby known that the various attributes associated with a var_decl,
// things like TREE_PUBLIC and TREE_STATIC and TREE_CONST seem to line up with
// their meanings in the C language. But I haven't investigated it enough to
// be completely sure about that. A hard look at gcc/tree.h is on my list of
// homework assignments. In the meantime, I continue to learn by compiling
// C programs with the fdump-generic-nodes option, and copying them as
// necessary to accomplish specific tasks.
//
// Specifically, this routine creates and returns a VAR_DECL, which is the
// prototype.
//
// The gg_define_variable() routines take a VAR_DECL and create a DECL_EXPR
// node from it. When that DECL_EXPR is appended to the statement list, it
// causes the storage to be allocated.
// It is routine to let the compiler assign names to stack variables. The
// assembly code does not use names for variables on the stack; they are
// referenced by offsets to the base pointer. But static variables have to
// have names, and there are places in my code generation -- Lord only knows
// why -- where I didn't give the variables explicit names. We remedy that
// here:
static std::map<std::string, tree>seen;
tree var_name = NULL_TREE;
tree var_decl;
// Assume that for an external reference we know what we want:
char *unique_name = NULL;
if( name )
{
// We were provided a name
unique_name = gg_unique_in_function(name, vs_scope);
var_name = get_identifier(unique_name);
std::map<std::string, tree>::const_iterator it = seen.find(unique_name);
if( it != seen.end() )
{
// We've seen this one before
var_decl = it->second;
if( already_defined )
{
*already_defined = true;
}
}
else
{
var_decl = build_decl(UNKNOWN_LOCATION,
VAR_DECL,
var_name,
type_decl);
}
}
else
{
// We were not provided a name, so we have to create one.
if( vs_scope == vs_static )
{
// static variables have to have names:
static int counter = 1;
char ach[32];
sprintf(ach, "__unnamed_static_variable_%d", counter++);
var_name = get_identifier(ach);
}
var_decl = build_decl(UNKNOWN_LOCATION,
VAR_DECL,
var_name,
type_decl);
}
switch(vs_scope)
{
case vs_stack:
// This is a stack variable
DECL_CONTEXT(var_decl) = current_function->function_decl;
break;
case vs_static:
// This is a function-level static variable
DECL_CONTEXT(var_decl) = current_function->function_decl;
TREE_STATIC(var_decl) = 1;
break;
case vs_file_static:
// File static variables have translation_unit_scope. I have chosen to
// provide access to them through a map; see gg_trans_unit_var_decl();
// TREE_STATIC seems to imply const.
DECL_CONTEXT (var_decl) = gg_trans_unit.trans_unit_decl;
TREE_STATIC(var_decl) = 1;
break;
case vs_file:
// File variables have translation_unit_scope.
// When TREE_STATIC is on, they seem to get put into the .text section
DECL_CONTEXT (var_decl) = gg_trans_unit.trans_unit_decl;
break;
case vs_external:
// This is for defining variables with global scope
DECL_CONTEXT (var_decl) = gg_trans_unit.trans_unit_decl;
TREE_USED(var_decl) = 1;
TREE_STATIC(var_decl) = 1;
TREE_PUBLIC(var_decl) = 1;
seen[unique_name] = var_decl;
break;
case vs_external_reference:
// This is for referencing variables defined elsewhere
DECL_CONTEXT (var_decl) = gg_trans_unit.trans_unit_decl;
TREE_USED(var_decl) = 1;
DECL_EXTERNAL (var_decl) = 1;
TREE_PUBLIC(var_decl) = 1;
break;
}
DECL_INITIAL(var_decl) = initial_value;
free(unique_name);
return var_decl;
}
tree
gg_define_from_declaration(tree var_decl)
{
// Append the var_decl to either the chain for the current function or for
// the translation_unit, depending on the var_decl's context:
gg_append_var_decl(var_decl);
if( !SCOPE_FILE_SCOPE_P(DECL_CONTEXT(var_decl)) )
{
// Having made sure the chain of variable declarations is nicely started,
// it's time to actually define the storage with a decl_expression:
tree stmt = build1_loc (gg_token_location(),
DECL_EXPR,
TREE_TYPE(var_decl),
var_decl);
gg_append_statement(stmt);
}
// And we are done. That variable is now available for computation.
return var_decl;
}
tree
gg_define_variable(tree type_decl)
{
tree var_decl = gg_declare_variable(type_decl);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_variable(tree type_decl, tree initial_value)
{
tree var_decl = gg_declare_variable(type_decl,
NULL,
gg_cast(type_decl, initial_value),
vs_stack);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_variable(tree type_decl, gg_variable_scope_t vs_scope)
{
tree var_decl = gg_declare_variable(type_decl, NULL, NULL_TREE, vs_scope);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_variable( tree type_decl,
const char *var_name,
gg_variable_scope_t vs_scope,
tree initial_value)
{
tree var_decl = gg_declare_variable(type_decl, var_name, initial_value, vs_scope);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_variable(tree type_decl, const char *name, gg_variable_scope_t vs_scope)
{
bool already_defined = false;
tree var_decl = gg_declare_variable(type_decl, name, NULL_TREE, vs_scope, &already_defined);
if( !already_defined )
{
gg_define_from_declaration(var_decl);
}
return var_decl;
}
tree
gg_define_bool()
{
tree var_decl = gg_declare_variable(BOOL);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_char()
{
// The nearest C equivalent: "char name;", but this one is given a
// compiler-assigned name.
// Beware: This is the "implementation specific" version of char, which
// in GENERIC seems to be signed on Windows/Linux Intel machines. But we
// need to be careful if we use an 8-bit type for numerical calculation.
tree var_decl = gg_declare_variable(CHAR);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_char(const char *variable_name)
{
// The C equivalent: "char name;"
// Beware: This is the "implementation specific" version of char, which
// in GENERIC seems to be signed on Windows/Linux Intel machines. But we
// need to be careful if we use an 8-bit type for numerical calculation.
tree var_decl = gg_declare_variable(CHAR, variable_name);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_char(const char *variable_name, tree ch)
{
tree var_decl = gg_declare_variable(CHAR, variable_name, ch);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_char(const char *variable_name, int ch)
{
return gg_define_char(variable_name, char_nodes[ch&0xFF]);
}
tree
gg_define_uchar()
{
// The C equivalent: "char name;"
// Beware: This is the "implementation specific" version of char, which
// in GENERIC seems to be signed on Windows/Linux Intel machines. But we
// need to be careful if we use an 8-bit type for numerical calculation.
return gg_define_variable(UCHAR);
}
tree
gg_define_uchar(const char *variable_name)
{
// The C equivalent: "char name;"
// Beware: This is the "implementation specific" version of char, which
// in GENERIC seems to be signed on Windows/Linux Intel machines. But we
// need to be careful if we use an 8-bit type for numerical calculation.
return gg_define_variable(UCHAR, variable_name);
}
tree
gg_define_uchar(const char *variable_name, tree ch)
{
tree var_decl = gg_declare_variable(UCHAR, variable_name, ch);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_uchar(const char *variable_name, int ch)
{
return gg_define_char(variable_name, char_nodes[ch&0xFF]);
}
tree
gg_define_int()
{
tree var_decl = gg_declare_variable(INT);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_int(int N)
{
tree var_decl = gg_declare_variable(INT, NULL, build_int_cst_type(INT, N));
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_int(const char *variable_name)
{
tree var_decl = gg_declare_variable(INT, variable_name);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_int(const char *variable_name, tree N)
{
tree var_decl = gg_declare_variable(INT, variable_name, N);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_int(const char *variable_name, int N)
{
tree var_decl = gg_declare_variable(INT, variable_name, build_int_cst_type(INT, N));
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_size_t()
{
tree var_decl = gg_declare_variable(SIZE_T);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_size_t(const char *variable_name)
{
tree var_decl = gg_declare_variable(SIZE_T, variable_name);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_size_t(tree N)
{
tree retval = gg_define_variable(SIZE_T);
gg_assign(retval, N);
return retval;
}
tree
gg_define_size_t(size_t N)
{
tree var_decl = gg_declare_variable(SIZE_T, NULL, build_int_cst_type(SIZE_T, N));
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_size_t(const char *variable_name, tree N)
{
tree var_decl = gg_declare_variable(SIZE_T, variable_name, N);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_size_t(const char *variable_name, size_t N)
{
tree var_decl = gg_declare_variable(SIZE_T, variable_name, build_int_cst_type(SIZE_T, N));
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_int128()
{
// The C equivalent: "INT128 <compiler_name>;"
return gg_define_variable(INT128);
}
tree
gg_define_int128(const char *variable_name)
{
// The C equivalent: "INT128 name;"
return gg_define_variable(INT128, variable_name);
}
tree
gg_define_int128(const char *variable_name, tree N)
{
// The C equivalent: "INT128 name = N"
tree var_decl = gg_declare_variable(INT128, variable_name, N);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_int128(const char *variable_name, int N)
{
// The C equivalent: "INT128 name = N"
tree var_decl = gg_define_int128(variable_name, build_int_cst_type(INT128, N));
return var_decl;
}
tree
gg_define_char_star()
{
// The C equivalent: "char *name;"
return gg_define_variable(CHAR_P);
}
tree
gg_define_char_star(const char *variable_name)
{
return gg_define_variable(CHAR_P, variable_name);
}
tree
gg_define_char_star(const char *variable_name, gg_variable_scope_t scope)
{
tree var_decl = gg_declare_variable(CHAR_P, variable_name, NULL_TREE, scope);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_char_star(tree var)
{
tree var_decl = gg_declare_variable(CHAR_P, NULL, var);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_char_star(const char *variable_name, tree var)
{
tree var_decl = gg_declare_variable(CHAR_P, variable_name, var);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_uchar_star()
{
tree var_decl = gg_declare_variable(UCHAR_P);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_uchar_star(const char *variable_name)
{
tree var_decl = gg_declare_variable(UCHAR_P, variable_name);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_uchar_star(const char *variable_name, gg_variable_scope_t scope)
{
tree var_decl = gg_declare_variable(UCHAR_P, variable_name, NULL_TREE, scope);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_uchar_star(tree var)
{
tree var_decl = gg_declare_variable(UCHAR_P, NULL, var);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_uchar_star(const char *variable_name, tree var)
{
tree var_decl = gg_declare_variable(UCHAR_P, variable_name, var);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_void_star()
{
tree var_decl = gg_declare_variable(VOID_P);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_void_star(const char *variable_name)
{
tree var_decl = gg_declare_variable(VOID_P, variable_name);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_void_star(const char *variable_name, tree var)
{
tree var_decl = gg_declare_variable(VOID_P, variable_name, var);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_void_star(const char *variable_name, gg_variable_scope_t scope)
{
tree var_decl = gg_declare_variable(VOID_P, variable_name, NULL_TREE, scope);
gg_define_from_declaration(var_decl);
return var_decl;
}
tree
gg_define_longdouble()
{
tree var_decl = gg_declare_variable(LONGDOUBLE);
gg_define_from_declaration(var_decl);
return var_decl;
}
extern tree
gg_define_array(tree type_decl, size_t size)
{
tree array_type = build_array_type_nelts(type_decl, size);
return gg_define_variable(array_type);
}
extern tree
gg_define_array(tree type_decl, const char *name, size_t size)
{
tree array_type = build_array_type_nelts(type_decl, size);
return gg_define_variable(array_type, name);
}
extern tree
gg_define_array(tree type_decl, size_t size, gg_variable_scope_t scope)
{
tree array_type = build_array_type_nelts(type_decl, size);
return gg_define_variable(array_type, scope);
}
extern tree
gg_define_array(tree type_decl, const char *name, size_t size, gg_variable_scope_t scope)
{
tree array_type = build_array_type_nelts(type_decl, size);
return gg_define_variable(array_type, name, scope);
}
tree
gg_get_address_of(const tree var_decl)
{
// Returns an ADDR_EXPR which points to var_decl.
// The C equivalent is &variable
// We need to be able to use this guy's address directly:
// In order to do that, this fellow's "addressable" bit has to be on, otherwise
// the GIMPLE reducer creates a temporary variable, sets its value to var_decl's,
// and returns the pointer to the temp. I suppose this has something to do with
// pass by reference and pass by value, but it makes my head hurt, and, frankly,
// I'll take the dangerous road.
TREE_ADDRESSABLE(var_decl) = 1;
TREE_USED(var_decl) = 1;
return build1( ADDR_EXPR,
build_pointer_type (TREE_TYPE(var_decl)),
var_decl);
}
tree
gg_get_indirect_reference(tree pointer, tree offset)
{
// The C equivalent: auto pointer[offset];
// the returned indirect reference has the same type as
// what pointer points to. If pointer is a char *, then the returned
// value has type char. If pointer is an int *, then the returned
// value has type int.
// We also want the offset to operate the same way it does in C, so we
// are going to find the size of the objects the pointer points to, and
// multiply the offset by that size:
tree pointer_type = TREE_TYPE(pointer);
tree element_type = TREE_TYPE(pointer_type);
tree indirect_reference;
if( offset )
{
// We can now start building our little shrub:
tree distance = build2( MULT_EXPR,
SIZE_T,
gg_cast(sizetype, offset),
TYPE_SIZE_UNIT(element_type));
// Next, we build the pointer_plus_expr:
tree pointer_plus_expr = build2(POINTER_PLUS_EXPR,
pointer_type,
pointer,
distance);
// With that in hand, we can build the indirect_reference:
indirect_reference = build1(INDIRECT_REF, element_type, pointer_plus_expr);
}
else
{
indirect_reference = build1(INDIRECT_REF, element_type, pointer);
}
return indirect_reference;
}
tree
gg_indirect(tree pointer, tree byte_offset)
{
// Unlike gg_get_indirect_reference, which multiplies the offset by the
// size of the type pointed to by pointer, this routine simply adds the offset
// to the pointer.
tree pointer_type = TREE_TYPE(pointer);
tree element_type = TREE_TYPE(pointer_type);
tree retval;
if( byte_offset == NULL_TREE )
{
retval = build1(INDIRECT_REF, element_type, pointer);
}
else
{
tree pointer_plus_expr = build2(POINTER_PLUS_EXPR,
pointer_type,
pointer,
gg_cast(SIZE_T, byte_offset));
retval = build1(INDIRECT_REF, element_type, pointer_plus_expr);
}
return retval;
}
tree
gg_array_value(tree pointer, tree offset)
{
// We arrange the function so that it can work on either an ARRAY_TYPE
// or a pointer type
tree pointer_type = TREE_TYPE(pointer);
tree element_type = TREE_TYPE(pointer_type);
if(POINTER_TYPE_P(pointer_type))
{
// It is a pointer
tree retval = gg_get_indirect_reference(pointer, offset);
return retval;
}
else
{
return build4(ARRAY_REF,
element_type,
pointer,
offset,
NULL_TREE,
NULL_TREE);
}
}
tree
gg_array_value(tree pointer, int N)
{
return gg_array_value(pointer, build_int_cst(INT, N));
}
void
gg_increment(tree var)
{
tree var_type = TREE_TYPE(var);
gg_assign(var, gg_add(var, build_int_cst_type(var_type, 1)));
}
void
gg_decrement(tree var)
{
tree var_type = TREE_TYPE(var);
gg_assign(var,
gg_cast(var_type,
gg_subtract(var,
build_int_cst_type(var_type, 1))));
}
tree
gg_negate(tree var)
{
return build1(NEGATE_EXPR, TREE_TYPE(var), var);
}
tree
gg_bitwise_not(tree var)
{
return build1(BIT_NOT_EXPR, TREE_TYPE(var), var);
}
tree
gg_abs(tree var)
{
return build1(ABS_EXPR, TREE_TYPE(var), var);
}
static tree
gg_get_larger_type(tree A, tree B)
{
tree larger = TREE_TYPE(B);
if( TREE_INT_CST_LOW(TYPE_SIZE(TREE_TYPE(A)))
> TREE_INT_CST_LOW(TYPE_SIZE(TREE_TYPE(B))) )
{
larger = TREE_TYPE(A);
}
return larger;
}
tree
gg_add(tree addend1, tree addend2)
{
tree retval;
if( POINTER_TYPE_P(TREE_TYPE(addend1)) )
{
// operand1 is a pointer.
// Make this work like C pointer arithmetic. We'll find the
// size of the things that pointer points to, and multiply accordingly
tree pointer_type = TREE_TYPE(addend1);
tree pointer_type_type = TREE_TYPE(pointer_type);
tree bytes_per_element = TYPE_SIZE_UNIT(pointer_type_type);
tree op2 = gg_cast(SIZE_T, gg_multiply(addend2, bytes_per_element));
retval = build2(POINTER_PLUS_EXPR,
TREE_TYPE(addend1),
addend1,
op2);
}
else
{
// Ordinary addition. Scale both operands to match the larger
// type of the two operands.
tree larger_type = gg_get_larger_type(addend1, addend2);
retval = build2( PLUS_EXPR,
larger_type,
gg_cast(larger_type, addend1),
gg_cast(larger_type, addend2));
}
return retval;
}
tree
gg_subtract(tree A, tree B)
{
// We are doing A - B, instead.
if( POINTER_TYPE_P(TREE_TYPE(A)) && INTEGRAL_TYPE_P(TREE_TYPE(B)) )
{
// We are subtracting an integer from a pointer. That's handled
// in gg_add, by converting the integer, possibly signed, to
// an unsigned huge number.
return gg_add(A, gg_negate(B));
}
if( POINTER_TYPE_P(TREE_TYPE(A)) && POINTER_TYPE_P(TREE_TYPE(A)) )
{
// We are subtracting two pointers, yielding a signed size_t
return build2(POINTER_DIFF_EXPR, SSIZE_T, A, B);
}
// This is an ordinary subtraction. Scale everything to the larger_type
// of the two operands.
tree larger_type = gg_get_larger_type(A, B);
tree stmt = build2( MINUS_EXPR,
larger_type,
gg_cast(larger_type, A),
gg_cast(larger_type, B) );
return stmt;
}
tree
gg_multiply(tree A, tree B)
{
// We will return the product of A and B, adjusting to
// whichever is larger:
tree larger_type = gg_get_larger_type(A, B);
return build2( MULT_EXPR, larger_type, gg_cast(larger_type, A), gg_cast(larger_type, B) );
}
tree
gg_real_divide(tree A, tree B)
{
// This floating point division:
tree larger_type = gg_get_larger_type(A, B);
return build2( RDIV_EXPR, larger_type, gg_cast(larger_type,A), gg_cast(larger_type,B));
}
tree
gg_divide(tree A, tree B)
{
// This is the equivalent of C integer divide
tree larger_type = gg_get_larger_type(A, B);
return build2( TRUNC_DIV_EXPR, larger_type, gg_cast(larger_type,A), gg_cast(larger_type,B));
}
tree
gg_mod(tree A, tree B)
{
// This is the equivalent of C A % B
tree larger_type = gg_get_larger_type(A, B);
return build2( TRUNC_MOD_EXPR, larger_type, gg_cast(larger_type,A), gg_cast(larger_type,B));
}
tree
gg_lshift(tree A, tree B)
{
// Equivalent of A << B;
return build2( LSHIFT_EXPR, TREE_TYPE(A), A, B );
}
tree
gg_rshift(tree A, tree B)
{
// Equivalent of A >> B;
return build2( RSHIFT_EXPR, TREE_TYPE(A), A, B );
}
tree
gg_bitwise_or(tree A, tree B)
{
// This is C equivalent to A | B
tree larger_type = gg_get_larger_type(A, B);
return build2( BIT_IOR_EXPR, larger_type, gg_cast(larger_type,A), gg_cast(larger_type,B));
}
tree
gg_bitwise_xor(tree A, tree B)
{
// This is C equivalent to A ^ B
tree larger_type = gg_get_larger_type(A, B);
return build2( BIT_XOR_EXPR, larger_type, gg_cast(larger_type,A), gg_cast(larger_type,B));
}
tree
gg_bitwise_and(tree A, tree B)
{
// This is C equivalent to A & B
tree larger_type = gg_get_larger_type(A, B);
return build2( BIT_AND_EXPR, larger_type, gg_cast(larger_type,A), gg_cast(larger_type,B));
}
tree
gg_build_relational_expression(tree operand_a,
enum relop_t op,
tree operand_b)
{
tree_code compare = EQ_EXPR; // Assuage the compiler
switch(op)
{
case eq_op:
compare = EQ_EXPR;
break;
case ne_op:
compare = NE_EXPR;
break;
case lt_op:
compare = LT_EXPR;
break;
case gt_op:
compare = GT_EXPR;
break;
case ge_op:
compare = GE_EXPR;
break;
case le_op:
compare = LE_EXPR;
break;
}
tree relational_expression = build2_loc(gg_token_location(),
compare,
boolean_type_node,
operand_a,
operand_b);
return relational_expression;
}
tree
gg_build_logical_expression(tree operand_a,
enum logop_t op,
tree operand_b)
{
tree logical_expression = NULL_TREE;
tree_code logical_op;
switch(op)
{
case and_op:
logical_op = TRUTH_ANDIF_EXPR;
logical_expression = build2(logical_op,
boolean_type_node,
operand_a,
operand_b);
break;
case or_op:
logical_op = TRUTH_ORIF_EXPR;
logical_expression = build2(logical_op,
boolean_type_node,
operand_a,
operand_b);
break;
case not_op:
logical_op = TRUTH_NOT_EXPR;
logical_expression = build1(logical_op,
boolean_type_node,
operand_b);
break;
case xor_op:
logical_op = TRUTH_XOR_EXPR;
logical_expression = build2(logical_op,
boolean_type_node,
operand_a,
operand_b);
break;
case xnor_op:
case true_op:
case false_op:
// This is handled elsewhere
break;
}
return logical_expression;
}
void
gg_create_goto_pair(tree *goto_expr, tree *label_expr, tree *label_addr, const char *name)
{
// We are going to create a pair of expressions for our
// caller. They are a matched set of goto/label expressions,
// to be included in a statement list
tree label_decl = build_decl( UNKNOWN_LOCATION,
LABEL_DECL,
gg_create_assembler_name(name),
void_type_node);
DECL_CONTEXT(label_decl) = current_function->function_decl;
TREE_USED(label_decl) = 1;
*goto_expr = build1(GOTO_EXPR, void_type_node, label_decl);
*label_expr = build1(LABEL_EXPR, void_type_node, label_decl);
*label_addr = gg_get_address_of(label_decl);
}
void
gg_create_goto_pair(tree *goto_expr, tree *label_expr, tree *label_addr)
{
// We are going to create a pair of expressions for our
// caller. They are a matched set of goto/label expressions,
// to be included in a statement list
tree label_decl = build_decl( UNKNOWN_LOCATION,
LABEL_DECL,
NULL_TREE,
void_type_node);
DECL_CONTEXT(label_decl) = current_function->function_decl;
TREE_USED(label_decl) = 1;
*goto_expr = build1(GOTO_EXPR, void_type_node, label_decl);
*label_expr = build1(LABEL_EXPR, void_type_node, label_decl);
*label_addr = gg_get_address_of(label_decl);
}
void
gg_create_goto_pair(tree *goto_expr,
tree *label_expr,
tree *label_addr,
tree *label_decl)
{
// We are going to create a pair of expressions for our
// caller. They are a matched set of goto/label expressions,
// to be included in a statement list
*label_decl = build_decl( UNKNOWN_LOCATION,
LABEL_DECL,
NULL_TREE,
void_type_node);
DECL_CONTEXT(*label_decl) = current_function->function_decl;
TREE_USED(*label_decl) = 1;
*goto_expr = build1(GOTO_EXPR, void_type_node, *label_decl);
*label_expr = build1(LABEL_EXPR, void_type_node, *label_decl);
*label_addr = gg_get_address_of(*label_decl);
}
void
gg_goto_label_decl(tree label_decl)
{
tree goto_expr = build1_loc( gg_token_location(),
GOTO_EXPR,
void_type_node,
label_decl);
gg_append_statement(goto_expr);
}
void
gg_create_goto_pair(tree *goto_expr, tree *label_expr)
{
// We are going to create a pair of expressions for our
// caller. They are a matched set of goto/label expressions,
// to be included in a statement list
tree label_decl = build_decl( UNKNOWN_LOCATION,
LABEL_DECL,
NULL_TREE,
void_type_node);
DECL_CONTEXT(label_decl) = current_function->function_decl;
TREE_USED(label_decl) = 1;
*goto_expr = build1(GOTO_EXPR, void_type_node, label_decl);
*label_expr = build1(LABEL_EXPR, void_type_node, label_decl);
}
void
gg_create_goto_pair(tree *goto_expr, tree *label_expr, const char *name)
{
// We are going to create a pair of named expressions for our
// caller. They are a matched set of goto/label expressions,
// to be included in a statement list
tree label_decl = build_decl( UNKNOWN_LOCATION,
LABEL_DECL,
gg_create_assembler_name(name),
void_type_node);
DECL_CONTEXT(label_decl) = current_function->function_decl;
TREE_USED(label_decl) = 1;
*goto_expr = build1(GOTO_EXPR, void_type_node, label_decl);
*label_expr = build1(LABEL_EXPR, void_type_node, label_decl);
}
// Used for implementing SECTIONS and PARAGRAPHS. When you have a
// void *pointer = &&label, gg_goto is the same as
// goto *pointer
void
gg_goto(tree var_decl_pointer)
{
tree go_to = build1_loc(gg_token_location(),
GOTO_EXPR,
void_type_node,
var_decl_pointer);
gg_append_statement(go_to);
}
void
gg_while( tree operand_a,
enum relop_t op,
tree operand_b)
{
/*
See demonstration_while_if for the canonical demonstration
You use it like this:
WHILE
....
WEND
We do the C construct:
while( a OP b )
{
<block>
}
like this:
goto test
top:
<block>
test:
if( a OP b)
goto top
else
goto leave:
leave:
*/
tree goto_top;
tree label_top;
tree goto_test;
tree label_test;
tree goto_leave;
tree label_leave;
gg_create_goto_pair(&goto_top, &label_top);
gg_create_goto_pair(&goto_test, &label_test);
gg_create_goto_pair(&goto_leave, &label_leave);
tree statement_block = make_node(STATEMENT_LIST);
TREE_TYPE(statement_block) = void_type_node;
// During development, I tried appending a statement_list to a statement_list,
// intending it to be collected together that way. But it was too smart for me;
// it just unwound the second list and tacked it onto the end of the first.
// So I used a BIND_EXPR to collect them together. This isn't a new context, so I don't
// point operand[0] at a string of vars, nor operand[2] at a block.
tree bind_expr = build3( BIND_EXPR,
void_type_node,
NULL_TREE,
statement_block,
NULL_TREE);
// With the pairs created and the bind_expr sorted out, we can now put
// together our while construction:
gg_append_statement(goto_test);
gg_append_statement(label_top);
gg_append_statement(bind_expr);
gg_append_statement(label_test);
IF( operand_a, op, operand_b )
gg_append_statement(goto_top);
ELSE
gg_append_statement(goto_leave);
ENDIF
gg_append_statement(label_leave);
// And here's the statement_list for the programmer to fill
// and end with a WEND
current_function->statement_list_stack.push_back(statement_block);
}
void
gg_create_true_false_statement_lists(tree relational_expression)
{
// Create the two statement_lists for ifness, one for true and
// the other for false. Put them on the stack, ready for the first
// pop on ELSE and the second pop on ENDIF:
tree if_true_statement_list = make_node(STATEMENT_LIST);
TREE_TYPE(if_true_statement_list) = void_type_node;
tree if_false_statement_list = make_node(STATEMENT_LIST);
TREE_TYPE(if_false_statement_list) = void_type_node;
tree conditional = build3( COND_EXPR,
boolean_type_node,
relational_expression,
if_true_statement_list,
if_false_statement_list);
// We need to put our conditional onto the current_stack:
gg_append_statement(conditional);
// And with that done, we can push the FALSE and TRUE blocks
// onto the stack in the correct order:
current_function->statement_list_stack.push_back(if_false_statement_list);
current_function->statement_list_stack.push_back(if_true_statement_list);
}
void
gg_if( tree operand_a,
enum relop_t op,
tree operand_b)
{
/* Listen up, troops. Here's how you use this constructor.
You use it like this:
IF( this, LT, that)
....
ELSE
....
ENDIF
You *must* have all three: IF ELSE ENDIF, if you don't, the
current_function->statement_list_stack gets all higgledepiggledy
It is the C equivalent of
if( a OP b )
{
<if_true_statement_list>
}
else
{
<if_false_statement_list>
}
This routine pushes the false_statement_list onto current_function->statement_list_stack,
followed by the true_statement_list.
You then generate statements for the TRUE block
You then pop the current_function->statement_list_stack.
Then you do the same for the FALSE block
You then pop the current_function->statement_list_stack again.
For the sake of readability, we define ELSE and ENDIF to do
that popping.
I don't plan on explaining this everywhere it's used.
See demonstration_while_if for the canonical demonstration
*/
if( TREE_TYPE(operand_a) != TREE_TYPE(operand_b) )
{
fprintf(stderr, "%s(): a and b have different TREE_TYPES\n", __func__);
gcc_unreachable();
}
// Build the relational expression:
tree relational_expression =
gg_build_relational_expression(operand_a,
op,
operand_b);
// And with that in hand, create the two statement lists, one for
// true and one for false, and set up the stacks:
gg_create_true_false_statement_lists(relational_expression);
}
tree
gg_get_function_address(tree return_type, const char *funcname)
{
// This routine finds a function by name. It calls build_fn_decl
// with an empty array of varargs. I haven't investigated all the
// possibilities, but this returns an address expression for a function
// that can be built with any argument[s].
// There is no compile-time checking; if you specify disaster, then
// disaster will be what you get.
tree fndecl_type = build_varargs_function_type_array (return_type,
0,
NULL);
tree function_decl = build_fn_decl (funcname, fndecl_type);
DECL_EXTERNAL (function_decl) = 1;
tree retval = build1(ADDR_EXPR, build_pointer_type (fndecl_type), function_decl);
return retval;
}
void
gg_printf(const char *format_string, ...)
{
// This allows you to use fprintf(stderr, ...) with a format string
// and a list of arguments ending with a NULL
// Use this for conveniently adding print statements into the generated
// code, for run-time print-statement debugging. gg_write is used for
// actual program code.
// Note that the return value from the printf() call is *not* available
// to the caller.
int nargs = 0;
tree args[ARG_LIMIT];
args[nargs++] = build_string_literal(strlen(format_string)+1, format_string);
va_list ap;
va_start(ap, format_string);
tree arg = va_arg(ap, tree);
while(arg)
{
if(nargs >= ARG_LIMIT)
{
yywarn("You *must* be joking");
gcc_unreachable();
}
if( TREE_CODE(arg) >= NUM_TREE_CODES)
{
// Warning: This test is not completely reliable, because a garbage
// byte could have a valid TREE_CODE. But it does help.
yywarn("You forgot to put a %<NULL_TREE%> at the end of a "
"%<gg_printf()%> again");
gcc_unreachable();
}
args[nargs++] = arg;
arg = va_arg(ap, tree);
}
va_end (ap);
static tree function = NULL_TREE;
if( !function )
{
function = gg_get_function_address(INT, "__gg__fprintf_stderr");
}
tree stmt = build_call_array_loc (gg_token_location(),
INT,
function,
nargs,
args);
gg_append_statement(stmt);
}
tree
gg_fprintf(tree fd, int nargs, const char *format_string, ...)
{
tree retval = gg_define_int();
gg_push_context();
tree buffer = gg_define_char_star();
gg_assign(buffer, gg_cast(CHAR_P, gg_malloc(1024)));
tree args[ARG_LIMIT];
// Set up a call to sprintf:
int argc = 0;
args[argc++] = buffer;
args[argc++] = build_string_literal(strlen(format_string)+1, format_string);
va_list ap;
va_start(ap, format_string);
tree arg = va_arg(ap, tree);
int narg = 0;
while(narg++ < nargs)
{
if(argc >= ARG_LIMIT)
{
yywarn("You *must* be joking");
gcc_unreachable();
}
args[argc++] = arg;
arg = va_arg(ap, tree);
}
va_end (ap);
static tree function = NULL_TREE;
if( !function )
{
function = gg_get_function_address(INT, "sprintf");
}
tree stmt = build_call_array_loc (gg_token_location(),
INT,
function,
argc,
args);
gg_assign(retval, stmt);
gg_write(fd, buffer, gg_strlen(buffer));
gg_free(buffer);
gg_pop_context();
return retval;
}
tree
gg_read(tree fd, tree buf, tree count)
{
// The C equivalent: "read(fd, buf, count)"
// Because the caller might need the ssize_t return value, this routine
// returns the statement_decl for the call. It is used this way:
// tree num_chars = gg_define_int("_num_chars");
// gg_assign(num_chars, gg_read(fd, buf, count));
return gg_call_expr(SSIZE_T,
"read",
fd,
buf,
count,
NULL_TREE);
}
void
gg_write(tree fd, tree buf, tree count)
{
gg_call(SSIZE_T,
"write",
fd,
buf,
count,
NULL_TREE);
}
void
gg_memset(tree dest, const tree value, tree size)
{
tree the_call =
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_MEMSET),
3,
dest,
value,
size);
gg_append_statement(the_call);
}
tree
gg_memchr(tree buf, tree ch, tree length)
{
tree the_call = fold_convert(
pvoid_type_node,
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_MEMCHR),
3,
buf,
ch,
length));
return the_call;
}
/* Built-in call to memcpy() */
void
gg_memcpy(tree dest, const tree src, tree size)
{
tree the_call = build_call_expr_loc(
gg_token_location(),
builtin_decl_explicit (BUILT_IN_MEMCPY),
3,
dest,
src,
size);
gg_append_statement(the_call);
}
/* Built-in call to memmove() */
void
gg_memmove(tree dest, const tree src, tree size)
{
tree the_call = build_call_expr_loc(
gg_token_location(),
builtin_decl_explicit (BUILT_IN_MEMMOVE),
3,
dest,
src,
size);
gg_append_statement(the_call);
}
tree
gg_memdup(tree data, tree length)
{
// Duplicates data; gg_free should eventually be called
tree retval = gg_define_char_star();
gg_assign(retval, gg_malloc(length));
gg_memcpy(retval, data, length);
return retval;
}
tree
gg_memdup(tree data, size_t length)
{
// Duplicates data; gg_free should eventually be called
tree retval = gg_define_char_star();
gg_assign(retval, gg_malloc(length));
gg_memcpy(retval, data, build_int_cst_type(SIZE_T, length));
return retval;
}
void
gg_strcpy(tree dest, tree src)
{
tree the_call =
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_STRCPY),
2,
dest,
src);
gg_append_statement(the_call);
}
tree
gg_strcmp(tree A, tree B)
{
tree the_call = fold_convert(
integer_type_node,
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_STRCMP),
2,
A,
B));
return the_call;
}
tree
gg_open(tree char_star_A, tree int_B)
{
return gg_call_expr(INT,
"open",
char_star_A,
int_B,
NULL_TREE);
}
tree
gg_close(tree int_A)
{
return gg_call_expr(INT,
"close",
int_A,
NULL_TREE);
}
tree
gg_strncmp(tree char_star_A, tree char_star_B, tree size_t_N)
{
tree the_call = fold_convert(
integer_type_node,
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_STRNCMP),
3,
char_star_A,
char_star_B,
size_t_N));
return the_call;
}
void
gg_return(tree operand)
{
tree stmt;
if( !gg_trans_unit.function_stack.size() )
{
// I put this in to cope with the problem of two END PROGRAM statements, which
// should be a syntax error but, as of 2021-02-24, is ignored by GnuCOBOL and
// by our parser.
return ;
}
// We have to pop ourselves off of the module_name_stack:
gg_call(VOID,
"__gg__module_name_pop",
NULL_TREE);
if( !operand || !DECL_RESULT(current_function->function_decl) )
{
// When there is no operand, or if the function result is void, then
// we just generate a return_expr.
stmt = build1_loc(gg_token_location(), RETURN_EXPR, void_type_node, NULL_TREE);
}
else
{
// Life is a wee bit more complicated, because we want to return the operand
tree function_type = TREE_TYPE(DECL_RESULT(current_function->function_decl));
tree modify = build2( MODIFY_EXPR,
function_type,
DECL_RESULT(current_function->function_decl),
gg_cast(function_type, operand));
stmt = build1_loc(gg_token_location(), RETURN_EXPR, void_type_node, modify);
}
gg_append_statement(stmt);
}
void
chain_parameter_to_function(tree function_decl, const tree param_type, const char *name)
{
tree parm = build_decl (gg_token_location(),
PARM_DECL,
get_identifier (name),
param_type);
DECL_CONTEXT(parm) = function_decl;
TREE_USED(parm) = 1;
DECL_INITIAL(parm) = param_type;
if( DECL_ARGUMENTS(function_decl) )
{
chainon(DECL_ARGUMENTS(function_decl),parm);
}
else
{
DECL_ARGUMENTS(function_decl) = parm;
}
}
/* There are five ways that we use function_decls:
1, We define a main() entry point.
2. We call a function that turns out to be a static "t" function local to the source code module.
3. We define an global "T" function, and possibly call it later.
4. We call a function that we define later in the source code module.
5. We call a function that ends up being an extern that is not defined in the source code module.
Cases 3. and 4. turn out to require the same flags. Here are the combinations of
flags that are required for each flavor of function_decl. This was empirically
determind by compiling a C++ program with sample code for each type.
| addressable | used | nothrow | static | external | public | no_instrument
main | | | | X | | X | X
local | X | X | X | X | | | X
external defined inside | X | X | X | X | | X | X
external defined elsewhere | X | X | | | X | X |
*/
static std::unordered_map<std::string, tree> map_of_function_decls;
static
std::string function_decl_key(const char *funcname, tree fndecl_type)
{
std::string retval;
retval += funcname;
retval += gg_show_type(TREE_TYPE(fndecl_type));
return retval;
}
tree
gg_peek_fn_decl(const char *funcname, tree fndecl_type)
{
// When funcname is found in map_of_function_decls, this routine returns
// the type of the return value of that function decl.
tree retval = NULL_TREE;
std::string key = function_decl_key(funcname, fndecl_type);
std::unordered_map<std::string, tree>::const_iterator it =
map_of_function_decls.find(key);
if( it != map_of_function_decls.end() )
{
// This function_decl has already been defined.
retval = TREE_TYPE(TREE_TYPE(it->second));
}
return retval;
}
tree
gg_build_fn_decl(const char *funcname, tree fndecl_type)
{
tree function_decl;
std::string key = function_decl_key(funcname, fndecl_type);
std::unordered_map<std::string, tree>::const_iterator it =
map_of_function_decls.find(key);
if( it != map_of_function_decls.end() )
{
// This function_decl has already been defined. Just return it; the caller
// is responsible for modifying it, if necessary.
function_decl = it->second;
}
else
{
// When creating a never-seen function_decl, we default to the type used
// for calling a function defined elsewhere. It's up to our caller to
// modify the flags, for example if this is part of creating a function.
function_decl = build_fn_decl(funcname, fndecl_type);
// These are the bits shown in the table in the comment up above
TREE_ADDRESSABLE(function_decl) = 1;
TREE_USED(function_decl) = 1;
TREE_NOTHROW(function_decl) = 0;
TREE_STATIC(function_decl) = 0;
DECL_EXTERNAL (function_decl) = 1;
TREE_PUBLIC (function_decl) = 1;
DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(function_decl) = 0;
DECL_PRESERVE_P (function_decl) = 0;
DECL_ARTIFICIAL(function_decl) = 0;
map_of_function_decls[key] = function_decl;
}
return function_decl;
}
tree
gg_define_function( tree return_type,
const char *funcname,
const char *unmangled_name,
...)
{
// This routine builds a function_decl, puts it on the stack, and
// gives it a context.
// At this time we don't know how many parameters this function expects, so
// we set things up and we'll tack on the parameters later.
/* There is some bookkeeping we need to do to avoid crashing.
It's possible for the source code to have two top-level functions with
the same name. This is a compile-time error, but the GCC processing gets
upset when it happens. We'll prevent it from happening here:
*/
int nparams = 0;
tree types[ARG_LIMIT];
const char *names[ARG_LIMIT];
va_list params;
va_start(params, unmangled_name);
for(;;)
{
tree var_type = va_arg(params, tree);
if( !var_type )
{
break;
}
if( TREE_CODE(var_type) >= NUM_TREE_CODES)
{
// Warning: This test is not completely reliable, because a garbage
// byte could have a valid TREE_CODE. But it does help.
yywarn("You forgot to put a %<NULL_TREE%> at the end of a "
"%<gg_define_function()%> again");
gcc_unreachable();
}
const char *name = va_arg(params, const char *);
types[nparams] = var_type;
names[nparams] = name;
nparams += 1;
if(nparams > ARG_LIMIT)
{
yywarn("%d parameters? Really? Are you insane?", ARG_LIMIT+1);
gcc_unreachable();
}
}
va_end(params);
char ach[32];
std::unordered_set<std::string>::const_iterator it =
names_we_have_seen.find(funcname);
if( it != names_we_have_seen.end() )
{
static int bum_counter = 1;
// We have seen this name before. Replace it with something unique:
sprintf(ach, "..no_dupes.%d", bum_counter++);
funcname = ach;
}
else
{
names_we_have_seen.insert(funcname);
}
tree fndecl_type = build_varargs_function_type_array( return_type,
nparams,
types);
// Create the FUNCTION_DECL for that FUNCTION_TYPE
tree function_decl = gg_build_fn_decl (funcname, fndecl_type);
// This code makes COBOL nested programs actual visible on the
// source code "trans_unit_decl" level, but with non-public "static"
// visibility.
if( gg_trans_unit.function_stack.size() == 0 )
{
// gg_trans_unit.function_stack is empty, so our context is
// the compilation module, and we need to be public because this is a
// top-level function with global scope:
// These are the bits shown in the table for gg_build_fn_decl()
TREE_ADDRESSABLE(function_decl) = 1;
TREE_USED(function_decl) = 1;
TREE_NOTHROW(function_decl) = 1;
TREE_STATIC(function_decl) = 1;
DECL_EXTERNAL (function_decl) = 0;
TREE_PUBLIC (function_decl) = 1;
DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(function_decl) = 1;
DECL_CONTEXT (function_decl) = gg_trans_unit.trans_unit_decl;
}
else
{
// The stack has something in it, so we are building a contained
// program-id. Such function are implemented local static functions.
//
// It's not necessarily true that a static call to such a function will be
// part of the source code (the call can be through a variable), and so
// optimization routines can decide the function isn't used and can
// therefore be optimized away. The preserve flag prevents that.
// These are the bits shown in the table for gg_build_fn_decl()
TREE_ADDRESSABLE(function_decl) = 1;
TREE_USED(function_decl) = 1;
TREE_NOTHROW(function_decl) = 1;
TREE_STATIC(function_decl) = 1;
DECL_EXTERNAL (function_decl) = 0;
TREE_PUBLIC (function_decl) = 0;
DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(function_decl) = 1;
DECL_CONTEXT (function_decl) = gg_trans_unit.trans_unit_decl;
DECL_CONTEXT(function_decl) = gg_trans_unit.trans_unit_decl;
DECL_PRESERVE_P (function_decl) = 1;
gg_append_var_decl(function_decl);
}
// Chain the names onto the variables list:
for(int i=0; i<nparams; i++)
{
chain_parameter_to_function(function_decl, types[i], names[i]);
}
// Establish the RESULT_DECL for the function:
tree resdecl = build_decl (gg_token_location(), RESULT_DECL, NULL_TREE, return_type);
DECL_CONTEXT (resdecl) = function_decl;
DECL_RESULT (function_decl) = resdecl;
// The function_decl has a .function member, a pointer to struct_function.
// This is quietly, almost invisibly, extremely important. You need to
// call this routine after DECL_RESULT has been established:
allocate_struct_function(function_decl, false);
struct gg_function_t new_function = {};
new_function.context_count = 0;
new_function.function_decl = function_decl;
new_function.our_name = IDENTIFIER_POINTER(DECL_NAME(function_decl));
new_function.our_unmangled_name = xstrdup(unmangled_name);
new_function.function_address = gg_get_address_of(function_decl);
// Each program on the stack gets a unique identifier. This is used, for
// example, to make sure that static variables have unique names.
static size_t program_id = 0;
new_function.program_id_number = program_id++;
// With everything established, put this function_decl on the stack
gg_trans_unit.function_stack.push_back(new_function);
// All we need is a context, and we are ready to go:
gg_push_context();
return function_decl;
}
void
gg_modify_function_type(tree function_decl, tree return_type)
{
tree fndecl_type = build_varargs_function_type_array( return_type,
0, // No parameters yet
NULL); // And, hence, no types
TREE_TYPE(function_decl) = fndecl_type;
tree resdecl = build_decl (UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, return_type);
DECL_CONTEXT (resdecl) = function_decl;
DECL_RESULT (function_decl) = resdecl;
}
tree
gg_get_function_decl(tree return_type, const char *funcname, ...)
{
// This very similar routine creates and returns the function_decl
// It was designed for implementing nested functions, in particular
// in cases of forward references. Thus, you need to have the function_decl
// in order to create the call_expr, even though you don't yet have a body,
// and you aren't ready to create it at this time.
int nparams = 0;
tree types[ARG_LIMIT];
const char *names[ARG_LIMIT];
va_list params;
va_start(params,funcname);
for(;;)
{
tree var_type = va_arg(params, tree);
if( !var_type )
{
break;
}
if( TREE_CODE(var_type) >= NUM_TREE_CODES)
{
// Warning: This test is not completely reliable, because a garbage
// byte could have a valid TREE_CODE. But it does help.
yywarn("You forgot to put a %<NULL_TREE%> at the end of a "
"%<gg_define_function()%> again");
gcc_unreachable();
}
const char *name = va_arg(params, const char *);
types[nparams] = var_type;
names[nparams] = name;
nparams += 1;
if(nparams > ARG_LIMIT)
{
yywarn("%d parameters? Really? Are you insane?",
ARG_LIMIT+1);
gcc_unreachable();
}
}
va_end(params);
// Create the FUNCTION_TYPE for that array:
tree fndecl_type = build_varargs_function_type_array( return_type,
nparams,
types);
// Create the FUNCTION_DECL for that FUNCTION_TYPE
tree function_decl = build_fn_decl (funcname, fndecl_type);
// Some of this stuff is magical, and is based on compiling C programs
// and just mimicking the results.
TREE_ADDRESSABLE(function_decl) = 1;
TREE_STATIC(function_decl) = 1;
DECL_EXTERNAL (function_decl) = 0;
DECL_PRESERVE_P (function_decl) = 0;
DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT(function_decl) = 1;
DECL_ARTIFICIAL(function_decl) = 0;
TREE_NOTHROW(function_decl) = 0;
TREE_USED(function_decl) = 1;
if( gg_trans_unit.function_stack.size() == 0 )
{
// gg_trans_unit.function_stack is empty, so our context is
// the compilation module, and we need to be public:
DECL_CONTEXT (function_decl) = gg_trans_unit.trans_unit_decl;
TREE_PUBLIC(function_decl) = 1;
}
else
{
// The stack has something in it, so we are building a nested function.
// Make the current function our context
DECL_CONTEXT (function_decl) = current_function->function_decl;
TREE_PUBLIC(function_decl) = 0;
DECL_STATIC_CHAIN(function_decl) = 1;
}
// Chain the names onto the variables list:
for(int i=0; i<nparams; i++)
{
chain_parameter_to_function(function_decl, types[i], names[i]);
}
// Establish the RESULT_DECL for the function:
tree resdecl = build_decl (gg_token_location(), RESULT_DECL, NULL_TREE, return_type);
DECL_CONTEXT (resdecl) = function_decl;
DECL_RESULT (function_decl) = resdecl;
// The function_decl has a .function member, a pointer to struct_function.
// This is quietly, almost invisibly, extremely important. You need to
// call this routine after DECL_RESULT has been established:
allocate_struct_function(function_decl, false);
// It will be the caller's responsibility to push this function_decl onto
// the stack at the appropriate time, and create the appropriate context.
return function_decl;
}
void
gg_finalize_function()
{
// Unless it has already been handled:
if( !gg_trans_unit.function_stack.size() )
{
return ;
}
// Finish off the context
gg_pop_context();
/* 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
done with the entire compilation unit.
An actual example:
IDENTIFICATION DIVISION.
PROGRAM-ID. A.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 CWD PIC X(100).
01 LEN_OF_CWD PIC 999 VALUE 100.
PROCEDURE DIVISION.
CALL "getcwd" USING BY REFERENCE CWD BY VALUE LEN_OF_CWD
DISPLAY CWD
goback.
END PROGRAM A.
IDENTIFICATION DIVISION.
PROGRAM-ID. B.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 CWD PIC X(100).
01 RETURNED-CWD PIC X(100).
01 LEN_OF_CWD PIC 999 VALUE 100.
PROCEDURE DIVISION.
CALL "getcwd" USING BY REFERENCE CWD BY VALUE LEN_OF_CWD RETURNING RETURNED-CWD
DISPLAY RETURNED-CWD
goback.
END PROGRAM B.
When we encounter the first call to getcwd, we have no clue as to the
type of the return value, so we assume it is COBOL_FUNCTION_RETURN_TYPE
When we encounter the second call, we learn that it returns CHAR_P. But
an attempt to change the return type of the function_decl will result
in problems if the function_decl of A is processed by the middle end
before we get a chance to change the getcwd functiona_decl.
Hence the need for finalized_function_decls, which gets processed
at the end of the file. */
finalized_function_decls.push_back(current_function->function_decl);
dump_function (TDI_original, current_function->function_decl);
if( gg_trans_unit.function_stack.back().context_count )
{
cbl_internal_error("Residual context count");
}
gg_trans_unit.function_stack.pop_back();
}
void
gg_leaving_the_source_code_file()
{
for( std::vector<tree>::const_iterator it=finalized_function_decls.begin();
it != finalized_function_decls.end();
it++ )
{
//This makes the function visible on the source code module level.
cgraph_node::finalize_function(*it, true);
}
}
void
gg_push_context()
{
// Sit back, relax, prepare to be amazed.
// functions need a context in which they build variables and whatnot.
// they also need to be able to create subcontexts.
// Functions have an DECL_INITIAL member that points to the first block. The
// first block has a BLOCK_VARS member that points to the first of a chain
// of var_decl entries. The first block has a BLOCK_SUBBLOCKS member that
// points to the block of the first subcontext.
// Functions have a DECL_SAVED_TREE member that points to the first bind_expr
// That first bind_expr has a BIND_EXPR_BLOCK that points back to the first block
// has a BIND_EXPR_VARS that points back to the first block's first var_decl
// has a BIND_EXPR_BODY that points to the first statement_list
// Each subsequent context gets a new block that is chained to the prior block through BLOCK_SUBBLOCKS
// Each subsequent context gets a new bind_expr which gets added to the parent context's statement list
// Yes, it's confusing. Have a nice lie-down.
// Here's what we need for this recipe:
// We need a block:
tree block = make_node(BLOCK);
TREE_USED(block) = 1;
BLOCK_SUPERCONTEXT(block) = current_function->function_decl;
// We need a statement list:
tree statement_list = alloc_stmt_list();
// We need a bind_expr:
tree bind_expr = build3(BIND_EXPR,
void_type_node,
NULL_TREE, // There are no vars yet.
statement_list,
block);
TREE_SIDE_EFFECTS(bind_expr) = 1;
// At this point, we might be creating the initial context for a function,
// or we might be creating a sub-context.
if( !DECL_INITIAL(current_function->function_decl) )
{
// We are creating the initial context of the function:
DECL_INITIAL(current_function->function_decl) = block;
DECL_SAVED_TREE(current_function->function_decl) = bind_expr;
// To avoid an N-squared time complexity when chaining blocks, we save the
// current end of the chain of blocks:
current_function->current_block = block;
}
else
{
// We are in the subtext business:
// We need to tack on our new block to the end of the
// chain of existing blocks:
tree cblock = current_function->current_block;
BLOCK_SUBBLOCKS(cblock) = block;
current_function->current_block = block;
// And we need to put our new bind_expr onto the end of the
// current active statement list:
gg_append_statement(bind_expr);
}
// And now we make our statement_list and bind_expr the active ones:
current_function->statement_list_stack.push_back(statement_list);
current_function->bind_expr_stack.push_back(bind_expr);
// And the new context is ready to rock and roll
gg_trans_unit.function_stack.back().context_count += 1;
}
void
gg_pop_context()
{
// Backing out is much easier:
current_function->bind_expr_stack.pop_back();
current_function->statement_list_stack.pop_back();
gg_trans_unit.function_stack.back().context_count -= 1;
}
static
std::unordered_map<std::string, tree> fndecl_from_name;
static
tree
function_decl_from_name(tree return_type,
const char *function_name,
int nargs,
tree arg_types[])
{
tree fndecl;
std::unordered_map<std::string, tree>::const_iterator it =
fndecl_from_name.find(function_name);
if( it != fndecl_from_name.end() )
{
fndecl = it->second;
}
else
{
tree fntype = build_function_type_array(return_type, nargs, arg_types);
fndecl = build_fn_decl (function_name, fntype);
fndecl_from_name[function_name] = fndecl;
}
return fndecl;
}
tree
gg_call_expr(tree return_type, const char *function_name, ...)
{
// Generalized caller. Params are terminated with NULL_TREE
// Use this routine to call function_name when you need the return value.
// Typically you will do something like
// tree call_expr = gg_call_expr(...);
// gg_assign( dest, call_expr );
// Note that everyt time call_expr is laid down, the function will be called,
// so you probably don't want to do things like
// gg_assign( dest1, call_expr );
// gg_assign( dest2, call_expr );
int nargs = 0;
static tree arg_types[ARG_LIMIT+1];
static tree args[ARG_LIMIT+1];
va_list ap;
va_start(ap, function_name);
for(;;)
{
if(nargs >= ARG_LIMIT)
{
yywarn("You *must* be joking");
gcc_unreachable();
}
tree arg = va_arg(ap, tree);
if( arg == NULL_TREE )
{
break;
}
arg_types[nargs] = TREE_TYPE(arg);
args[nargs] = arg;
nargs += 1;
}
arg_types[nargs] = NULL_TREE;
args[nargs] = NULL_TREE;
va_end (ap);
tree function_decl = function_decl_from_name( return_type,
function_name,
nargs,
arg_types);
DECL_EXTERNAL (function_decl) = 1;
tree the_func_addr = build1(ADDR_EXPR,
build_pointer_type (TREE_TYPE(function_decl)),
function_decl);
tree the_call = build_call_array_loc(gg_token_location(),
return_type,
the_func_addr,
nargs,
args);
// This routine returns the call_expr; the caller will have to deal with it
// as described up above
return the_call;
}
void
gg_call(tree return_type, const char *function_name, ...)
{
// Generalized caller. function_name is followed by a NULL_TREE-terminated
// list of formal parameters.
// Use this routine when you don't care about the return value, and
// you want the subroutine to be invoked.
int nargs = 0;
static tree arg_types[ARG_LIMIT+1];
static tree args[ARG_LIMIT+1];
va_list ap;
va_start(ap, function_name);
for(;;)
{
if(nargs >= ARG_LIMIT)
{
yywarn("You *must* be joking");
gcc_unreachable();
}
tree arg = va_arg(ap, tree);
if( arg == NULL_TREE )
{
break;
}
arg_types[nargs] = TREE_TYPE(arg);
args[nargs] = arg;
nargs += 1;
}
arg_types[nargs] = NULL_TREE;
args[nargs] = NULL_TREE;
va_end (ap);
tree function_decl = function_decl_from_name( return_type,
function_name,
nargs,
arg_types);
DECL_EXTERNAL (function_decl) = 1;
tree the_func_addr = build1(ADDR_EXPR,
build_pointer_type (TREE_TYPE(function_decl)),
function_decl);
tree the_call = build_call_array_loc(gg_token_location(),
return_type,
the_func_addr,
nargs,
args);
// This simply executes the_call; any return value is ignored
gg_append_statement(the_call);
}
tree
gg_call_expr_list(tree return_type, tree function_pointer, int param_count, tree args[])
{
// Generalized caller. param_count is the count of params in the arg[]]
// Use this routine when you need the return value. Typically you
// will do something like
// tree call_expr_Plist = gg_call_expr_list(...);
// gg_append_statement(call_expr);
// Note that every time call_expr is invoked, the routine will run again.
// Avoid that with something like
// gg_assign( dest, gg_call_expr_list(...) );
tree the_call = build_call_array_loc(gg_token_location(),
return_type,
function_pointer,
param_count,
args);
// This routine returns the call_expr; the caller will have to deal with it
// as described up above
return the_call;
}
tree
gg_create_bind_expr()
{
// In support of things like PERFORM paragraph, we need to create
// blocks of statements that can be executed.
// This will be a naked bind_expr, like we use for WHILE construction.
// It's not defining a context, so it has no variable list, nor does
// it point to a block.
tree statement_block = make_node(STATEMENT_LIST);
TREE_TYPE(statement_block) = void_type_node;
tree bind_expr = build3( BIND_EXPR,
void_type_node,
NULL_TREE,
statement_block,
NULL_TREE);
return bind_expr;
}
void
gg_exit(tree exit_code)
{
tree the_call =
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_EXIT),
1,
exit_code);
gg_append_statement(the_call);
}
void
gg_abort()
{
tree the_call =
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_ABORT),
0);
gg_append_statement(the_call);
}
tree
gg_strlen(tree psz)
{
tree the_call = fold_convert(
size_type_node,
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_STRLEN),
1,
psz));
return the_call;
}
tree
gg_strdup(tree psz)
{
tree the_call = fold_convert(
build_pointer_type(char_type_node),
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_STRDUP),
1,
psz));
return the_call;
}
/* built_in call to malloc() */
tree
gg_malloc(tree size)
{
tree the_call = fold_convert(
pvoid_type_node,
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_MALLOC),
1,
size));
return the_call;
}
tree
gg_realloc(tree base, tree size)
{
tree the_call = fold_convert(
pvoid_type_node,
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_REALLOC),
2,
base,
size));
return the_call;
}
tree
gg_realloc(tree base, size_t size)
{
return gg_realloc(base, build_int_cst_type(SIZE_T, size));
}
tree
gg_malloc(size_t size)
{
return gg_malloc(build_int_cst_type(SIZE_T, size));
}
void
gg_free(tree pointer)
{
tree the_call =
build_call_expr_loc(gg_token_location(),
builtin_decl_explicit (BUILT_IN_FREE),
1,
pointer);
gg_append_statement(the_call);
}
void
gg_record_statement_list_start()
{
// We need a statement list:
tree statement_list = alloc_stmt_list();
current_function->statement_list_stack.push_back(statement_list);
}
tree
gg_record_statement_list_finish()
{
tree retval = current_function->statement_list_stack.back();
current_function->statement_list_stack.pop_back();
return retval;
}
size_t
gg_sizeof(tree node)
{
size_t size_in_bytes;
if( DECL_P(node) )
{
size_in_bytes = TREE_INT_CST_LOW(TYPE_SIZE_UNIT(TREE_TYPE(node)));
}
else
{
gcc_assert(TYPE_P(node));
size_in_bytes = TREE_INT_CST_LOW(TYPE_SIZE_UNIT(node));
}
return size_in_bytes;
}
tree
gg_array_of_size_t( size_t N, size_t *values)
{
tree retval = gg_define_variable(build_pointer_type(SIZE_T));
tree sz = build_int_cst_type(SIZE_T, N * int_size_in_bytes(SIZE_T));
gg_assign(retval, gg_cast(build_pointer_type(SIZE_T), gg_malloc(sz)));
for(size_t i=0; i<N; i++)
{
gg_assign(gg_array_value(retval, i), build_int_cst_type(SIZE_T, values[i]));
}
return retval;
}
tree
gg_array_of_bytes( size_t N, unsigned char *values)
{
tree retval = gg_define_variable(UCHAR_P);
gg_assign(retval, gg_cast(UCHAR_P, gg_malloc( build_int_cst_type(SIZE_T, N))));
for(size_t i=0; i<N; i++)
{
gg_assign(gg_array_value(retval, i), build_int_cst_type(UCHAR, values[i]));
}
return retval;
}
tree
gg_string_literal(const char *string)
{
/* This is a message in a bottle.
A genapi.cc program calling
gg_call(VOID,
"puts",
build_string_literal(strlen(ach)+1, ach),
NULL_TREE);
ten thousand times compiles about ten percent slower than a C program
calling
puts(ach);
ten thousand times.
Trapping through the C front end reveals that they do not call
build_string_literal(). They instead use build_string() in a way that
I gave up trying to figure out that produces, apparently, more efficient
GENERIC.
Their GENERIC: call_expr -> nop_expr -> addr_expr -> string_cst
My GENERIC: call_expr -> addr_expr -> array_ref -> string_cst
I tried for an hour to duplicate the C stuff, but made no headway.
This comment is a reminder to myself to investigate this, someday, because
I eventually want that ten percent.
*/
return build_string_literal(strlen(string)+1, string);
}
tree
gg_trans_unit_var_decl(const char *var_name)
{
std::unordered_map<std::string, tree>::const_iterator it =
gg_trans_unit.trans_unit_var_decls.find(var_name);
if( it != gg_trans_unit.trans_unit_var_decls.end() )
{
return it->second;
}
return NULL_TREE;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
void
gg_insert_into_assembler(const char ach[])
{
if( !optimize )
{
// Create the required generic tag
tree asm_expr = build5_loc( gg_token_location(),
ASM_EXPR,
VOID,
build_string(strlen(ach), ach),
NULL_TREE,
NULL_TREE,
NULL_TREE,
NULL_TREE);
// And insert it as a statement
gg_append_statement(asm_expr);
}
}
void
gg_insert_into_assemblerf(const char *format, ...)
{
// Temporarily defeat all ASM_EXPR for optimized code per PR119214
// The correct solution using LABEL_DECL is forthcoming
if( !optimize )
{
// This routine inserts text directly into the assembly language stream.
// Note that if for some reason your text has to have a '%' character, it
// needs to be doubled in the GENERIC tag. And that means if it is in the
// 'format' variable, it needs to be quadrupled.
// Create the string to be inserted:
char ach[256];
va_list ap;
va_start(ap, format);
vsnprintf(ach, sizeof(ach), format, ap);
va_end(ap);
gg_insert_into_assembler(ach);
}
}
#pragma GCC diagnostic pop
static location_t sv_token_location_override = 0;
void
token_location_override(location_t loc)
{
sv_token_location_override = loc;
}
location_t
gg_token_location()
{
location_t retval;
if( sv_token_location_override )
{
retval = sv_token_location_override;
sv_token_location_override = 0;
}
else
{
retval = current_token_location();
}
return retval;
}