Recent

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

Fibonacci

  • Hero Member
  • *****
  • Posts: 1029
  • Behold, I bring salvation - FPC Unleashed
Anyway, as I said before: I am just writing that as an observer. I.e. if my thoughts do help, well fine. If they don't, it wont hurt me, I am not going to be affected.

Observer or not, the input's appreciated - you basically did the design review for me. The takeaway being that the "complete" version is a small effects system, which is good to know before sinking time into it.
FPC Unleashed: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12538
  • Debugger - SynEdit - and more
    • wiki
Anyway, as I said before: I am just writing that as an observer. I.e. if my thoughts do help, well fine. If they don't, it wont hurt me, I am not going to be affected.

Observer or not, the input's appreciated - you basically did the design review for me. The takeaway being that the "complete" version is a small effects system, which is good to know before sinking time into it.

Well, glad it helps.

It may not even be that complicated to do... (But I am not that familiar with the compiler code)

- The parsing of syntax -> probably not an issue
- Scoping, would have been needed anyway
- Then its mostly keeping count.
   Order does not matter. The free call can happen first (e.g. in an if, inside a loop).
- The probably hardest bit: ensuring modifiers on virtual methods.

So basically,
- upon encountering any declaration of demands/provides => set the target counts
- upon encountering any call, check if the function has demands/provides
- upon function end => check the calls
  (forwarding happens by the declaration, which has set up new count-ables for that function).




But upon writing this, there is one other that I hadn't looked at yet.

Code: Pascal  [Select][+][-]
  1. var a: array [0..1] of x;
  2. begin
  3.   a[0] := Alloc; // demands
  4.   a[1] := Alloc; // demands
  5.   for i := 0 to 1 do
  6.      release(a[i]); // provides
  7. end;

And the counts don't match up.

So actually, the question is, if there should be counts at all, or under what conditions?

Maybe it only needs a boolean demanded and provided. I.e.
- ONE call to provide fulfills ALL open requires (for the given token).
- ONE call to require allows any number of calls to provide

"forwards" would still exist. To forward a demand, the demand must be triggered in the function. But the function may call demand and provide for the token, and yet still also forward the demand (or provide) of it, since the number of calls can't be told.

So basically any "presence = true" holds for any number (of calls and forwards) for the duration of the entire function.


Thaddy

  • Hero Member
  • *****
  • Posts: 19433
  • Glad to be alive.
