Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2019-06

Record types with unbounded trailing data

10 messages · 6 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

J. Gareth Moreton

Hi everyone,

So after a problem over at https://bugs.freepascal.org/view.php?id=35753 that led to a change of https://bugs.freepascal.org/view.php?id=35671 , it became apparent that there may need to be better support for unbounded arrays in record types - not open arrays, but unbounded ones.

One of the main culprits is the LOGPALETTE structure that is defined in the Windows unit:

LOGPALETTE = record
   palVersion : WORD;
   palNumEntries : WORD;
   palPalEntry : array[0..0] of PALETTEENTRY;
end;
PLogPalette = ^LOGPALETTE;

(PALETTEENTRY is a relatively simple record type with four Byte fields)

This record type, or rather, a pointer to it, is used for passing in new palette data to the CreatePalette() function.  The number of entries that the API function reads is equal to the palNumEntries field.  However, writing these entries beforehand is difficult to do without raising compiler warnings because the array index will be out of bounds.  Because the Windows API uses such constructs in places, it is necessary to support them to retain full functionality.  Normally, you have to call GetMem to allocate memory for a pointer of type PLogPalette, equal to "SizeOf(LOGPALETTE) + (EntryCount - 1) * SizeOf(PALETTEENTRY)" - this itself is untidy because SizeOf(LOGPALETTE) contains the length of one iteration of SizeOf(PALETTEENTRY) due to the array being defined as having one element (granted, forgetting the "- 1" is usually not critical).

We can't really use a new keyword to define an unbounded array, so I'm wondering if we can work with the syntax for open arrays.  For example, say we redefined LOGPALETTE to the following:

LOGPALETTE = record
   palVersion : WORD;
   palNumEntries : WORD;
   palPalEntry : array of PALETTEENTRY;
end;

Currently, this doesn't work because palPalEntry contains a hidden pointer and length field (which is why "@OpenArray" is not equal to "@OpenArray[0]"), so passing a pointer to this block into CreatePalette() will cause apparent garbage to appear in the first few entries, but I'm wondering if it might it be possible to rework the compiler slightly so if an open array exists as part of a record type, the pointer and length are instead stored before the record's fields in memory.  That is, something akin to:

[Array pointer]   -$10
[Array length]    -$08
[palVersion]
[palNumEntries]   +$02
[palPalEntry]     +$04 (Array length * SizeOf(PALETTEENTRY))

With such a setup, one can declare a variable of type LOGPALETTE rather than a pointer, use SetLength to set the array length, fill in the elements conventionally, then pass the pointer (or a var parameter) into CreatePalette().  There are some nuances to work out, like does @LogPaletteVar return the address of the first field or the hidden array pointer.  The latter is more consistent, but might lead to problems with external API support since it won't point to the expected data.  The former approach fixes this, but this leads to the problem of how a pointer of type PLogPalette is handled by GetMem and FreeMem.

Other things to consider is what happens if a record type has more than one unbounded array, or a regular field is declared after it.  Also, what about if a variable is defined as an array of the record... how is that stored in memory?  Truth be said, when I ask these questions, it makes me wonder if we should use a new keyword, such as "open record" or "unbounded record" or declaring the field as "open array of PALETTEENTRY;" or "unbounded array of PALETTEENTRY;", as it will then be easier to enforce the limitations and minimise the chance of the compiler doing something unexpected.  This leads to the problem of a new keyword possibly causing existing code to no longer compile, although since we're dealing with variable and type definitions, the compiler and the code editor (for the sake of highlighting it) should be smart enough to realise that the word "open", if it appears in a code block, is a method and not a directive, and hopefully people won't declare a new type named "Open" (truthfully, "Unbounded" sounds more likely as a type name, possibly to represent an untyped pointer to a large block of data).  Additionally, due to how such a record is put together, variables of such types become internal pointers rather than direct values.

