Jump to content

Mahdi Safsafi

Members
  • Content Count

    383
  • Joined

  • Last visited

  • Days Won

    10

Everything posted by Mahdi Safsafi

  1. Mahdi Safsafi

    Typed constants in Delphi.

    The code showed clearly that they do on x64!!!
  2. Mahdi Safsafi

    Typed constants in Delphi.

    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) !
  3. Mahdi Safsafi

    Typed constants in Delphi.

    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 !
  4. Mahdi Safsafi

    Typed constants in Delphi.

    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. 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 ?
  5. Mahdi Safsafi

    Typed constants in Delphi.

    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.
  6. Mahdi Safsafi

    Typed constants in Delphi.

    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 !!! I accept your example. That does not change the fact that type complexity is not the reason.
  7. Mahdi Safsafi

    Typed constants in Delphi.

    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! 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 ? 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 ???
  8. Mahdi Safsafi

    Typed constants in 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;
  9. Mahdi Safsafi

    Typed constants in Delphi.

    If compiler treats typed-constant as constant it will be a factor and I clearly explained that ! 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"); } } 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.
  10. 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.
  11. Mahdi Safsafi

    Strange behavior for literals

    @Kas Ob. Yes, you're right ! untyped constant is handled as Integer : // a fix: this will produce true, true for both (x86, x64): X := TMyVar(A) shl B; Y := A; Y := TMyVar(Y) shl B;
  12. Mahdi Safsafi

    Strange behavior for literals

    Moreover, compiler on a specific platform such aarch32/aarch64 must not at any case allow shifting outside a given range ! because mostly the opcode is qualified as invalid/reserved. Meaning CPU may raise UD exception. Or worse, you may end-up executing another different instruction.
  13. Mahdi Safsafi

    Strange behavior for literals

    @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. }
  14. Mahdi Safsafi

    Strange behavior for literals

    @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.
  15. Mahdi Safsafi

    Strange behavior for literals

    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
  16. Mahdi Safsafi

    Strange behavior for literals

    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.
  17. Mahdi Safsafi

    Strange behavior for literals

    @Leif Uneus That's because literal act like find and replace macro(something similar for C define macro) for runtime expression(TypeInfo). The problem is with static expressions such SizeOf.
  18. Mahdi Safsafi

    Strange behavior for literals

    @Kas Ob. 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.
  19. Mahdi Safsafi

    Strange behavior for literals

    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).
  20. Mahdi Safsafi

    Strange behavior for literals

    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 !
  21. Mahdi Safsafi

    Strange behavior for 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.
  22. Mahdi Safsafi

    Strange behavior for literals

    @David Heffernan 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;
  23. Mahdi Safsafi

    Passing back a string from an external program

    @David Heffernan You can use LoadLibrary with exe file ! The only things is that you need to keep an eye on initialisation.
  24. Mahdi Safsafi

    Passing back a string from an external program

    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.
  25. In addition to what @Stefan Glienke pointed out, there is a problem with your prototypes, your function does not change aQuality param so why you're passing it by value ? Also you're adding extra-no-sense comparison : if aQuality = rsPoor then Result := 0 // you may remove this one ! ... else Result := 0; // rsPoor covered here !
×