gm2
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Passing parameters of type 'ARRAY[0..MAXINDEX] OF ElementType' to C


From: Benjamin Kowarsch
Subject: Re: Passing parameters of type 'ARRAY[0..MAXINDEX] OF ElementType' to C
Date: Tue, 30 Jan 2024 19:12:03 +0900

Hi Michael,

I believe I had already answered this in my previous response to Rudolf, but maybe it wasn't clear enough. I will therefore try to explain the matter in more detail and illustrate with examples and their respective stack frames.

First, there are three general cases to consider:

(1) parameter passing both syntactically and actually by value
(2) parameter passing both syntactically and actually by reference
(3) parameter passing syntactically by value, but actually by reference

I am going to ignore any sub-cases where parameters are passed in registers* because (a) there is no notion of any 'before' and 'after' when passing parameters in registers, (b) it is not likely that open array parameters are passed in registers, at least not when actually passing by value, and (c) it is even less likely when interfacing to C.

For parameter passing on the stack, a stack frame or activation record in C is as follows:

Stack Frame:
| caller's saved registers |
| parameter N |
...
| parameter 2 |
| parameter 1 |
| parameter 0 |
| caller's return address |
| caller's saved stack frame | <= Frame Pointer
| local variable 0 |
| local variable 1 |
| local variable 2 |
...
| local variable N |
| callee's saved registers |

The stack grows from higher addresses to lower addresses. Thus, local variables are accessed by subtracting their respective offsets from the frame pointer and parameters are accesses by adding their respective offsets to the frame pointer (FP):

local variable Vn = FP - Offset(Vn)
parameter Pn = FP + Offset(Pn)

The compiler needs to calculate the respective offsets at compile time.

For parameters whose size is known at compile time, the compiler generates code that adds static offsets to the frame pointer.

parameter p = FP + Offset(p)

For parameters passed by value whose size is unknown at compile time, such as is the case with open array parameters, the compiler has to generate code that adds a dynamic offset to the frame pointer. The offset is then calculated at runtime by adding the value of the (hidden) HIGH parameter to the address at the offset where the array parameter starts.

open parameter p[HIGH(p)] = FP + Offset(p) + Value(FP + Offset(high))

This requires the offset of the HIGH parameter to be determinable without knowing its value.

And this means that the HIGH parameter must be placed on the stack frame 'before' the open array. In this context 'before' means it must be at an address that is closer to the frame pointer than the open array itself.

PROCEDURE P ( HIDDEN high : CARDINAL; str : ARRAY OF CHAR );

Stack Frame Segment
|  str[n]  |
...
|  str[2]  |
|  str[1]  |
|  str[0]  |  <= FP + Offset(str)
|  n        |  <= FP + Offset(high)

If the HIGH parameter was placed on the stack 'after' the open array, then it would not be possible to address its location on the stack without knowing its value beforehand. Here again, 'before' means located at an address that is closer to the frame pointer than the open array itself.

PROCEDURE P ( str : ARRAY OF CHAR; HIDDEN high : CARDINAL );

Stack Frame Segment
|  n        |  <= how do we get here without already knowing what value n is?
|  str[n]  |
...
|  str[2]  |
|  str[1]  |
|  str[0]  |  <= FP + Offset(str)

HOWEVER, when interfacing to C we need to take into consideration that arrays are pointers. Thus, we are actually dealing with case #3.

A Modula-2 procedure

PROCEDURE P ( str : ARRAY OF CHAR );

although syntactically passing str by value, will actually pass str by reference and thereby the size of parameter str is static as it is an address. In this case it does not matter whether the hidden HIGH parameter is placed 'before' or 'after' the array parameter.

Both

void P ( unsigned high, char *str );

and

void P ( char *str, unsigned high );

are feasible implementations.

It is left to the compiler implementor to decide the parameter order.


If you take a look at Gaius' example code in unix4.def and unix4.c, you will see that he chose to implement open array parameter passing with a single record containing a pointer to the array and a length field.

However, he could also have chosen to implement what C calls a variable length array:

typedef struct unbounded_s {
  unsigned length;
  char contents[];
} unbounded;

