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

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 13018
  • FPC developer.

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
Still opinion based, an no facts to support it.

Demanding facts from someone in the case of the swap operator is just as absurd as it is for the >< operator or even the += operator. Such facts simply do not exist.

Quote
You are diverging from the topic.

No, I gave an example that fits the discussion very well, showing how a dedicated operator for a concrete, specialized operation is the better choice. But of course, that too will be dismissed as a subjective, unfounded opinion.

Quote
Why is it an explicit case for an operator in the first place?

I’ve already explained why, but you’re either successfully ignoring my arguments or dismissing them as baseless opinions, asking for facts that don’t exist.

A separate operator for such a specialized operation was just a rough suggestion; I don’t care whether it’s an operator or an intrinsic, as long as the language allows for a shorthand form of the swap (without having to declare additional variables and manually copy values between three variables) and is able to optimize that swap for higher performance.

Quote
Well, I'm not.

Your profile's Personal text field says FPC Developer. If you're not an FPC developer, your personal text is misleading.



Or Exchange, which would fit neatly with InterlockedExchange, InterlockedCompareExchange etc.

That's a great idea, I like it. So screw the operator—let it be an Exchange intrinsic and everyone will be happy.
« Last Edit: May 29, 2026, 09:32:34 pm by flowCRANE »
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.

440bx

  • Hero Member
  • *****
  • Posts: 6619
Exchange sounds good.   xchg which is the spelling used in most assemblers might be acceptable as an abbreviation for those who don't like to type ;) as it is already accepted and well known.

Another possibility is switch but, while it makes the swapping intent clear too, its intent is tainted by the use of switch in C and C++ which is not about swapping but branch selection.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

bytebites

  • Hero Member
  • *****
  • Posts: 799
Code: Pascal  [Select][+][-]
  1. generic function Exchange<T>(var target:T; const newvalue:T) :T;
  2. begin
  3.   Result := target;
  4.   target := newvalue;
  5. end;
  6.  
  7. generic procedure Swap<T>(var lhs,rhs: T);
  8. type
  9.   RawT = array[0 .. sizeof(T) - 1] of byte;
  10. var
  11.   tmp:RawT;
  12. begin
  13.   tmp:=RawT(lhs);
  14.   RawT(lhs):=RawT(rhs);
  15.   RawT(rhs):=tmp;
  16. end;  
  17.  
  18. Leashed Fpc (trunk) has these swappers, has anyone mentioned them?
  19.  
Harmless bot

Fibonacci

  • Hero Member
  • *****
  • Posts: 1103
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
Leashed Fpc (trunk) has these swappers, has anyone mentioned them?

That settles it. SysUtils.Swap<T> already does the optimal thing: a bitwise swap, which for managed types (strings, dynarrays, interfaces) moves the references with zero refcount churn - faster than a naive temp-variable swap.

In Unleashed it's already as short as it gets, no specialize thanks to implicit generics:

Code: Pascal  [Select][+][-]
  1. uses SysUtils;
  2. Swap<YourType>(a, b);

And it's not just short - it's optimal. Here's Swap<Integer>(a, b) at -O3:

Code: Text  [Select][+][-]
  1. movl   32(%rsp), %eax
  2. movl   36(%rsp), %edx
  3. movl   %edx, 32(%rsp)
  4. movl   %eax, 36(%rsp)

