The COBOL FE emits code for a recent ARM VM that is definitely not what
the user or, ahem, the FE author intended.  The observed behavior is
that the program enters an infinite loop calling the main entry point,
eventually exhausting the stack. The observed assembler code does or
does not refer to the GOT and ends up not going where it should. 

We think either we're not using GENERIC as intended, or what we're
doing is tripping up the code generator.  Possibly both.  

The working VM is

hostname = gcc-cobol
uname -m = aarch64
uname -r = 5.15.0-122-generic
uname -s = Linux
uname -v = #132-Ubuntu SMP Thu Aug 29 13:45:17 UTC 2024

The broken VM is

hostname = potato
uname -m = aarch64
uname -r = 6.8.0-60-generic
uname -s = Linux
uname -v = #63-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 15 18:51:58 UTC 2025

The COBOL is 

        IDENTIFICATION DIVISION.
        PROGRAM-ID. prog.
        PROCEDURE DIVISION.
        PROG-MAIN.
            DISPLAY "I am prog"
            CALL "prog2"
            STOP RUN.
        
        IDENTIFICATION DIVISION.
        PROGRAM-ID. prog2.
        PROCEDURE DIVISION.
        PROG-PROG2.
            DISPLAY "I am prog2".
        END PROGRAM prog2.

        END PROGRAM prog.

The problem is a forward reference to a function without external
linkage, namely prog2.  

In COBOL parlance, prog2 is a "contained program".  The containing
program, prog1, can call contained programs but not vice-versa.  There
is no requirement for a function prototype denoting a forward
reference. 

A COBOL program (top-level) is a function with external linkage and C
semantics. A contained program is function with "internal linkage" if
there is such a thing.  In C terms, the above might be represented as 

        void prog() { puts("I am prog"); prog2(); }
        static void prog2() { puts("I am prog2"); }

Names with external linkage are published verbatim.  Names with
internal linkage get an internal name unique to the translation unit, in
this case, "prog2.62". It is the compiler's job, I think obviously, to
find prog2; the linker is not involved.   

Because a contained program always appears after the containing program,
the compiler does not know when it encounters CALL whether "prog2"
names a contained program or is a reference to another module to be
linked in later.  We begin by assuming it's an external reference.  At
EOF we review the CALLs and, for string constants that name contained
programs, substitute the name of the function representing the contained
program.  For your reference, that touch-up work is done by
parser_call_target_update().  

One other data point, as a sidebar.  The target of a CALL statement in
COBOL need not be a literal.  In C there's no syntax to "call by name",
where the name is a mere string determined at runtime.  In COBOL for, 

        CALL P.

P names an alphanumeric variable whose contents are of course resolved
at runtime with dlsym(3), even if the value of P was established when
initialized and never changed.  If we change the above program to call
prog2 through a variable, the program works on both architectures.  The
above substitution does not occur (because the compiler doesn't know
what's being called). dlsym(3) nevertheless finds the internal name, I
think because of -rdynamic. End sidebar.  

The first question, then, is "Are we doing it right?"  If not, what are
the constraints on changing GENERIC as it's being built up?  It would
be nice to support forward references without redesigning the FE.  

If we are doing it right, then we want to report mumble something else
is wrong.  We can supply an infinitude of details, including assembly
listings.  

--jkl

Reply via email to