This would have been matching the case of passing an open array by value in Modula-2 more closely. In this case, the length field must occur before the contents for the same reason that the hidden HIGH parameter in Modula-2 must be placed 'before' the open array when actually passing by value. 

hope this clarifies
regards
benjamin


[*] When parameters are passed in registers, there is obviously no such thing as 'before' or 'after'. What matters is that values of or references to parameters passed in registers are directly addressable without having to calculate the location of these values or references, neither at compile time, nor at runtime.

In the event that an open array parameter is passed in registers, it is highly unlikely that the array is passed by value because the compiler has no way of telling when the actual array passed at runtime will be small enough to fit into one or more registers. The compiler would need to generate separate versions of the procedure to accommodate both the case that the array fits, and the case that it doesn't, each with a different passing convention. Then it would need to insert code to check the size before any call to that procedure to determine which version of the procedure to call and then generate code to pass the array in one or more registers in the former case, or to pass the array on the stack in the latter case. It cannot categorically be ruled out that a highly optimising compiler will ever use such an approach but it is safe to assume that the additional complexity would not likely justify whatever performance gain might be obtained.


On Mon, 29 Jan 2024 at 21:43, Michael Riedl <udo-michael.riedl@t-online.de> wrote:

Benjamin,

can you have a look on the solution I provided in my last mail - I think the "high" parameter is comming after the actual value. Maybe I am wrong,

anyhow, worth checking.

Gruß

Michael

Am 28.01.24 um 18:04 schrieb Benjamin Kowarsch:
Gaius will probably answer this with the specific parameter passing and foreign function interfacing conventions used by GM2, but I would like to recommend a different approach which is to use open array parameters.

The actual number of array components of an open array parameter is passed as a hidden parameter before the open array parameter.

PROCEDURE P ( str : ARRAY OF CHAR );

is actually

PROCEDURE P ( count : CARDINAL; str : ARRAY OF CHAR );

and in C

void p ( unsigned argc, char str[] );

This convention is pretty universal for most if not all Modula-2 compilers and thus its use is portable.

When using specific array types, varying parameter passing conventions will apply depending on the compiler and even use cases, for example, a compiler or its optimising back-end may decide to pass fixed arrays exceeding a certain size by reference even if they aren't VAR parameters. The code will then become more difficult to maintain and non-portable.

regards
benjamin

On Sun, 28 Jan 2024 at 22:03, Rudolf Schubert <rudolf@muc.de> wrote:
         High Gaius,

         I'm facing a problem when trying to pass a parameter of typ String256
         to C, which I've defined like this:

         CONST
           len256=                       256;
         TYPE
           String256=                    ARRAY[0..len256-1] OF CHAR;

         In the appended example I've set up three DEFINITION MODULEs
         (unix1.def, unix2.def, unix3.def) each with a corresponding
         C file (unix1.c, unix2.c, unix3.c). There is only one simple
         function in these C files which should print out the length
         of the passed string and the string proper.

         unix1.def does NOT have 'FOR "C"' whereas unix2.def and unix3.def
         do have a 'FOR "C"' in the MODULE definition. In unix1.def and unix2.def
         the PROCEDURE ParamTest has got (my_str: String256) but in unix3.def
         I'm using 'varargs' (...).

         I'm seeing that only unix3 does give the expected results. unix1 and
         unix2 do either crash the programm or return wrong results.

         I do observe similar behaviour not only for

         type 'ARRAY[0..MAXINDEX] OF CHAR'

         but for instance also for

         type 'ARRAY[0..MAXINDEX] OF CARDINAL'

         Now my question is: how would I pass parameters of this type to
         C functions? I would suspect that this should not only work for
         'varargs' but also in the other cases? When using open ARRAYs in
         the PROCEDURE things are also working fine. It's just when using
         'non open' ARRAYs that the problem occurs.

BR

Rudolf

--
Rudolf Schubert                 \
Kirchstr. 18a                    \  mailto:rudolf@muc.de
82054 Sauerlach                   > http://www.dose.muc.de
Deutschland                      /   (alpine archkiste)
Tel. 08104/908311               /

reply via email to

[Prev in Thread] Current Thread [Next in Thread]