Jump to content

Mahdi Safsafi

Members
  • Content Count

    383
  • Joined

  • Last visited

  • Days Won

    10

Posts posted by Mahdi Safsafi


  1. Quote

    My point is that the difference in compilation time is insignificant, and absolutely should not drive your choices of how to write the code. 

    Your point was that typed-constants have no impact on compilation time (you were strongly believing on that, called my explanation BS and asked for tests) !


  2. I forgot to mention an important point that I believe will change your mind about typed-symbols: 

    I'll give you a very basic example (and I'm sure you've experienced this before). When you use many dll(s) ... did you notice that the startup of your app has significantly taking additional time ??? that's because the dynamic-linker (a component from the os-loader) is fixing your symbols !


  3. Quote

    That's clearly a deficiency in the x64 compiler implementation since the x86 compiler doesn't suffer the problem. The x86 compiler demonstrates that the difference in performance is not due to a fundamental conceptual hurdle, but a poor implementation. We know that the x64 compiler performs very poorly. 

    Indeed Delphi x64 compiler has a poor implementation, but that's not the primary reason why it performs slowly than an x86 compiler ! In fact, If you've ever used a toolset other than Delphi, you wan't give such explanation ! you'll just realize that compilerX32/linkerX32 outperforms compilerX64/linkerX64. E.g : MSVC (are MS and other teams implementing a super implementation for x86 and a poor one for x64 too ?????).

    The reason behind this is subject to the following(but not limited) :

    - x64-Linker uses long type for calculating addresses. (Int64 vs Int32).

    - x64-Object file size usually is greater than a comparable x86-object file.

    -  x64-Linker does an extra heavy work : fixing/linking exception tables (SEH). 

    - Finally, It's not fair to compare x64 vs x86 (different file-format, different arch, different exception mechanism, ...).

    - ...

    FYI, this is slowly going to change (a lot of efforts are made by clang/gcc/icc) but still an x64 toolset has a lot of challenges.

    Quote

    In any case the test is totally unrealistic. You aren't going to encounter this in real world code. 

    First, even if the test is unrealistic, I want to point you that I did my best to make a very fair test comparison :

    - I compiled tests without debug-info ( flavor for typed-constants (TC)).

    - Each symbol is used ONLY once ... meaning only one relocation (favor for TC).

    - All symbols are declared on the same file (favor for TC).

    - No detailed map file (favor for TC).

    - I used short name for all symbols (favor for TC).

    As you can see, all the above conditions where made in favor of TC (I wanted a true fair conditions !). In a real world, breaking the above rules is very easy and may lead to slow compilation. 

     

    Second, I will give you a real world example where a usage of just 32 typed-constant can slow-down compilation :

    unit BinarySearch;
    interface
    
    const BIT_0: Integer  = 1 shl 0;
    // ..
    const BIT_31: Integer = 1 shl 31;
    
    function binary_search(value: Integer): Integer;
    
    implementation
    // node count > 10K
    function binary_search(value: Integer): Integer;
    begin
    	if( (value and BIT_0) <> 0)then
    	begin
    		// state0
    		if ((value and BIT_X) <> 0) then
    		begin
    			// state1
    			// another if ...
    			// terminal node.
    		end
    	end 
    	else
    	begin
    		// statex
    		if ((value and BIT_X) <> 0) then
    		begin
    			// another if ...
    			// terminal node.
    		end
    	end;
    end;	
    end.

    Here is a sample tree, tree2. The first time I developed the code for pascal, it was looking just like above (the decision behind this was to increase readability/debug). When everything was working perfectly, I decided to replace BIT_X with hex value (I wasn't comfortable seeing unppaded text) ... When I did this I noticed a small improvement on compilation time ... the reason that made me investigate on typed-constant ! It was a small improvement but noticeable !

     

    Now, if you still insist, than you should really start giving me a good explanation -as I did- (forget about good explanation, just give an explanation or even come up with just a theory) why a typed-constant does not  add any extra overhead on the compiler/linker ?

    • Like 1

  4. Quote

    Show me some evidence based on measurements. 

     

    Since I gave an explanation, I believe that it's you who should give a test! Anyway, I made a simple Perl-script that generates random constants values and a dummy function that consumes those constants:

    unit DummyTypedUnit;
    
    interface
    
    const 
    	A0 : Integer = RandomValue;
    	...
    	AN : Integer = RandomValue;
    
    implementation
    
    end.
    
    // -----------------------------
    unit DummyUnTypedUnit;
    
    interface
    
    const 
    	A0  = RandomValue;
    	...
    	AN  = RandomValue;
    
    implementation
    
    end.
    
    
    program [Un]TypedTest;
    
    {$APPTYPE CONSOLE}
    {$R *.res}
    
    uses
      System.SysUtils,
      Dummy[Un]TypedUnit in 'Dummy[Un]TypedUnit.pas';
    
    function Dummy(Input: Integer): Integer;
    begin
      Result := 0;
      if Input = A0 then inc (Result);
      // ...
      if Input = AN then inc (Result);
    end;
    
    var
      i: Integer;
    
    begin
      Read(i);
      i := Dummy(i);
      WriteLn(IntToStr(i));
    
    end.

    For each n time, I compiled both test on x86 and x64:
     

    # result ; second time
    # n = number of constant
    # time in min.sec.ms
    
    # x86:
    # ----
    
    n = 100    :
    untyped    : 00.01.12
    typed      : 00.01.18
    
    n = 1000   :
    untyped    : 00.01.14
    typed      : 00.01.16
    
    n = 10000  :
    untyped    : 00.00.88
    typed      : 00.00.82
    
    n = 100000 :
    untyped    : 00.05.85
    typed      : 00.06.26
    
    #x64:
    #----
    
    n = 100    :
    untyped    : 00.01.10
    typed      : 00.01.09
    
    n = 1000   :
    untyped    : 00.01.12
    typed      : 00.01.15
    
    n = 10000  :
    untyped    : 00.01.56
    typed      : 00.02.02
    
    n = 100000 :
    untyped    : 00.08.90    ;   00.06.48
    typed      : 01.00.95    ;   00.48.15


    I did my best to make them run on a fair conditions :
    - Declaring constants in the same unit.
    - Using a short name for constants.


    In a world where typed-constants are declared on different units and mixed with long name/type things may get worse specially for x64 !

    Let me know if you need to test yourself, I'll be glade to send script and tests.

     

     

    • Like 1

  5. Quote

    You do. If you can't provide a real world example where compilation time is significantly impacted, then I call BS. 

    That's prove the lack of your experience toward manual compilation/linking and absolutely you have no idea about how a linker work ! 
    I'll just give a short explanation :
    When declaring a typed-constant, the compiler must mangle its name (this may slow compilation) so the linker can clearly uses/links the correct symbol: Usually a mangled name = Unit-Name+Symbol-Name+Additional-Information. Whenever the symbol is used, the compiler just emits a blank case ... and adds a new entry in the symbol table !
    Now, when linking, the linker must iterates through all the blank cases and makes a fix-up ! Now because a symbol table is stored as a hash table, In many time it just happens that the table contains a collision which will be solved by raw string comparison ... a source for a slow linking time. 

    If you just ask any experienced c++ developer (because those people who do much manual linking) about what makes a link time slow. He/She will definitely answer: symbol-table size. Moreover the length of the mangled symbol can play a role too ... there was a study I have read a while ago proving that using short name for template type/variable may improve linker time. Just google , you'll find much topics talking about linker.

    Untyped-constants on the other hand do not require mangling or linking (they act like a macro) all handled by the compiler.

    Now, do you still believe that you need a test to just prove that compilation/linking of typed-constants is slower against constants ? If so, please I'd like to see from you a real world example !!!

     

    Quote

    As I said, when I need to take the address. An example from my code base is when calling external code, written in Fortran, that expects values to be passed by reference always. 

    I accept your example.

     

    Quote

    I'm talking about the language as it stands today. Your original post was too. 

    That does not change the fact that type complexity is not the reason.


  6. Quote

    I didn't see the part where you measured compile times. 

    You don't need to measure the elapsed time !!! think about it : a typed-constant must be mangled by the compiler and the linker must resolve the symbol !!! you're adding an extra overhead to the linker when just you'have the possibility to write const a = 1!
     

    Quote

    It is true. We're talking about Delphi. We're not talking about some potential other language.

     

    I gave the example just to show you that the reason behind not allowing a true constant for array, record, ... wasn't about the complexity of the type (they can do it at any time ... but they didn't ! not because the type is complex ... after all it's statically initialized ! if the compiler evaluated it ... it can handle it !!!!!!!). Are you convinced ?
     

    Quote

    The fact that you've never come across a need for taking the address of a constant doesn't mean the need doesn't exist. 

    If the Delphi compiler allowed dynamic constant expression ... I may need it. Otherwise why should I bother my self with address of a static constant ? unless as I said you want to use pointer manipulation ! 

    Can you give a good example on when using an ordinal-typed-constant is better than just a constant ???
     


  7. 14 minutes ago, Leif Uneus said:

    Whether a typed constant is immutable or not is controlled by a compiler switch. See http://docwiki.embarcadero.com/RADStudio/en/Writeable_typed_constants_(Delphi)

    That's not a true immutability ... because the const keyword only qualifies the variable and not the type !

    procedure foo;
    const
      a: Integer = 5; 
    var 
      P: PInteger;
    begin
      P := @a;   // typeof(@a) = typeof(P) ... thats not an immutability !!!!!!!!!!!!!!
      P^ := 3;   // Ok.
    end;

     


  8.  

    Quote

    I don't think the compilation speed is a factor

    If compiler treats typed-constant as constant it will be a factor and I clearly explained that !

    Quote

    The factors that force you to use typed constants are broadly because the type is a complex type which doesn't permit true constants

    That's not true ! they could allow using complex type ... but they just didn't for the reason I explained ... they want a performance compilation-time. FYI, the D language for example allows complex compile time constant !!!

    Here is a quick example just to prove that this is possible :

    import std;
    
    struct TPoint{
    	int left;
    	int right;
    }
    
    static const TPoint[] points = [{0, 10}, {10, 20}, {30, 40}]; //  static-compile-time constant array [0..2] of TPoint
    
    // compile time if
    static if (points[1].left == 10){
    	version = TEN; // just like {$define TEN}
    }
    
    void main()
    {
        version(TEN) // {$IFDEF TEN}
        {
        	writeln("ten");
        }
    }

     

    Quote

    Or you need to take the address of the constant. 

    If you need to get an address of a constant, you should probably make it a variable ! Getting and address usually involves pointer manipulation. For me I prefer to use a variable. Constant in Delphi is not immutable.  


  9. Hello there,

    In the previous topic about Strange behavior for literals @Lars Fosdal asked himself a very interesting question about typed-constant. I quote :

    Quote

    For me, it is a bigger challenge that a typed constant is not regarded as an actual constant.

    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.

     

    • Like 1

  10. @Kas Ob.

    For programming language, the better way for my opinion is to refuse compiling. 

    On x86, the CPU may just choose to mask the amount and continue execution.

    On ARM, It's even not possible to encode an amount (as immediate) that violates instruction range ! However when amount is specified as register. CPU masks the amount before processing the instruction:

    # x86:
    shl r/m32, amount  # amount = amount and RegSizeMask.
    
    # aarch32(arm):
    lsl Rd Rm, 65      #  you can not encode it !
    lsl Rd, Rm, Rs     #  Rs = Rs and RegSizeMask.
    
    # aarch64:
    shl Vd, Vn, amount #  UNDEFINED if shift > RegSizeMask!

     

    While masking the amount is the most popular way ... its still considered as unpredictable behavior and may vary between implementation and all are corrects !

    MSVC:

    const int C = 1 << 100 ; // just warning ... but it compiles and gives 0!
    
    int main()
    {
    	cout << C << endl; // C = 0.	
    }

     


  11. @Kas Ob.

    Again, shifting outside range is implementation defined as I said before !!! Compilers, Interpreters, CPU, Virtual-Machines, are free to choose how to handle it !!! The behavior may even vary between same vendor products !!!

    So comparing to FPC is worthless !!!

    Delphi always mask with 0x1F meaning for your example :

    X := A shl b; // TConst=UInt64 => B = 63 and $1F = 31.  31 < 64 ... compiles.
    X := A shl b; // TConst=UInt32 => B = 63 and $1F = 31.  31 < 32 ... compiles.
    X := A shl b; // TConst=UInt16 => B = 63 and $1F = 31.  31 > 16 ... error.

     


  12. Yes you're right ! Compiler and CPU love UInt32 type ... its the CPU native word ! 

    Even for x64 machine, CPU always prefer UInt32 type over UInt64. And gives much performance against any other type (UInt8, UInt16, UInt64, ...). 

    For performance reason, you should always use UInt32/Int32 type even if your data is less than U/Int32. 

    Final things: M is a UInt64 global variable stored in memory. Meaning you can view/access it as array! When comparing it against UIn32, UInt16, UInt8, the compiler can just compare its elements. However in your example, the compiler generated a non optimized code ! 

     

    # In fact Its much better to do : 
    cmp  (dword|word|byte)  [@M + offset], constant 
    # rather than:
    mov rax, constant 
    cmp qword [@M], rax

     


  13. 7 minutes ago, Kas Ob. said:

    Thank you for explaining,

    Now i want to ask for your opinion about the generated assembly from my example above, and i was explicitly trying to hint compiler that i want the consistent of type UInt64, compiler did refused to compile "C : UInt64 = 1 shl 63" but accepted 1 shl 63 and UInt64(1 shl 63) in if statement, in both cases the result was false comparison.

     

    Const.thumb.png.0cad397c44b7e4af9634de8528485172.png

     

    is this consistent behaviour for a compiler ?

    is it abnormal and faulty?

     

    Two different questions.

    That's because the compiler uses two different evaluator. One for static expression. And the other for optimization.

    C : UInt64 = 1 shl 63 this does not compile because it uses static expression evaluator.

    Inside if that is another story because the expression is not static (it's a dynamic expression). The compiler may not evaluate it and if does it just a kindness from him. Now whenever the compiler sees an if statement it checks if the expression inside it could be evaluated if so it evaluates it but using another component evaluator (not the static evaluator). And that's why you get two different result (the static evaluator complained, but the other just didn't care). 

    About the generated assembly, that's correct and is just a kind of optimization :

    # Rather than emitting :
    mov rax, -1
    shl rax, 63
    cmp rax, qword[@M]
    jx @address
    # it just choose to optimize things:
    cmp qword[@M], (-1 shl 64)

    Did you know

    const
      c: UInt64 = 1 shl 63; // does not compile.
      d: UInt64 = 1 shl (63 + 1); // compile fine.
      e: UInt64 = 1 shl 64; // Ok  twoo.

     

     


  14.  @Kas Ob. 

    Quote
    
      C: Int32= Int32(-1) shl 63; 
    

    In most CPU, shifting outside register bits range is qualified as implementation defined. Meaning CPU vendor may: 

    - May mask the amount with register bits size and continue execution.

    - Execute instruction as NOP.

    - May raise exception.

    - ...

    The first one is the most popular.

    In MSVC compiler, it choose to return 0 value

    However, Delphi on the other hand, choose to mask the amount with 0x1F before computing the value. So the compiler see the above expression as: C : Int32 = Int32(-1) shl (63 and $1F);

     

    So its not a bug ... it just how the compiler choose to handle it. 

     


  15. 20 minutes ago, Daniel said:

    @Mahdi Safsafi there is no need to convince someone here. Sometimes we just have different views on an item. This is not wrong by itself.

    if you think that you have found an error in Delphi, please create a bug-report at quality.embarcadero.com

    I'm not trying to convince anyone ! Everyone is free to believe in whatever think is right. I just tried to give a clean explanation (from my point of view of course).  


  16. 39 minutes ago, David Heffernan said:

    I don't think the issue is that I don't see the full picture and have aack of clarity

    Saying that "The inability to control the type of a floating point literal is the only real issue here." pointed me to believe that you're not seeing the full picture !


  17. 12 minutes ago, David Heffernan said:

    As far as I am concerned the only issue raised in this thread that can't be worked around is the type of floating point literals. Integer literals can be cast. But not floating point literals. 

    @David Heffernan

    I believe that you are not seeing the full picture here : The real issue is the dissociative type between literal and expression (not like what you understand "the ability to control FP constants") .

    BTW you don't work around an issue ... you need to fix it (fixing the type system will let you control FP type) !!

    This is a critical issue ! For now it just looks less critical but just think if at some time Delphi introduces type inquiring feature ! what will happen ?

    I was working on a Pascal-like compiler and I used a weak-typed literal system for constants, meaning the const was stored using the longest available type and casted on demand. I got a very surprising result when inquiring types/expressions/variables. This was my motivation to inspect Delphi constants. So I'm very sure that this aspect is wrong and will at some point produce unpredictable behaviors.

    I hope -by now- that you are seeing the full picture and everything is clear for you.

     

    • Confused 1

  18. @David Heffernan

     

    Quote

    The inability to control the type of a floating point literal is the only real issue here. 

    No that's not the issue !! In fact the compiler by its design is free to assume literal type AND force it (prevent you from modifying it by emitting error when casting it to another type) Most scripting languages do that (they assume it is double and they do not let you control it). However the real issue here is that sizeof(1) != sizeof(+1) !!! 
    Why ? this is because there is a dissociative system between literal and expression type. Delphi choose the smallest type to represent literal. However it choose the Word/Native CPU available type for expression. This is wrong ! because literal is an expression too (terminal) ! In LLVM for example constants are all typed and the strong-typing system there prevent you from operating on two different types. 

    const
      ConstA = 1;
      ConstB = +1;
      ConstC = 0.2;
      ConstD = Single(0.2); // I don't mind but I will do something behind your back !
    
    begin
      Writeln(IntToStr(SizeOf(ConstA))); // size = 1
      Writeln(IntToStr(SizeOf(ConstB))); // size = 4
      Writeln(IntToStr(SizeOf(ConstC))); // size = 8
      Writeln(IntToStr(SizeOf(ConstD))); // size = 10
      Readln;
    end.
    
    
    //  correct behavior is:
    
    // ----------------------
    
    const
      ConstA = 1; // sizeof(1) = sizeof(OrdinalExpression).
      ConstB = +1; // sizeof(+1) = sizeof(OrdinalExpression).
      ConstD = Single(0.2); // error static expression must be of type Extend.
      ConstD2 = Single(0.2); // type of ConstD2 = Single.

    For the last thing: I'd prefer :

    const
      d = 0.1;
      s = 0.1f;

     


  19. Hello,

     I found a very strange behavior about how Delphi handle literals.

    Here is a quick example (Delphi 10.3) showing what I found : 

     

    // For each error, read the comment ... comment the line and next.
    const
      F1 = 0.2;
      F2 = Single(0.2); // this is not a single type ... see below !
      I1: Integer = F1; // E2010 Incompatible types: Integer and Comp.    => F1 = Comp ! 
      I2: Integer = F2; // E2010 Incompatible types: Integer and Extended => F2 = Extended even when specifying it as Single !
      ConstInt1 = 1; // primary   expression
      ConstInt2 = +1; // unary    expression
      SizeOfConstInt1 = sizeof(ConstInt1); // size = 1 !
      SizeOfConstInt2 = sizeof(ConstInt2); // size = 4 !
    
      FArray = [1, 2, 3] + [4, 5, 6]; // untyped array.
    
    function GetFArrayPointerUsingAsm(): Pointer;
    asm
      lea eax,   FArray[0]  // return invalid pointer !
    end;
    
    procedure ProcessFArray(const X);
    begin
      // compiler pushed unkown STACK reference !
    end;
    
    var
      len: Integer;
      size: Integer;
      P: PInteger;
      value: Integer = 3;
    
    begin
      len := length([1, 2, 3] + [4, 5, 6]); // len = 6
      len := length(FArray); // Error E2008 Incompatible type !
    
      size := sizeof([1, 2, 3] + [4, 5, 6]); // error does not compile.
      size := sizeof(FArray); // wrong size = 1 !
      size := sizeof([1, 2, 3]); // wrong size = 1 !
    
      P := @FArray[0]; // Error E2016 Array type required.
      P := GetFArrayPointerUsingAsm(); // function returns invalid pointer !
      ProcessFArray(FArray); // compiler passed unkown/invalid stack reference !
    
      if (value in FArray) then // Ok.
      begin
        Writeln('FArray contains value'); // passed.
      end;
      Readln;
    
    end.

    I'm not even going to mention typed constants or constants that may change ! I just believe that it's really the time to consider checking the compiler's const type system.


  20. Quote

    Converting program2 into a DLL or Package is not an option in this case, neither is integrating the source code of program2 to program1.

    You don't need to convert Program2 into DLL. Just exports your functions and consume them directly from Program1 (just like DLL).

     

    // ========================================== Program1 ==========================================
    program Project1;
    {$APPTYPE CONSOLE}
    {$R *.res}
    uses
      System.SysUtils,
      WinApi.Windows;
    const
      Program2 = 'Project2.exe';
    type
      TGetProgram2Result = function(Param1, Param2: Integer): string;
    var
      LIB: HMODULE;
      GetProgram2Result: TGetProgram2Result = nil;
      s: string = '';
    begin
      try
        LIB := LoadLibrary(Program2);
        @GetProgram2Result := GetProcAddress(LIB, 'GetProgram2Result');
        s := GetProgram2Result(5, 2);
        Writeln(s);
        Readln;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.
    
    // ========================================== Program2 ==========================================
    program Project2;
    {$APPTYPE CONSOLE}
    {$R *.res}
    uses
      System.SysUtils,
      WinApi.Windows;
    function GetProgram2Result(Param1, Param2: Integer): string;
    begin
      Result := Format('%d+%d=%d', [Param1, Param2, Param1 + Param2]);
    end;
    // ------------- EXPORTS -------------
    exports GetProgram2Result;
    begin
      try
        Writeln('This is Program2');
        Writeln(GetProgram2Result(5, 2));
        Readln;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.

     

×