Author Topic: Unleashed Pascal (async/await, parallel for, match, string interpolation & more)  (Read 54829 times)

Fibonacci

  • Hero Member
  • *****
  • Posts: 1084
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
Can I just use the installer anew? Because that is a neat feature.

Yes - the installer handles both: fresh install or update of your current installation.



I have a question regarding the new int128...

"Normal FPC", when dealing with mixed signed expressions promotes types as needed up to int64.  This causes problems when the expression uses qwords because there is no int128 "superset type" that could be used to "homogenize" the expression.

My question is: in unleashed, if the modeswitch int128 is enabled and an expression has item types int64 and qword in it, will the compiler automatically promote the types to int128 to do the calculations ?

Two separate cases here, because promotion happens per operator, not per expression.

Case 1: the expression only mixes Int64 and QWord, no 128 bit operand anywhere

No automatic promotion. It behaves exactly like stock FPC (both sides go through Int64), with or without the modeswitch. I kept the switch away from existing expressions on purpose - code that compiles today must mean the same thing tomorrow. BTW, C compilers with __int128 made the same call: an expression that mixes int64_t and uint64_t still resolves to uint64_t (plain unsigned 64 bit), not __int128 - the usual arithmetic conversions never reach for a wider type that isn't already in the expression.

Case 2: at least one operand is Int128/UInt128

Then yes - promotion kicks in, pairwise at each operator. From the first operator that touches the 128 bit operand, everything downstream runs at 128 bits. The variable you assign to plays no role (Pascal has no target-typed evaluation), narrowing happens at the assignment itself: silent truncation by default, range error with -Cr.

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. procedure main;
  4. begin
  5.   var q: QWord   := 18446744073709551615;
  6.   var a: Int128  := 2;
  7.   var n: Integer := 3;
  8.   var x: DWord;
  9.  
  10.   x := q * a * n;         // computed fully in 128 bit, then narrowed ->
  11.   writeln(x);             // 4294967290
  12.   writeln(a * q * n);     // 110680464442257309690 - 128 bit from the first operator
  13.   writeln(q * n * a);     // 36893488147419103226 - surprise!
  14.  
  15.   readln;
  16. end;
  17.  
  18. begin
  19.   main;
  20. end.

The last line is the one to watch: operators group left to right, so q * n is evaluated first, at 64 bits, wraps around, and only the already-wrapped result gets promoted for the final multiply. Same rule C applies to __int128. So to homogenize a mixed expression, put one Int128(...) cast on the leftmost operand and the whole chain follows.

The compiler also warns you when a 64 bit multiply or add lands in a 128 bit slot, so the wrap does not pass unnoticed:

Code: Pascal  [Select][+][-]
  1. var
  2.   a, b: Int64;
  3.   c: Int128;
  4. begin
  5.   c := a * b;   // a*b is a 64 bit multiply, result widened afterwards
  6. end;

Code: Text  [Select][+][-]
  1. Hint: Converting the operands to "Int128" before doing the multiply could prevent overflow errors.

Compile with -vh, or watch the Messages window in Lazarus. It is the same hint FPC already gives on 32 bit targets when a longint*longint result goes into an Int64 - Int128 just moves that safety net one level up.

Compile time constants are a different story: under the switch the constant evaluator works in full 128 bit arithmetic at every step, so writeln(10000000000000000000 * 10 div 10000000000000000000) prints 10 even though the intermediate is 1e20, and the result constant is adapted back down to the smallest fitting type. Past 128 bits you get a compile error. Without the switch every folding step must stay inside the historical Int64/QWord envelope, exactly like stock.



To add: does the compiler optimize to avx2 instructions? Because that is the only benefit when using 128 bit integer types......