One final point... if the record type is known to be unbounded, then a potential syntax choice could be to define a variable as follows "PaletteData: LOGPALETTE[256]".  From a code generation point of view, all the memory required for the variable can be reserved in the function prologue in one go (possibly on the stack if it's not too large), saving an expensive reallocation by not requiring a call to SetLength, plus it gives the compiler a distinct upper index to work with, thus returning a genuine error if you attempt to go out of bounds.

It's a lot to think about for sure, and is probably overengineering a solution to a very niche problem (I personally can't think of where one would use such constructs instead of something with a fixed-size array or just having the header and open array stored separarely), but is it worth discussing?

Gareth aka. Kit

---
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Michael Van Canneyt
to J. Gareth Moreton
J. Gareth Moreton wrote 7 lines
Hi everyone, So after a problem over at https://bugs.freepascal.org/view.php?id=35753 that led to a change of https://bugs.freepascal.org/view.php?id=35671 , it became apparent that there may need to be better support for unbounded arrays in record types - not open arrays, but unbounded ones.

[snip]

Show quoted text 6 lines
It's a lot to think about for sure, and is probably overengineering a solution to a very niche problem (I personally can't think of where one would use such constructs instead of something with a fixed-size array or just having the header and open array stored separarely), but is it worth discussing?

No. This *really* is overengineering.

If you declare what is clearly a dummy structure, then tricks will be needed. Disabling range checks on such constructs and warnings is the only sensible thing to do.