Four moves, fully inlined, no call, no stack temp. You can't beat that - swapping two memory locations is 2 loads + 2 stores, and there's no memory-to-memory xchg on x86 (xchg isn't faster than plain moves on modern CPUs anyway).

And here's Swap<String>(a, b) - the managed case:

Code: Text  [Select][+][-]
  1. movq   -8(%rbp), %rax
  2. movq   -16(%rbp), %rdx
  3. movq   %rdx, -8(%rbp)
  4. movq   %rax, -16(%rbp)

Just the two pointers swapped - no fpc_ansistr_incr_ref / decr_ref calls, no refcount churn at all. A naive tmp := a; a := b; b := tmp would emit those refcount calls; the bitwise swap skips them entirely. A compiler intrinsic would emit the exact same four instructions.



So: do we close this, or are there other arguments for a dedicated intrinsic? The only one I can see is dropping the SysUtils dependency - making swap available in any unit without a uses clause. That's a convenience argument, not a performance one. If that's worth it to people, say so; otherwise SysUtils.Swap<T> already covers this and there's nothing to add.
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub ⭐

creaothceann

  • Sr. Member
  • ****
  • Posts: 471
Is that <YourType> really needed?

Fibonacci

  • Hero Member
  • *****
  • Posts: 1103
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
Is that <YourType> really needed?

Not if you enable {$modeswitch implicitfunctionspecialization}.

But is the <YourType> really that much of a problem? An intrinsic wouldn't be any faster - at best equally fast, since the asm above is already optimal. And the obvious names are taken: System.Swap (the 1-arg one that swaps the halves of an ordinal) plus SysUtils.Swap<T> / Exchange<T>.

Technically a 2-arg Swap intrinsic could still coexist with the 1-arg System.Swap - different arity, no collision - so it's doable. The real question is whether it's worth adding at all, when it buys nothing over what's already there.
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub ⭐

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
So: do we close this, or are there other arguments for a dedicated intrinsic? The only one I can see is dropping the SysUtils dependency - making swap available in any unit without a uses clause.

Dropping SysUtils dependency would be quite important, since simply including this module does things that are often undesirable (such as initializing exceptions and changing the default error and assertion handlers), and it also bloats the executable file because it’s quite havy. This would be important to me as well, since the entire source code for my engine/game does not use any unit other than the basic System unit.

The introduction of the new intrinsic will provide a convenient and efficient method for swapping, independent from the syntax of generics and units used in the project. The original Free Pascal should have it too (but that's of course a suggestion for its development team).

Quote
Technically a 2-arg Swap intrinsic could still coexist with the 1-arg System.Swap - different arity, no collision - so it's doable.

In my opinion, the Swap(A, B) intrinsic would be a pretty good choice, since Exchange is already taken for generics.
« Last Edit: May 30, 2026, 03:35:43 am by flowCRANE »
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.

Fibonacci

  • Hero Member
  • *****
  • Posts: 1103
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
xchg() - how about this one? Exchange() / Swap() are taken - name collisions. Exchange() from SysUtils would shadow the intrinsic, and Swap() might have its own problems too.
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub ⭐

Thausand

  • Hero Member
  • *****
  • Posts: 618
xchg() - how about this one? Exchange() / Swap() are taken - name collisions. Exchange() from SysUtils would shadow the intrinsic, and Swap() might have its own problems too.
Then make introduce 3th option that do same and is name switch  :D
A docile goblin always follow HERMES.md

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
xchg() - how about this one? Exchange() / Swap() are taken - name collisions. Exchange() from SysUtils would shadow the intrinsic, and Swap() might have its own problems too.

That name is a bit too short; it sounds more like assembly or C than Pascal. In Pascal, it’s standard to use fully capitalized names.

I’d go with Swap with two arguments, and if not, then the intrinsic Exchange and if not, as a last resort, intrinsic SwapValues. I believe that intrinsic functions should have the highest priority when it comes to reserved names, followed by RTL functions, and finally user-defined functions.

In my opinion, intrinsic Swap with two arguments would be the best option, because intrinsic Swap already exists, so the new one would fit perfectly, and the difference in parameters would clearly indicate the purpose of the new function. Unless there are any technical reasons against it, there’s really no point in coming up with other names, especially abbreviated ones.
« Last Edit: May 30, 2026, 02:43:24 pm by flowCRANE »
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.

Fibonacci

  • Hero Member
  • *****
  • Posts: 1103
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
Choose.
  • SwapValues
  • SwapVal
  • SwapVar
  • ExchangeValues
  • ExchangeVal
  • ExchangeVar
  • ... any other that's used nowhere else
The feature is implemented - last thing left is to choose the name. And merge.
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub ⭐

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
Choosing a name is one of the hardest things in programming. 8)

If I were to ignore my previous post and choose from the list provided, I would go with SwapValues.
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.

Fibonacci

  • Hero Member
  • *****
  • Posts: 1103
  • Behold, I bring salvation - Unleashed Pascal
    • fibo.gg
SwapValues it is.



New intrinsic: SwapValues

On by default in {$mode unleashed}.

A compiler intrinsic that swaps the values of two variables. No unit, no uses clause, no generics - works everywhere, including code that only pulls in the System unit.

Code: Pascal  [Select][+][-]
  1. var
  2.   a: integer = 1;
  3.   b: integer = 2;
  4. begin
  5.   SwapValues(a, b); // a = 2, b = 1
  6. end.

Why, when SysUtils already has Swap<T>

SysUtils already ships a generic Swap<T>, and it compiles to optimal code. But pulling in SysUtils just to swap two variables drags in exception initialization, changed error/assertion handlers, and noticeable binary bloat. Code that deliberately uses only System - low-level, embedded, gamedev - shouldn't have to pay that for a swap. SwapValues is a builtin: zero units, zero dependencies.

Any type, zero overhead

It's a bitwise swap. For managed types (strings, dynamic arrays, interfaces) it swaps the references directly - no refcount churn, which is actually faster than a naive temp-variable swap.

Codegen is optimal. SwapValues(a, b) for integers at -O3:

Code: Text  [Select][+][-]
  1. movl   32(%rsp), %eax
  2. movl   36(%rsp), %edx
  3. movl   %edx, 32(%rsp)
  4. movl   %eax, 36(%rsp)

Four moves, no call, no temp. For strings, just the two pointers swapped - no incr_ref / decr_ref calls.

Rules
  • Both arguments must be assignable - variables, fields, array elements, dereferenced pointers. Literals, consts and function results are a compile error.
  • Both must be the same type.
  • Coexists with the existing System.Swap (1-arg ordinal-half swap) and SysUtils.Swap<T> - different name, no collision.



Dev note: I first named the intrinsic xchg on the branch, then renamed it in bulk - and the mass find-replace quietly broke something else in the compiler. Took me an half an hour to hunt down. Should be fine now, but if you hit anything odd, that rename is the prime suspect.
« Last Edit: May 30, 2026, 03:58:40 pm by Fibonacci »
Unleashed Pascal: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub ⭐

Thaddy

  • Hero Member
  • *****
  • Posts: 19950
  • Pointless static linking author
There are already intrinsics for swap in system. Just follow that syntax. And check for double code.
Any "programmer" that knows only one programming language is not a programmer

 

TinyPortal © 2005-2018