No, and no compiler does that - gcc and clang included. As @marcov pointed out, the registers themselves are wide (128 bit in SSE2, 256 bit in AVX2), but they are vectors of smaller elements, and the widest integer element is 64 bit. There is no 128 bit integer ALU behind them: vpaddq adds independent 64 bit lanes and a carry never crosses a lane boundary. Emulating a single 128 bit addition in SIMD means extra instructions just to move the carry from the low lane into the high one - slower than the two instructions the scalar unit needs. SIMD pays off for arrays of independent values, not for one wide integer.

What Unleashed emits on x86_64 is a pair of 64 bit general purpose registers - the same model gcc and clang use for __int128:

Code: ASM  [Select][+][-]
  1. ; a + b
  2. add rax, r8
  3. adc rdx, r9
  4.  
  5. ; a * b
  6. imul rcx, r9
  7. imul rdx, r8
  8. add  rcx, rdx
  9. mul  r8
  10. add  rdx, rcx

add/sub, logic, compares, shifts, mul and the 64<->128 conversions are all inline sequences like the above. Only div/mod, overflow-checked mul and Str/Val stay runtime helpers - which is also what gcc does with __divti3.

Some numbers from my machine (at -O2):

Code: Text  [Select][+][-]
  1. Int128 add: 0.44 ns/op   (Int64 add: 0.22 ns/op)
  2. Int128 mul: 1.33 ns/op   (Int64 mul: 0.86 ns/op)

Roughly a factor of two - the same ratio Int64 had against LongInt back on 32 bit CPUs.

So the benefit is not vectorization. The benefit is a native 128 bit type that costs about twice its 64 bit sibling, plus on SysV targets parameters follow the C __int128 ABI, so cdecl interop with gcc/clang works out of the box.

Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

Thaddy

  • Hero Member
  • *****
  • Posts: 19633
  • Glad to be alive.
It is nice you followed the C abi, Initial tests look all OK here.
Although I am puzzled with this (w/o checks returns 1, not 340282366920938463426481119284349108225)
Code: Pascal  [Select][+][-]
  1. {$mode unleashed}{$R+}{$Q+}//<---
  2. procedure main;
  3. var
  4.   a, b: uInt64;
  5.   c: uInt128;
  6. begin
  7.   a :=High(uint64);
  8.   b := High(Uint64);
  9.   c := a * b;   // a*b is a 64 bit multiply, result widened afterwards
  10.   writeln(c);
  11. end;
  12.  
  13. begin
  14.   main;
  15.   readln;
  16. end.
Throws an arithmetic overflow, but the result of 1 is suspect if checks are omitted.. It should the correct value? It fits in a uint128 easily.
Compare with:
Code: Pascal  [Select][+][-]
  1. {$mode unleashed}{$R+}{$Q+}
  2. procedure main;
  3. var
  4.   a, b: uInt32;
  5.   c: uInt64;
  6. begin
  7.   a :=High(uint32);
  8.   b := High(Uint32);
  9.   c := a * b;   // a*b is a 32 bit multiply, result widened afterwards
  10.   writeln(c);
  11. end;
  12.  
  13. begin
  14.   main;
  15.   readln;
  16. end.
Which computes without error into 18446744065119617025.

Early explicit promotion works correct, though:
Code: Pascal  [Select][+][-]
  1. {$mode unleashed}{$R+}{$Q+}
  2. var
  3.   a, b: UInt64;
  4.   c: UInt128;
  5. begin
  6.   a := High(UInt64);
  7.   b := High(UInt64);
  8.   c := UInt128(a) * b;   // promote to 128-bit
  9.   WriteLn(c);
  10.   readln;
  11. end.
Which outputs:340282366920938463426481119284349108225


« Last Edit: July 18, 2026, 04:15:32 pm by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

440bx

  • Hero Member
  • *****
  • Posts: 6578
@Fibonacci, thank you for the detailed reply.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 19633
  • Glad to be alive.
128 bit quadruple float missing, but that is not an easy fix. Although a lot of it can use the 128 bit code too, it affects platform. I have some ideas on how to approach it, though. Certainly doable.
The current 128bit integer type can not be relied upon when using the math unit: that uses the 64 bit integer types.
Any "programmer" that knows only one programming language is not a programmer