Only if the replacement is the same size or smaller than the original. Resources, on the other hand, you can swap for anything.
No: I use a patcher that can binary patch anything where the shared code is more than the patch based on lcs and crc32 checks: That is the code from Dream Components, which some of you may have, but is never released as public domain, so I can't share the code. Dream Company's sources were bought by an Australian company and seemingly did not survive. Maybe our Australian members can shed some light on that, because that code is brilliant, not only the patch code. I believe the original developers are from Ukraine. It also contained the best scripting engine, including all GUI components, I have ever seen for Object Pascal. (Basically Delphi written in Delphi, which Delphi isn't)

I, ahum.... :-[ , used that patch code to generate a diff between a full version and a demo version of some piece of commercial software... The diff could be applied to unlock the full version from the demo and the demo version was well written.(No unlock, missing code, not hidden code).
I also disagree about the necessity to encode/decode strings. It should be low-level byte-wise. Anything other looks silly.
You could also have used some kind of compression like UCL/UPX which has a less that 1000 bytes penalty on the binary. Base64? Come on...
« Last Edit: June 18, 2026, 11:36:33 am by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

440bx

  • Hero Member
  • *****
  • Posts: 6556
I was under the impression this was only meant for library imports - flagging external APIs like GetEnvironmentStrings - not your own functions/procedures/methods.
The construct can be used for library imports or regular functions/procedures, it's general in nature.   I used GetEnvironmentStrings  and FreeEnvironmentStrings because they are obviously a good example of why the facility is very useful.

As far as who would make the annotation, ideally the API declarations would be improved as time goes by or, a programmer could write his/her own definitions (which is what I did.)  Either way works fine. 

As far as syntax goes, it really is as simple as it gets, I'd simply add it to the end of the current declaration as optional.  Basically, add the "requires_clause" as an optional clause after the name/index clause.   As shown in a previous post, the clause is:

requires_clause       ::= "requires" subprogram_identifier "reason" string_const_expression ";"

Again, the goal is for the compiler to let the programmer know that using some feature has hidden side effects he/she should be aware of to ensure the code is correct.  Whether the call to FreeEnvironmentStrings is in the same scope as GetEnvironmentStrings is a detail the programmer can manage as he/she sees fit.    The only thing that matters is that FreeEnvironmentStrings should be called someplace with the value returned by GetEnvironmentStrings.  That's it.  No more, no less. Simple is best.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 1029
  • Behold, I bring salvation - FPC Unleashed
I also disagree about the necessity to encode/decode strings. It should be low-level byte-wise. Anything other looks silly.
You could also have used some kind of compression like UCL/UPX which has a less that 1000 bytes penalty on the binary. Base64? Come on...

I think we're talking past each other. {$incfile} embeds the file 1:1, byte-for-byte - no encoding, no compression, nothing. The Base64 I mentioned was just an example of a string-taking API (to show why the string form is convenient), not something incfile does. You've brought up encoding a few times now and I honestly don't follow what you mean.



@440bx

Ok, the guessing game.

Code: Pascal  [Select][+][-]
  1. function GetEnvironmentStrings: PChar; stdcall; external 'kernel32.dll' name 'GetEnvironmentStringsA' requires 'FreeEnvironmentStrings' reason 'should be freed by calling FreeEnvironmentStrings';

The question remains - who will fix the Windows (or any other) unit and add these?
FPC Unleashed: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12538
  • Debugger - SynEdit - and more
    • wiki
Whether the call to FreeEnvironmentStrings is in the same scope as GetEnvironmentStrings is a detail the programmer can manage as he/she sees fit.      The only thing that matters is that FreeEnvironmentStrings should be called someplace with the value returned by GetEnvironmentStrings.  That's it.  No more, no less. Simple is best.

Just look at "complaints" we have on existing hints/warnings when they are false positives.  If those hints appear, people will have their opinions (and they should have each as much value as yours ;) ).

But, well if I read that correct "should be called someplace" => ok, so doesn't have to be the same procedure. But, might not be the same unit.
So then the compiler needs to check all used units (recursively) for that call. Of course, that may then include some units in the RTL that have their own pairing of those functions, and that would falsely "its called" suppress it.

So your "simple" does not really work. Or you have to define some arbitrary limits to "should be called someplace". (yes, you may call "unit bounds" to be "not arbitrary", but looking at how much else goes across them.... I do call them arbitrary)

But anyway, just adding my notes....



Quote
with the value returned by GetEnvironmentStrings

And that is all, but definitely not simple. How shall that value be tracked? It may have been re-assigned to a diff variable...

Also,
- there may be push/pop like calls, that don't have an argument.
- there may be functions that call free on a different (derived) value.
- functions may return/take several values, how to identify the one to track?

So unless you want that feature for a "selected to your personal taste" set of functions, I don't see how checking the value should work.

440bx

  • Hero Member
  • *****
  • Posts: 6556
Code: Pascal  [Select][+][-]
  1. function GetEnvironmentStrings: PChar; stdcall; external 'kernel32.dll' name 'GetEnvironmentStringsA' requires 'FreeEnvironmentStrings' reason 'should be freed by calling FreeEnvironmentStrings';
That's exactly right.  append it to the optional "name"/"index", last thing in the definition.  No guessing.

The question remains - who will fix the Windows (or any other) unit and add these?
As I mentioned in the previous post, as time goes by, hopefully the people who maintain the definitions would add that information, just as they do with, for instance, "deprecated".

I would use the feature because, I have my own definitions and it is definitely an annotation I'd like to have because there are a good number of APIs in Windows that require further actions that, if omitted, cause problems such as memory leaks or resource leaks or some other problem and, currently, the only way to be sure is to go to the doc page, re-read it and make sure there isn't some wrinkle that has to be taken care of.  It would be so much simpler, if the API could be annotated instead of having to do a "just in case check" again and again.

Honestly, I would also use it for my own functions/procedures.  It is not unusual to have processes (not in the O/S sense) that require two functions/procedures, usually one for setup and the other for takedown, it's nice to annotate the "setup" function with whatever it requires for everything to work properly and, for regular functions, I would follow the same structure, append the "requires_clause" to whatever the function/procedure definition allows today.  No guessing, just append.

The concept is very simple and, it should not be made complicated.  It's just a convenient reminder for the programmer, no more, no less.  It's not meant to do any CFG, just emit a note that when using the function something else is needed and why.  That's it.

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

Fibonacci

  • Hero Member
  • *****
  • Posts: 1029
  • Behold, I bring salvation - FPC Unleashed
Several changes landed today:
  • {$incfile} removed -> replaced by {$embedstr} + {$embedbytes}
  • new lock / trylock statements
  • thread-static locals (threadstatic / tstatic)
  • auto-properties (readonly / writeonly) - beta
  • new zeroinit routine modifier
  • IDE: Simple Program startup + Project Overrides page


{$incfile} replaced by {$embedstr} and {$embedbytes}

Breaking: the {$incfile} directive is gone, replaced by two focused directives split by output type:
  • {$embedstr} - the file becomes a String
  • {$embedbytes} - the file becomes an array of byte
Migration is a rename: {$incfile name 'f'} -> {$embedstr name 'f'}.

Docs: docs/embed.md

Both read the file as raw bytes at compile time and bake it into the binary at the directive site - no runtime file I/O, no external dependency. They are directives, not modeswitches, so they work in every {$mode}. Each has a 2-arg form (NAME 'path' -> a named const) and a 1-arg form ('path' -> a bare value expression, usable inline as an argument or RHS).

Code: Pascal  [Select][+][-]
  1. program embed_demo;
  2.  
  3. {$mode unleashed}
  4.  
  5. {$embedstr   greeting 'banner.txt'}   // -> const greeting: String
  6. {$embedbytes bytes    'banner.txt'}   // -> const bytes: array[N] of byte
  7.  
  8. begin
  9.   WriteLn(Length(greeting), ' bytes');   // 18 bytes  (= exact file size)
  10.   WriteLn(greeting);                     // Hello from embed!
  11.   WriteLn('first byte = ', bytes[0]);    // first byte = 72 ('H')
  12. end.

greeting holds the file's bytes byte-for-byte (Length = file size); bytes is the same data typed as array of byte for binary APIs (hashing, decoders, protocols). Use $embedstr for text/buffers, $embedbytes when an API demands array of byte.

Notes
  • 2-arg forms expand to a const (must sit where a const is valid); 1-arg forms expand to a bare value - {$embedstr 'f'} is a String expression, {$embedbytes 'f'} a [$xx,..] array literal you can pass straight to a function or compare inline.
  • Gotcha: var x := {$embedbytes 'f'}; infers array of LongInt (4-byte elements), not array of byte. Pin it with the 2-arg form, an explicit : array of byte, or a typed parameter.
  • Path resolution matches {$I}: absolute verbatim, relative searched in the source dir then the include paths.


New statements: lock / trylock

On by default in {$mode unleashed}. Outside: {$modeswitch lock}.

Docs: docs/lock.md

Two statements that serialize access across threads on top of FPC's TRTLCriticalSection, built so the acquire/release pairing can never be forgotten - the body is wrapped in a try..finally, so the lock is released on normal exit, exit/break/continue/goto, or a propagating exception. The verb split is the whole point: lock blocks until acquired and cannot fail (no else); trylock may miss, so its else branch is mandatory, with an optional wait N (milliseconds) bounded retry.

Code: Pascal  [Select][+][-]
  1. program lock_demo;
  2.  
  3. {$mode unleashed}
  4.  
  5. var counter: Integer;
  6.  
  7. procedure Bump;  begin lock(counter) do Inc(counter); end;   // per-variable lock
  8. procedure Bump2; begin lock(counter) do Dec(counter); end;   // same hidden CS as Bump
  9.  
  10. begin
  11.   counter := 0;
  12.   Bump; Bump; Bump; Bump2;
  13.   WriteLn('counter = ', counter);                 // counter = 2
  14.  
  15.   lock do WriteLn('one thread at a time here');   // per-callsite lock
  16.  
  17.   trylock(counter) do Inc(counter) else WriteLn('busy');
  18.   WriteLn('counter = ', counter);                 // counter = 3
  19.  
  20.   trylock wait 50 do WriteLn('acquired') else WriteLn('timed out');  // bounded wait
  21. end.

How the lock is chosen
  • lock(v) - every lock(v)/trylock(v) across the whole program shares one hidden critical section for v (protects the data).
  • lock do - a hidden CS per source position (protects that sequence of instructions).
  • lock(a, b, ...) - multi-lock; targets are entered in a fixed name-sorted order at every site, so the AB-vs-BA deadlock is impossible.
  • If a target is itself a TRTLCriticalSection it's used directly (your lifetime to manage) - the escape hatch. Auto targets must be a global var or a class var. All locks are recursive (re-entrant).


New feature: thread-static locals (threadstatic / tstatic)

On by default in {$mode unleashed}. Outside: {$modeswitch threadstatic}.

Docs: docs/thread-static.md

A block-local variable with program lifetime, like static - but per thread: each thread gets its own copy, initialized once per thread on first reach. Inline form (threadstatic name := expr; anywhere in a body) and declaration-section form (before the body, like var/static). tstatic is a short, drop-in alias for threadstatic.

Code: Pascal  [Select][+][-]
  1. program tstatic_demo;
  2.  
  3. {$mode unleashed}
  4.  
  5. function NextId: Integer;
  6. begin
  7.   threadstatic next := 1000;    // per-thread; each thread starts fresh at 1000
  8.   Result := next;
  9.   Inc(next);
  10. end;
  11.  
  12. function NextTick: Integer;
  13. tstatic                         // short alias, declaration-section form
  14.   n: Integer = 0;
  15. begin
  16.   Inc(n);
  17.   Result := n;
  18. end;
  19.  
  20. begin
  21.   WriteLn(NextId, ' ', NextId, ' ', NextId);        // 1000 1001 1002
  22.   WriteLn(NextTick, ' ', NextTick, ' ', NextTick);  // 1 2 3
  23. end.

In one thread these behave like a call-persistent counter; spawn more threads and each keeps its own independent next / n.

Notes
  • Cost: on win32/win64 the threadvar relocate is inlined to a TEB read; with the address hoisted out of a loop (-O2) an access costs the same as an ordinary global. (The old call-per-access path was several times slower.)
  • vs static: a static const-init folds into the data segment; thread-static can't (TLS has no per-thread template), so a non-zero initializer becomes a per-thread guarded assignment - one branch on first use per thread, free after. An all-zero init (0/nil/'') skips even that.
  • static = one instance shared by all threads; threadstatic = one per thread. Scope is the declaring routine only.


New feature: auto-properties - readonly / writeonly  (beta)

On by default in {$mode unleashed}. Outside: {$modeswitch autoproperties}.

Docs: docs/auto-properties.md

A property declared with a type but no read/write clause makes the compiler synthesize a hidden strict private backing field and bind the property straight to it (read FName write FName). No getter/setter method - identical codegen to a hand-written field-backed property, zero overhead. readonly/writeonly narrow the access; = const seeds the field with a default.

Code: Pascal  [Select][+][-]
  1. program autoprop_demo;
  2.  
  3. {$mode unleashed}
  4.  
  5. type
  6.   TPerson = class
  7.     property Name: String;            // -> strict private FName; read/write
  8.     property Id:   Integer; readonly; // -> read FId only
  9.     property Tag:  String = 'x';      // initializer seeds the backing field
  10.     constructor Create(aId: Integer);
  11.   end;
  12.  
  13. constructor TPerson.Create(aId: Integer);
  14. begin
  15.   FId := aId;                         // backing field reachable inside methods
  16. end;
  17.  
  18. var p: TPerson;
  19.  
  20. begin
  21.   p := TPerson.Create(42);
  22.   p.Name := 'Bob';
  23.   WriteLn(p.Name, ' ', p.Id, ' ', p.Tag);   // Bob 42 x
  24.   p.Free;
  25. end.

Notes
  • Backing field = F + property name; change the prefix with {$autopropprefix PREFIX} or --autopropprefix=PREFIX
  • Works on classes, objects and records; a class property -> a shared class var; a published auto-property is RTTI-complete.
  • Indexed properties still need explicit accessors; initializers apply only to instance properties of classes/objects.
  • Beta - the newest feature in this batch and still stabilizing. Try it, report anything odd.


New modifier: zeroinit - zero-fill all locals on entry

Available in {$mode unleashed}. No separate modeswitch.

Docs: docs/zeroinit.md

procedure foo; zeroinit; zero-initialises every local variable at function entry - the compiler inserts an implicit Default(T) for each local, in declaration order, before any statement runs. FPC already clears managed locals (strings, dynamic arrays, interfaces); zeroinit extends that to the simple ones (Integer, plain records, static arrays) that otherwise carry whatever the stack left behind. Add a new local and it's zeroed too - no explicit list to maintain.

Code: Pascal  [Select][+][-]
  1. function Sum: Integer; zeroinit;
  2. var
  3.   a, b, c: Integer;      // all zeroed on entry - no explicit := 0 needed
  4. begin
  5.   Inc(a, 10);
  6.   Inc(b, 20);
  7.   Inc(c, 30);
  8.   Result := a + b + c;   // 60  (a, b, c started at 0)
  9. end;

Handy for defensive code, FFI shims, code-gen output and quick prototypes, where a deterministic frame beats hunting "why is this 0 sometimes and garbage other times".

Notes
  • Applies to standalone routines, methods, constructors and destructors. Mutually exclusive with external / interrupt / assembler - those have no Pascal body to inject into.
  • Result is treated as a local and zeroed too, so an unmanaged return type is deterministic before you assign it.
  • Gotcha: a local declared with an inline anonymous compound type (var a: array[0..3] of Integer) is silently skipped - name the type (type TArr4 = array[0..3] of Integer) to have it zeroed.
  • Pure sugar: it injects ordinary zero-assignments - no calling-convention or stack-frame change, no runtime helper.


IDE: Simple Program startup + Project "Overrides" page

Two IDE additions (screenshots below).

IDE Options -> Environment -> IDE Startup -> Simple Program. Three new settings so a new Simple Program starts with your own names instead of the fixed defaults: Default app name sets the program name and Main proc name the main routine, so File -> New -> Simple Program builds the skeleton the way you like it. An Include {$mode unleashed} checkbox is there too, if you want it.

Project Options -> Overrides. A new page that surfaces the Unleashed binary-metadata and hardening switches in the GUI instead of hand-typing custom options:
  • FPC signature (--fpcsignature) - override it, or with Set empty drop the .fpc.version ident string entirely.
  • Override linker version / Override OS version (--linkerversion / --osversion) - PE-header fields, Windows targets only.
  • Strip RTTI (--striprtti) + a whitelist (--rttiexpose, wildcards allowed) - nulls type-name strings in RTTI/VMT.
  • Auto-properties prefix (--autopropprefix, default F) - the backing-field prefix for the feature above.
Docs: docs/binary-metadata.md, docs/strip-rtti.md
« Last Edit: July 01, 2026, 03:33:45 pm by Fibonacci »
FPC Unleashed: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

Thaddy

  • Hero Member
  • *****
  • Posts: 19433
  • Glad to be alive.
Thanks for implementing ThreadStatic!  :D
(although I think my anonymous record approach is faster and better)
I will build and review.
« Last Edit: July 01, 2026, 04:01:56 pm by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

creaothceann

  • Sr. Member
  • ****
  • Posts: 389
Gotcha: var x := {$embedbytes 'f'}; infers array of LongInt (4-byte elements), not array of byte. Pin it with the 2-arg form, an explicit : array of byte, or a typed parameter.
Gotcha: a local declared with an inline anonymous compound type (var a: array[0..3] of Integer) is silently skipped - name the type (type TArr4 = array[0..3] of Integer) to have it zeroed.

Couldn't these automatically create a hidden type?

Fibonacci

  • Hero Member
  • *****
  • Posts: 1029
  • Behold, I bring salvation - FPC Unleashed
@creaothceann:

Good question, and the two cases actually have different answers, they only look alike.

For {$embedbytes} the named form already does exactly that: {$embedbytes name 'f'} expands to const name: array[N] of byte = (...), a real typed constant. The 1-arg form can't, because it expands at an expression position (you drop it straight into an argument list or the right side of :=), and there is nowhere to hang a const declaration there. So it has to emit a bare [$aa,$bb,...] array-constructor literal, and a constructor of integer constants takes the default integer element type, not byte. It is less "anonymous vs named type" and more "expression vs declaration". The moment there is a target type (a typed array of byte parameter, an explicit annotation, or the 2-arg const) the element type is byte again, so it only bites bare := inference where nothing says byte.

For zeroinit you are right, and that one is worth doing. It is skipped today only because the zero-fill goes through the Default(T) intrinsic, which derives the name of its hidden zero-constant from the type's symbol name. An inline anonymous array[4] of Integer has no type symbol, so Default() has nothing to mangle and currently falls over, and zeroinit skips rather than crash. Synthesizing a throwaway type symbol for the anonymous def (or teaching Default() to handle a symbol-less type) would let it be zeroed like everything else.

Naming the type is the workaround for now, but you are right that the zeroinit half shouldn't need it. It will get there, Rome wasn't built in a day.
« Last Edit: July 02, 2026, 12:01:40 am by Fibonacci »
FPC Unleashed: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

440bx

  • Hero Member
  • *****
  • Posts: 6556
About ZeroInit, normally, that's implemented by zero-ing out the number of bytes the compiler already knows it needs to reserve for the locals.  That feature doesn't need to know anything about data types, all the compiler needs to know is how many bytes need to be allocated on the stack and, it always knows that (it couldn't work otherwise.)

In the case of FPC, one case that would be desirable to handle is when some variables are already explicitly zero-inited in a function/procedure that is subsequently marked as "ZeroInit".  Specifically, if there is an array of char that is initialized to #0, FPC currently generates code to zero out the entire array, if the function is ZeroInited, that step is redundant since the ZeroInit will take care of it.  The same applies to most other locals that are initialized to zero.  OTH, that's an optimization, it doesn't functionally hurt anything to initialize more than once, it just wastes some clock cycles.

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

schuler

  • Sr. Member
  • ****
  • Posts: 371
:) Just to comment that I pressed in the "fork" button of the repo... :)