If you want to get rid of warnings, define it as
LOGPALETTE = record
    palVersion : WORD;
    palNumEntries : WORD;
    palPalEntry : array[0..(MAXINT div sizeof(PALETTEENTRY)] of PALETTEENTRY;
end;

Since you will always work through a getmem'ed memory block & pointer anyway, and all bets are off in that case. There is simply no point in letting the compiler check anything extra since the construct is by definition uncheckable.

Michael.

Reply by email

J. Gareth Moreton
to Michael Van Canneyt

Thought I'd ask.  Nothing lost.  Just seeking a consensus on how to address the problem.

Gareth

Michael Van Canneyt wrote 47 lines
On Mon, 24 Jun 2019, J. Gareth Moreton wrote: > Hi everyone, > > So after a problem over at > https://bugs.freepascal.org/view.php?id=35753 that led to a change of > https://bugs.freepascal.org/view.php?id=35671 , it became apparent > that there may need to be better support for unbounded arrays in > record types - not open arrays, but unbounded ones. > [snip] > > It's a lot to think about for sure, and is probably overengineering a > solution to a very niche problem (I personally can't think of where > one would use such constructs instead of something with a fixed-size > array or just having the header and open array stored separarely), > but is it worth discussing? No. This *really* is overengineering. If you declare what is clearly a dummy structure, then tricks will be needed. Disabling range checks on such constructs and warnings is the only sensible thing to do. If you want to get rid of warnings, define it as LOGPALETTE = record    palVersion : WORD;    palNumEntries : WORD;    palPalEntry : array[0..(MAXINT div sizeof(PALETTEENTRY)] of PALETTEENTRY; end; Since you will always work through a getmem'ed memory block & pointer anyway, and all bets are off in that case. There is simply no point in letting the compiler check anything extra since the construct is by definition uncheckable. Michael. _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

---
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Sven Barth
to J. Gareth Moreton: So after a problem over at https://bugs.freepascal.org/view.php?id=35753 that led to a…
J. Gareth Moreton wrote 9 lines
We can't really use a new keyword to define an unbounded array, so I'm wondering if we can work with the syntax for open arrays.  For example, say we redefined LOGPALETTE to the following: LOGPALETTE = record   palVersion : WORD;   palNumEntries : WORD;   palPalEntry : array of PALETTEENTRY; end;

Michael already shot this, but nevertheless: if you would use that syntax then how would you differentiate from records that really contain a dynamic array and not such a unbounded one?

Regards,
Sven

Reply by email

J. Gareth Moreton
to Sven Barth

Well, that's when I said that a new keyword may be necessary so there's no ambiguity, but in this case, for a single open array, the two are effectively equivalent.  Even in cases where you might pass the array through the Move routine, you wouldn't do it for the entire record (or an array of them) because of the dynamic array's hidden fields.  A record having multiple dynamic arrays would probably not be eligible for such unbounded treatment.

Some of the semantics would need to be figured out still, but that's assuming it doesn't stay shot down.

Gareth aka. Kit

P.S. There is some danger with redefining LOGPALETTE to have an upper bound of MAXINT on the array, and that's SizeOf(LOGPALETTE) returning an excessively large value, something that would be passed into GetMem and potentially trigger an EOutOfMemory exception (or worse, not, but potentially cause excessive thrashing in a non-obvious way).

Sven Barth wrote 21 lines
Am 24.06.2019 um 05:57 schrieb J. Gareth Moreton: > We can't really use a new keyword to define an unbounded array, so > I'm wondering if we can work with the syntax for open arrays.  For > example, say we redefined LOGPALETTE to the following: > > LOGPALETTE = record >   palVersion : WORD; >   palNumEntries : WORD; >   palPalEntry : array of PALETTEENTRY; > end; Michael already shot this, but nevertheless: if you would use that syntax then how would you differentiate from records that really contain a dynamic array and not such a unbounded one? Regards, Sven _______________________________________________ fpc-devel maillist  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

---
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Martin Frb
to Michael Van Canneyt: [snip] No. This *really* is overengineering. If you declare what is clearly a dummy…
Michael Van Canneyt wrote 8 lines
If you want to get rid of warnings, define it as LOGPALETTE = record    palVersion : WORD;    palNumEntries : WORD;    palPalEntry : array[0..(MAXINT div sizeof(PALETTEENTRY)] of PALETTEENTRY; end;

Which of course can prevent debugging to work.

Older gdb will crash (out of mem).
Newer ones have a mem limit, and will simply give an error.

Gdb even has issues to get individual fields of such large structures.

Reply by email

Michael Van Canneyt
to Martin Frb
Martin Frb wrote 16 lines
On 24/06/2019 06:41, Michael Van Canneyt wrote: > > If you want to get rid of warnings, define it as > LOGPALETTE = record >    palVersion : WORD; >    palNumEntries : WORD; >    palPalEntry : array[0..(MAXINT div sizeof(PALETTEENTRY)] of > PALETTEENTRY; > end; Which of course can prevent debugging to work. Older gdb will crash (out of mem). Newer ones have a mem limit, and will simply give an error. Gdb even has issues to get individual fields of such large structures.

In the other case [0..0] it will presumably refuse to show more than 1 entry ?

As I said, it is a dummy structure, so expecting it to work without tricks is IMO futile. And that apparently extends to debugging as well...

Michael.

Reply by email

Martin Frb
to Michael Van Canneyt
Michael Van Canneyt wrote 26 lines
On Mon, 24 Jun 2019, Martin Frb wrote: > On 24/06/2019 06:41, Michael Van Canneyt wrote: >> >> If you want to get rid of warnings, define it as >> LOGPALETTE = record >>    palVersion : WORD; >>    palNumEntries : WORD; >>    palPalEntry : array[0..(MAXINT div sizeof(PALETTEENTRY)] of >> PALETTEENTRY; >> end; > > Which of course can prevent debugging to work. > > Older gdb will crash (out of mem). > Newer ones have a mem limit, and will simply give an error. > > Gdb even has issues to get individual fields of such large structures. In the other case [0..0] it will presumably refuse to show more than 1 entry ? As I said, it is a dummy structure, so expecting it to work without tricks is IMO futile. And that apparently extends to debugging as well...

It will show one by default, but gdb does not range-check. So one can show each individual entry.

Reply by email

Jonas Maebe
to Ben Grasset
Ben Grasset wrote 14 lines
On Mon, Jun 24, 2019 at 12:41 AM Michael Van Canneyt < > wrote: If you want to get rid of warnings, define it as LOGPALETTE = record     palVersion : WORD;     palNumEntries : WORD;     palPalEntry : array[0..(MAXINT div sizeof(PALETTEENTRY)] of PALETTEENTRY; end; Wouldn't that explicitly make the LOGPALETTE type unavoidably gigantic, though?

Yes, but whether it's an array declared with 1 element or with (MAXINT div sizeof(PALETTEENTRY) element, you will never declare an actual variable that has this type. You will always use pointers to this type.

Jonas

Reply by email