Fibonacci

  • Hero Member
  • *****
  • Posts: 1084
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
Although I am puzzled with this (w/o checks returns 1, not 340282366920938463426481119284349108225)
Code: Pascal  [Select][+][-]
  1. {$mode unleashed}{$R+}{$Q+}//<---
  2. procedure main;
  3. var
  4.   a, b: uInt64;
  5.   c: uInt128;
  6. begin
  7.   a :=High(uint64);
  8.   b := High(Uint64);
  9.   c := a * b;   // a*b is a 64 bit multiply, result widened afterwards
  10.   writeln(c);
  11. end;
  12.  
  13. begin
  14.   main;
  15.   readln;
  16. end.
Throws an arithmetic overflow, but the result of 1 is suspect if checks are omitted.. It should the correct value? It fits in a uint128 easily.
Compare with:
Code: Pascal  [Select][+][-]
  1. {$mode unleashed}{$R+}{$Q+}
  2. procedure main;
  3. var
  4.   a, b: uInt32;
  5.   c: uInt64;
  6. begin
  7.   a :=High(uint32);
  8.   b := High(Uint32);
  9.   c := a * b;   // a*b is a 32 bit multiply, result widened afterwards
  10.   writeln(c);
  11. end;
  12.  
  13. begin
  14.   main;
  15.   readln;
  16. end.
Which computes without error into 18446744065119617025.

Early explicit promotion works correct, though:
Code: Pascal  [Select][+][-]
  1. {$mode unleashed}{$R+}{$Q+}
  2. var
  3.   a, b: UInt64;
  4.   c: UInt128;
  5. begin
  6.   a := High(UInt64);
  7.   b := High(UInt64);
  8.   c := UInt128(a) * b;   // promote to 128-bit
  9.   WriteLn(c);
  10.   readln;
  11. end.
Which outputs:340282366920938463426481119284349108225

Before I get into the details of what's going on here - take your second example (the UInt32 one that "computes without error") and compile it for i386, not x64. See what happens ;)
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

Thaddy

  • Hero Member
  • *****
  • Posts: 19633
  • Glad to be alive.
fpc returns 1, like on 64 bit with 64 bit sizes, as does Delphi7. Delphi 12.1/32 bit crashes. IEEE defines inf or -inf on overflow, not one. (<--- only for floating point)
What a mess. Returning 1 is dangerous. Better crash, like Delphi 12.1, best: return inf. This is with {$R-}{$Q-}
This is a bug in fpc imho. And a bug in Delphi7. Delphi 12.1 behaves better by crashing (with errorcode).
All tested compilers stop in {$R+}{$Q+} mode.

GNU C and MSVC return 1, without overflow protection.
Code: C  [Select][+][-]
  1. #include <stdio.h>
  2. #include <stdint.h>
  3.  
  4. int main() {
  5.     uint32_t a = UINT32_MAX;
  6.     uint32_t b = UINT32_MAX;
  7.     uint32_t c = a * b;
  8.     printf("%u\n", c);
  9. }
That is technically correct because it is defined for at least C that it silently wraps around on overflow, but it is semantically wrong because a result of 1 will result in a working calculation that renders a wrong result.
« Last Edit: July 19, 2026, 04:17:02 am by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

Fibonacci

  • Hero Member
  • *****
  • Posts: 1084
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
Right - and that's the point. On i386 your "correct" UInt32 example returns 1 too, and fires the very same hint ("Converting the operands to "QWord" before doing the multiply could prevent overflow errors."). UInt64*UInt64 -> UInt128 on x64 is the identical case to UInt32*UInt32 -> UInt64 on i386: the multiply runs at operand width, the widening comes after. Same rule, same hint one level up, and - as you found - C and Delphi 7 agree. So it's not an Int128 bug; auto-widening UInt64*UInt64 to 128-bit would be the expression-level promotion I deliberately kept out - it'd silently change what existing code means and diverge from C's __int128.