creaothceann

  • Sr. Member
  • ****
  • Posts: 389
For {$embedbytes} the named form already does exactly that: {$embedbytes name 'f'} expands to const name: array[N] of byte = (...), a real typed constant.

The 1-arg form can't, because it expands at an expression position (you drop it straight into an argument list or the right side of :=), and there is nowhere to hang a const declaration there.

Couldn't the compiler go back to the global / subroutine declarations section and add a type there, or is that not possible due to Pascal's one-pass code generation? Just curious; I'll probably always use the named form to avoid that issue.

If I were to implement the short form I'd let it emit a (suppressable) hint/warning - but maybe most people wouldn't really care?

Fibonacci

  • Hero Member
  • *****
  • Posts: 1029
  • Behold, I bring salvation - FPC Unleashed
Easy fix.

Code: Pascal  [Select][+][-]
  1. function getdefaultvarsym(def:tdef):tnode;
  2. begin
  3.   // ...
  4.   defaultname:='$_'+make_mangledname('zero',def.owner,def.typesym.Name);

->

Code: Pascal  [Select][+][-]
  1. function getdefaultvarsym(def:tdef):tnode;
  2. begin
  3.   // ...
  4.   if assigned(def.typesym) then
  5.     defaultname:='$_'+make_mangledname('zero',def.owner,def.typesym.Name)
  6.   else
  7.     defaultname:='$_'+make_mangledname('zero',findunitsymtable(def.owner),'def'+tstoreddef(def).unique_id_str);

So from now on Default() just generates the name itself when the type doesn't have one. Simple.

Keeping this in sync with upstream is going to be complicated though: This branch is 384 commits ahead of and 371 commits behind fpc/FPCSource:main :)
FPC Unleashed: async/await, parallel for, match, tuples, string interpolation, inline vars, autofree, no-RTTI & tons more. Star on GitHub

 

TinyPortal © 2005-2018