Hello there,
In the previous topic about Strange behavior for literals @Lars Fosdal asked himself a very interesting question about typed-constant. I quote :
In this topic, I'll try to answer the following questions:
- What is a typed-constant ?
- How internally is handled by the compiler ?
- Why a typed-constant can't act as a compile-time constant ?
- What's the relationship between typed-constant and optimization ?
What's a typed constant ?
It is a constant that has a type associated with it.
const NotTypedConstant = StaticExpression;
const TypedConstant : ConstantType = StaticExpression;
// e.g:
const A = 5; // not typed constant.
const B: Integer = 5; // typed constant.
As you can see, all constants must be initialized with a static expression (an expression that the compiler can evaluate at compile time).
However, no-typed-constant can be used in static expression, but a typed-constant can't !
const
A = 5; // Ok.
B = A + 1; // Ok.
C: Integer = B; // Ok.
D: Integer = C; // Error.
Wondering why ? this is because the compiler is seeing C and D as variables (not static-compile-time constant) and it treats them same way as it does with pure variables !
You may think that this kind of behavior is unacceptable ? In fact there is a good reason for that !
How internally is handled by the compiler ?
Please, consider the following example :
// ---------------------------- Unit1 ----------------------------
unit Unit1;
interface
// public constants:
const
MIN = 0;
MAX = 1000;
MyData: array [MIN .. MAX] of Integer = (...);
implementation
// ...
end.
// ---------------------------- Unit2 ----------------------------
unit Unit2;
interface
implementation
uses Unit1; // using Unit1.
function ValidateData : Boolean;
begin
var First := MyData[MIN]; // first item.
var Last := MyData[MAX]; // last item.
// ...
end;
end.
Would not be just perfect to be able to declare First and Last as constants ?
const First: Integer = MyData[MIN];
const Last : Integer = MyData[MAX];
// or
const First = MyData[MIN];
const Last = MyData[MAX];
This is not possible ! because when compiling Unit1, the compiler produce a .dcu file that contains two things : an interface and an implementation (obj) section for Unit1:
// ---------------------------- Unit1.dcu ----------------------------
// Note that a .dcu file is a binary format ... I'm just using text format for demonstration !
interface section:
// static-compile-time constant with definition:
// ----------------------------------------------
const MIN = 0;
const MAX = 1000;
// constant WITHOUT definition:
// -----------------------------
const MyData: array [MIN .. MAX] of Integer;
implementation section:
DataSection:
MyData = [0, 1, ...];
...
CodeSection:
...
When compiling Unit2. the compiler sees uses Unit1; and opens Unit1.dcu file and only going to read the interface section. Meaning the compiler only sees the declaration of MyData, but does not know about the data itself (data inside MyData):
// unit2:
// ------
// knowing the type and size of MyData is more important than knowing whats inside
var value : Integer := MyData[MAX + 100]; // Error violating MyData bounds.
Now, because the compiler does not know about the data. MyData can't be used in a static expression. However you can query it's type and size:
// Unit2
const First = MyData[MIN]; // error. compiler does not know what MyData holds.
const Size = SizeOf(MyData); // Ok. compiler knows the type and size.
Why a typed-constant can't act as a compile-time constant ?
Now, if the compiler is able to read the implementation section from the .dcu file, it will also able to access/read MyData definition and allows using typed-constant inside a static expression ... and much better, it will do a very good job for optimization (constant folding && constant propagation). Excited ? Don't be ! If it does such a thing... It will certainly come at a cost of compilation time : a very long compilation time ! That's because for each unit, the compiler must read both section (interface and implementation) and must process all the data inside the implementation section.
Delphi is a fast compiler and allowing the use of typed-constant inside a static-expression will break that theory.
What's the relationship between typed-constant and optimization ?
So, Is Delphi typed-constant behavior correct ? From my point of view yes !
Is Delphi handling it smoothly ? Absolutely no !!! In fact the compiler does not do any kind of optimization (constant folding & propagation) even if the definition is available (constant declared in the same unit) :
implementation
{$O+}
// this is a private declaration. compiler can access to the definition !
const
MyData: array [0 .. 3] of Integer = (5, 16, 7, 10);
var
First: Integer;
initialization
// since it can access to the definition, it can generate much better code for :
// First := MyData[0];
// mov reg, 5 // MyData[0] = 5.
// but it just generated :
// mov reg, [@MyData + offset]
// ...
end.
The sad reason behind the above generated code is that typed-constant in Delphi is volatile !
Improving optimization:
1) Always declaring ordinal type as a non-typed-constant whenever its possible :
const First = ...; // compile time constant.
const Last = ...; // compile time constant.
const MyData: array [MIN .. MAX] of Integer = (First, ..., Last);
2) Using a Link-Time-Code-Generation (LTCG*): This is some how hard to implement but definitely is the best way to improve optimization one for all ! This gives a full view of the program ... meaning the code-generator will have much opportunity to do function in-lining, constant folding & propagation, ... The good thing, its just an option (so you can just use it when doing a Release build).
LTCG* = is not supported by Delphi. Perhaps its supported with C++Builder (I'm not sure about that). If anyone has a clean info, please make a comment.
Conclusion:
The purpose of typed-constants is to hold a non-ordinal-data(array, record, ...), and a way to share data between units and speed-up compilation-time. For ordinal-type (integer,...), you should always use a non-typed-constant.