Whether a silent wrap should trap instead is a fair question - but that's a call for the whole integer model, not for Int128 alone. Change it for the existing case (UInt32*UInt32 -> UInt64 on i386 - that's stock FPC, not something Unleashed added) and I'll mirror it for 64 -> 128 the same day.
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

Thaddy

  • Hero Member
  • *****
  • Posts: 19633
  • Glad to be alive.
Agreed. Also in light of my research.
Note that  {$R} and {$Q} are local switches, so calculations that need protection  can use then on a per block basis.
« Last Edit: July 19, 2026, 08:34:49 am by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

creaothceann

  • Sr. Member
  • ****
  • Posts: 431
@Fibonacci:
Adding "-al" in a project's custom compiler options causes a compilation error: "Error: Assembler as.exe not found, switching to external assembling". No executable is created. This is with a win64 installation (no cross-compilers selected) and win64 project.

"FPC_Unleashed \ fpc322 \ bin \ i386  -win32 \ as.exe" - exists
"FPC_Unleashed \ fpc    \ bin \ x86_64-win64 \ as.exe" - ?


EDIT: main branches for FPC and Lazarus, versions 3.3.1 and 4.99
« Last Edit: July 19, 2026, 09:18:09 pm by creaothceann »

Thaddy

  • Hero Member
  • *****
  • Posts: 19633
  • Glad to be alive.
I have no problems, but then again, my bootstrap 3.2.2 is also 64 bit.
Any "programmer" that knows only one programming language is not a programmer

Fibonacci

  • Hero Member
  • *****
  • Posts: 1084
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
@Fibonacci:
Adding "-al" in a project's custom compiler options causes a compilation error: "Error: Assembler as.exe not found, switching to external assembling". No executable is created. This is with a win64 installation (no cross-compilers selected) and win64 project.

"FPC_Unleashed \ fpc322 \ bin \ i386  -win32 \ as.exe" - exists
"FPC_Unleashed \ fpc    \ bin \ x86_64-win64 \ as.exe" - ?


EDIT: main branches for FPC and Lazarus, versions 3.3.1 and 4.99

Fixed - thanks for reporting. Please reinstall and let me know if you can still reproduce it.
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

creaothceann

  • Sr. Member
  • ****
  • Posts: 431
It works now, thanks. :)

Btw. I tried the installer on a PC with a 4k display too, with the Windows font scaling set to 150%. It makes the text a bit too big, but it still works.

flowCRANE

  • Hero Member
  • *****
  • Posts: 1003
Btw. I tried the installer on a PC with a 4k display too, with the Windows font scaling set to 150%. It makes the text a bit too big, but it still works.

The font size looks good, consistent with the font size used for the text in the window's title bar. The problem here isn't the font, but the fact that AnchorSide isn't being used, which causes the interface to become misaligned because the controls aren't automatically resizing to fit their content.

It's time to get familiar with the Anchor Editor. 8)
Lazarus 4.8 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

hedgehog

  • Full Member
  • ***
  • Posts: 135
It's monumental.
Now, when all modern programming languages are covered with a thick layer of syntactic honey, Pascal was walking around naked.

Can this be shortened somehow?
Code: Pascal  [Select][+][-]
  1. if Assigned(OnStartEvent) then OnStartEvent

440bx

  • Hero Member
  • *****
  • Posts: 6578
Can this be shortened somehow?
Code: Pascal  [Select][+][-]
  1. if Assigned(OnStartEvent) then OnStartEvent
It sure can, all that's needed is an operator that calls the method if its address is not nil.

Something like "OnStartEvent?" or "?OnStartEvent" might be a couple of possibilities.  I don't think the ? is being currently used in FPC and if so, parsing that would be simple and would not create ambiguities.  The prefix form would likely lead to a simpler implementation.


FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018