Hmm, I don’t agree. If you pass a structure by value, that is supposed to make a copy. The callee is free to modify the copy any way it likes (unless you use const, and that’s not much protection in C). Doing as the author suggests, and passing in an immutable parameter, would introduce copy-on-write semantics.
If the concern is the overhead of copying large structures around, the obvious solution is to not do that and simply pass structure pointers (like most APIs do). This also gives the API implementer (i.e. callee) full control over whether copies get made or not.
In fact, I think I’d argue that if you’re passing large structures by value, Your API Is Probably Wrong and this is absolutely not the ABI’s problem in the first place.
But there is a larger assumption that underlies your post that I think is important to correct.
You seem to be caught up in the idea that the code you write, and the code the compiler generates must have some kind of one-to-one corrospondance, but this isn't the case at all.
The code the compiler generates must have the same final result, but it doesn't have to actually work the same way.
So if you pass a structure by value, and the compiler can avoid a copy somehow because it's more efficient, then of course it should do so.
Making an ABI that is ammenible to allowing the compiler to make better optimisations is of course also a good idea. And the proposed ABI here achieves that.
> Doing as the author suggests, and passing in an immutable parameter, would introduce copy-on-write semantics.
Yes, and what's wrong with that, if it's transparently done by the compiler?
In this scheme, the caller would pass non-register-sized data "by value" by actually passing a pointer to it. (Importantly, this pointer is register-sized, and so wouldn't necessarily need to be spilled to the stack—unlike caller memory copy, which is always to the stack.)
A callee that can be statically determined to never modify the value, would, under this calling convention, be compiled to code that simply works with the data "through" the pointer that's been placed in its register / on the stack.
A callee that can't be statically determined to never modify the value, would, under this calling convention, generate a memcpy from the pointer onto the stack; where the pointer then goes dead at that point (and so, if the pointer was spilled to the stack by the caller, then the memcpy could be targeted so as to overwrite the pointer on the stack.)
This would be much more efficient under most conditions. Its only inefficiency would come from the situation where the pointer being passed would necessarily be spilled to the stack; and then the callee would be statically guaranteed to make a copy. In that case, you're doing an extra push (for the pointer) that current ABIs avoid by just having the caller do an eager copy.
But this would be quite rare in practice, since this ABI (and the ABIs it replaces) are only for external symbols — the kind whose linkage is fundamentally dynamic (i.e. where the linker-loader could in theory substitute anything it likes for the external symbol with an LD_PRELOAD shim.)
Internal symbols — private static functions — get bespoke compiler-specific ABIs that skip all this enforced caller/callee predetermined role business and just codegen whatever's most efficient on a case-by-case bases, with different callsites getting different monomorphizations or partial inlinings of the callee that distribute the responsibilities differently.
And since these ABIs only matter for external linkage, you have to remember that symbols with external linkage get no "type attribute enforcement" at link-load time, and so your symbol for a function that was originally guaranteed to be a callee-that-always-writes, might be substituted at link-load time for a callee-that-never-writes. In which case, having the pointer on the callee's stack once again becomes handy, rather than useless.
> In fact, I think I’d argue that if you’re passing large structures by value
Not necessarily large structures. More often things like UUIDs—values just large-enough to not fit into a (64-bit) machine register. It's these small memory spills, done in hot loops, that add up. There are huge wins for the cleanliness of e.g. RDBMS engine code, if their various 128-bit to 512-bit column types can be passed by value without necessitating an eager copy.
My reading of the C++ standard is that this behavior is effectively mandated and that one can write a program which can tell if an ABI observed the proposed optimization.
[expr.call]: "The lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the argument expression."
[conv.lval]: "... if T has a class type, the conversion copy-initializes the result object from the glvalue."
The way a program can tell if a compiler is compliant to the standard is like so:
struct S { int large[100]; };
int compliant(struct S a, const struct S *b);
int escape(const void *x);
int bad() {
struct S s;
escape(&s);
return compliant(s, &s);
}
int compliant(struct S a, const struct S *b) {
int r = &a != b;
escape(&a);
escape(b);
return r;
}
There are three calls to 'escape'. A programmer may assume that the first and third call to escape observes a different object than the second call to escape and they may assume that 'compliant' returns '1'.
The compiler would be forced to create copies in that case. In general (using my proposed ABI), taking the address of an object will cause this, because it is possible to mutate an object through its address.
It's still a win because 1) you can avoid making copies in many places, and 2) code size decreases because the copy happens one time in the callee rather than many times for every caller.
One of the things I'm really excited about for Rust is restrict semantics are baked right there into the language with &mut/&. I think there were still a few LLVM bugs to flush out(because very little C/C++ code uses restrict by nature of how it subtly explodes when you get it wrong).
In theory when they sort that out Rust should be able to turn on restrict where it applies globally "for free".
Propagating "noalias" metadata for LLVM has actually finally been enabled again recently in nightly [0].
However it has already caused some regressions so it is not clear whether we may go through another revert/fix in llvm/reenable cycle [1]. This has happened several times already sadly [2] as, exactly as you say, basically nobody else has forged through these paths in LLVM before.
The other neat thing about Rust is that if you turn on LTO and all of the involved code is Rust, there is no hard ABI. The linker will do all sorts of interesting things with register usage and how to pass data to functions.
Every major ABI is listed here as containing the same mistakes. I'm inclined to think the people who designed these ABIs were smart enough to understand the consequences of their design decisions.
I don't know whether this author is correct or not, but my gut is there is something missing here with respect to non local control flow (like exception handling, setjmp/longjmp, and fibers).
I love seeing others bring up Chesterton's fence; it's been a reference that comes to mind with quite a lot of the WTFery I've encountered in my career (usually it remains WTFery even when looking for underlying reasons, but it at least helps remind me to question my instincts).
I don't really know enough to weigh in on this, but I can say that having pursued a lot of WTFish things in my career so far, 90% of the times I've encountered bad decisions, the explanation for it was either "it was done that way because legacy reasons" (i.e., it had to be done that way then, the reason it had to be has changed, and now it would break things to do it 'correctly') or "it was easier" (i.e., at the time the badness wasn't really going to affect anyone, or not measurably, or was very intentional tech debt, and it's only 'now' that anyone is noticing/caring).
It didn't matter before, as compilers were not optimizing as much, code had a much closer 1:1 correspondence to assembly (if you are passing it by pointer and not register, you would want to make that clear in code).
It's much easier to implement in simple compilers. On the side of the callee you don't have to check if you manipulate your arguments, which is generally hard. Being able to manipulate your arguments is another shortcut for keeping the compiler simple.
On the side of the caller you don't have to check if you hand out a mutable pointer.
Also finally and most importantly: memory access was much cheaper in terms of cpu cycles. Just look at cdecl: all parameters are passed on the stack instead of registers.
Our current calling conventions stem from performance hacks like fastcall that were only optimizing for existing code (you pass big structs by pointer by convention).
> my gut is there is something missing here with respect to non local control flow (like exception handling, setjmp/longjmp, and fibers)
(Post author.)
Mechanically, what happens is essentially the same as what ms/arm/riscv do: the caller creates a reference and passes it to the callee. The only difference is that the callee is more restricted than it would otherwise have been in what it can do with the memory pointed to by that reference. So I don't think that there can possibly be any implications for non-local control flow.
Well for one, the language says a copy is made at the time of the function call, and it's perfectly valid to modify the original before the copy is finished being used. So pretty much any potentially aliasing write or function call in the callee would force a copy, and as he notes C's aliasing rules are lax enough that that's most of them.
Then if you care about the possibility of signal handlers modifying the original... you pretty much have to make a copy every time anyway.
Exactly, see my example elsethread. Also in C and derivatives distinct objects are guaranteed to have distinct addresses. Implicit sharing would break this.
> A correctly-specified ABI should pass large structures by immutable reference
is just not possible. CPUs don't know about `const`. So you have to work with the assumption that functions that you call can do anything to their arguments. Thus copies cannot be avoided.
For a copy on write const ref ABI trick to work, the referenced area has to be stable while the callee is running. Good luck with guarantying that in C or C++ (and no, it would not necessarily be a race for the refed object to be concurrently unstable, the called function could have internal synchro...)
So I think this idea is virtually impossible. Your ABI is probably not wrong, but your blog post probably is :P
"A correctly-specified ABI should pass large structures by immutable reference"
There is no such thing at the level that the ABI works, especially at a kernel-userland boundary (where the choice is to fully marshal the arguments or accept that you have a TOCTOU issue).
An ABI is a contract. If it wants to say that a pointer is an immutable reference, all it needs to do is to say "this pointer is an immutable reference", and specify what that means. It is up to those who follow that contract to follow those rules.
You ABI could say a pointer may only be accessed on Wednesdays if it really wanted to.
To each their own. To me it's one of the few articles I didn't have to use the Reader on. It's lightweight, has good contrast and no ads. Very easy to read.
Why would that optimization not be possible with a simple const *?
The problem I see with that proposal is that it introduces a new type of pointer, the immutable pointer. That seems like equal to a const pointer, but it's not. With a const pointer the callee can not mutate the pointee. But it can still be mutated from the outside. That means that any time you handed out a mutable pointer to something you have to make a copy for handing out an immutable pointer to it. An ABI like this would probably be much more complex to implement for a small gain.
You would end up with hardly predictable behaviour wether the struct gets copied or not. C# structs suffer a lot from this, because methods are mutating by default (https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-t...). The biggest problem is simply that this is not explicit.
Also there is a case where a calling convention like this can make things worse, as you will have to make two copies:
void fn1(A*);
void fn2(A a) {
// Have to make copy here because mutating
fn1(&a);
}
void fn3()
{
A a;
fn1(&a);
// Have to make copy here because reference to a might have escaped.
fn2(a);
}
> With a const pointer the callee can not mutate the pointee. But it can still be mutated from the outside
That's incorrect. In c semantics, at least, it is legal to take a pointer to non-const, cast it to a pointer to const, pass it back, and mutate the object pointed to. It is only illegal to mutate if the pointer actually points to const memory.
Which means that, in a general sense, if you're given a const pointer, since you can't know if it actually points at const memory, you shouldn't mutate through it. But if you're handing out const pointers to non-const memory, you shouldn't count on the memory not being changed through those pointers.
IOW const is all but useless in c (except as a declaration of intent).
> An ABI like this would probably be much more complex to implement for a small gain
Why? Mechanically, it's almost the same as the ms/arm/riscv abi, except that a copy is made by the callee rather than the caller.
> Unfortunately, that doesn’t work anymore; compilers are smart now, and they don’t like it when objects alias.
Let's be specific: compilers for languages that support aliasing. For example, FORTRAN does not permit aliasing and therefore has all sorts of optimizations that languages that do permit aliasing cannot have.
It's a tradeoff like any other, and isn't specific to a compiler beyond the fact that a given compiler X can compile language Y.
"Well, what’s wrong with that? Surely we can just do what we did in the days of dumb compilers and pass structures by pointer. Unfortunately, that doesn’t work anymore; compilers are smart now, and they don’t like it when objects alias. "
What does that even mean? Does gcc cry after I make it translate code with pointers, I simply don't get it.
"Doesn't work anymore" is a hyperbole. Aliasing can prevent optimizations, like in the code example immediately after that paragraph where the value of `x` needs to be loaded from memory to be returned.
Out of curiosity: wouldn't doing this make semantics between C and C++ a lot nastier since in C++ passing a copy of an object by value implicitly calls the copy constructor from the caller function?
Still, definitely an interesting optimization. I can definitely see a place to use this optimization myself in the near future (custom new ABI) either way.
You can't pass a C++ object with a user-defined copy constructor or a non-trivial default to a C function. If you want to pass an object from C++ to C it has to be POD (plain old data) i.e. effectively a C struct.
That per se wouldn't be a new issue; for example in the Itanium ABI non-trivially copyable objects already use a different calling convention than PODs (specifically they can't be passed via registers and need need to be passed via hidden pointer).
I think clang's noescape attribute for pointer function parameters are somewhat related. There are several compiler extensions that allow refining calling conventions, so clearly even compiler vendors think that there are things to explore here.
Having said that I think the suggestion would be observably break both the current C and C++ standards, so something like this shouldn't be done without an explicit attribute.
So, consider passing a struct A stored in the global variable “foo” to the function “bar” in the parameter “baz”. This function makes a modification to the global “foo”. The compiler cannot know this happens and so the value of “baz” is now affected by this modification even though it should have been a copy. Zig experimented with just this and got problems. So as a solution it seems broken.
The compiler knows that there is a mutable pointer to x floating around as soon as you did `P=&x`. Therefore it knows it needs to make a new copy of x and then pass an immutable pointer to that value when calling `bar`.
This is something the compiler already keeps track of anyway in order to be able to know if it needs to re-load variables from memory when they are used again after a function call.
Basically the optimsiation degrades to exactly what happened before in the case where you do this.
Well, no, that's just one of the situations where the compiler would have to make a copy in the callee; a write to a pointer which might alias the argument.
Could theoretically be for Rust, but it sounds like Rust doesn't have the aliasing optimizations working yet. Based off [1], it looks like Swift's ABI might be able to take advantage of these things. (I don't know much about Swift)
Based off [2], it looks like Zig definitely can do these sorts of optimizations. Ada also has aliasing constraints, (you need to specify to the compiler that you want a thing to be aliasable) but I don't know if any compilers use them for optimizations. I know that Fortran can do these sorts of optimizations. Both Ada nor Fortran seem to be undergoing a PR renaissance, so they might be "trendy new languages" for that purpose.
However, given that the other two articles on the author's website [3] are about Delphi and complaining about AT&T assembly syntax, I'd suspect that the author is just a system's programmer annoyed about ABIs.
[+] [-] nneonneo|4 years ago|reply
If the concern is the overhead of copying large structures around, the obvious solution is to not do that and simply pass structure pointers (like most APIs do). This also gives the API implementer (i.e. callee) full control over whether copies get made or not.
In fact, I think I’d argue that if you’re passing large structures by value, Your API Is Probably Wrong and this is absolutely not the ABI’s problem in the first place.
[+] [-] Negitivefrags|4 years ago|reply
But there is a larger assumption that underlies your post that I think is important to correct.
You seem to be caught up in the idea that the code you write, and the code the compiler generates must have some kind of one-to-one corrospondance, but this isn't the case at all.
The code the compiler generates must have the same final result, but it doesn't have to actually work the same way.
So if you pass a structure by value, and the compiler can avoid a copy somehow because it's more efficient, then of course it should do so.
Making an ABI that is ammenible to allowing the compiler to make better optimisations is of course also a good idea. And the proposed ABI here achieves that.
[+] [-] derefr|4 years ago|reply
Yes, and what's wrong with that, if it's transparently done by the compiler?
In this scheme, the caller would pass non-register-sized data "by value" by actually passing a pointer to it. (Importantly, this pointer is register-sized, and so wouldn't necessarily need to be spilled to the stack—unlike caller memory copy, which is always to the stack.)
A callee that can be statically determined to never modify the value, would, under this calling convention, be compiled to code that simply works with the data "through" the pointer that's been placed in its register / on the stack.
A callee that can't be statically determined to never modify the value, would, under this calling convention, generate a memcpy from the pointer onto the stack; where the pointer then goes dead at that point (and so, if the pointer was spilled to the stack by the caller, then the memcpy could be targeted so as to overwrite the pointer on the stack.)
This would be much more efficient under most conditions. Its only inefficiency would come from the situation where the pointer being passed would necessarily be spilled to the stack; and then the callee would be statically guaranteed to make a copy. In that case, you're doing an extra push (for the pointer) that current ABIs avoid by just having the caller do an eager copy.
But this would be quite rare in practice, since this ABI (and the ABIs it replaces) are only for external symbols — the kind whose linkage is fundamentally dynamic (i.e. where the linker-loader could in theory substitute anything it likes for the external symbol with an LD_PRELOAD shim.)
Internal symbols — private static functions — get bespoke compiler-specific ABIs that skip all this enforced caller/callee predetermined role business and just codegen whatever's most efficient on a case-by-case bases, with different callsites getting different monomorphizations or partial inlinings of the callee that distribute the responsibilities differently.
And since these ABIs only matter for external linkage, you have to remember that symbols with external linkage get no "type attribute enforcement" at link-load time, and so your symbol for a function that was originally guaranteed to be a callee-that-always-writes, might be substituted at link-load time for a callee-that-never-writes. In which case, having the pointer on the callee's stack once again becomes handy, rather than useless.
> In fact, I think I’d argue that if you’re passing large structures by value
Not necessarily large structures. More often things like UUIDs—values just large-enough to not fit into a (64-bit) machine register. It's these small memory spills, done in hot loops, that add up. There are huge wins for the cleanliness of e.g. RDBMS engine code, if their various 128-bit to 512-bit column types can be passed by value without necessitating an eager copy.
[+] [-] CyberRabbi|4 years ago|reply
Yes that’s the point the author is making. His scheme incurs strictly less overhead than the scheme currently used.
Some people may want to program in a “value oriented” way without pointers at the C level for various reasons but for efficiency reasons they cannot.
[+] [-] cokernel_hacker|4 years ago|reply
[expr.call]: "The lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the argument expression."
[conv.lval]: "... if T has a class type, the conversion copy-initializes the result object from the glvalue."
The way a program can tell if a compiler is compliant to the standard is like so:
There are three calls to 'escape'. A programmer may assume that the first and third call to escape observes a different object than the second call to escape and they may assume that 'compliant' returns '1'.[+] [-] moonchild|4 years ago|reply
It's still a win because 1) you can avoid making copies in many places, and 2) code size decreases because the copy happens one time in the callee rather than many times for every caller.
[+] [-] vvanders|4 years ago|reply
In theory when they sort that out Rust should be able to turn on restrict where it applies globally "for free".
[+] [-] nathankleyn|4 years ago|reply
[0]: https://github.com/rust-lang/rust/pull/82834
[1]: https://github.com/rust-lang/rust/issues/84958
[2]: https://stackoverflow.com/a/57259339
[+] [-] Teknoman117|4 years ago|reply
[+] [-] hctaw|4 years ago|reply
Every major ABI is listed here as containing the same mistakes. I'm inclined to think the people who designed these ABIs were smart enough to understand the consequences of their design decisions.
I don't know whether this author is correct or not, but my gut is there is something missing here with respect to non local control flow (like exception handling, setjmp/longjmp, and fibers).
[+] [-] lostcolony|4 years ago|reply
I don't really know enough to weigh in on this, but I can say that having pursued a lot of WTFish things in my career so far, 90% of the times I've encountered bad decisions, the explanation for it was either "it was done that way because legacy reasons" (i.e., it had to be done that way then, the reason it had to be has changed, and now it would break things to do it 'correctly') or "it was easier" (i.e., at the time the badness wasn't really going to affect anyone, or not measurably, or was very intentional tech debt, and it's only 'now' that anyone is noticing/caring).
[+] [-] legulere|4 years ago|reply
It didn't matter before, as compilers were not optimizing as much, code had a much closer 1:1 correspondence to assembly (if you are passing it by pointer and not register, you would want to make that clear in code).
It's much easier to implement in simple compilers. On the side of the callee you don't have to check if you manipulate your arguments, which is generally hard. Being able to manipulate your arguments is another shortcut for keeping the compiler simple. On the side of the caller you don't have to check if you hand out a mutable pointer.
Also finally and most importantly: memory access was much cheaper in terms of cpu cycles. Just look at cdecl: all parameters are passed on the stack instead of registers. Our current calling conventions stem from performance hacks like fastcall that were only optimizing for existing code (you pass big structs by pointer by convention).
[+] [-] moonchild|4 years ago|reply
(Post author.)
Mechanically, what happens is essentially the same as what ms/arm/riscv do: the caller creates a reference and passes it to the callee. The only difference is that the callee is more restricted than it would otherwise have been in what it can do with the memory pointed to by that reference. So I don't think that there can possibly be any implications for non-local control flow.
[+] [-] brigade|4 years ago|reply
Then if you care about the possibility of signal handlers modifying the original... you pretty much have to make a copy every time anyway.
[+] [-] gpderetta|4 years ago|reply
[+] [-] jbverschoor|4 years ago|reply
[+] [-] jcelerier|4 years ago|reply
is just not possible. CPUs don't know about `const`. So you have to work with the assumption that functions that you call can do anything to their arguments. Thus copies cannot be avoided.
[+] [-] chjj|4 years ago|reply
[+] [-] temac|4 years ago|reply
So I think this idea is virtually impossible. Your ABI is probably not wrong, but your blog post probably is :P
[+] [-] foobiekr|4 years ago|reply
There is no such thing at the level that the ABI works, especially at a kernel-userland boundary (where the choice is to fully marshal the arguments or accept that you have a TOCTOU issue).
[+] [-] user-the-name|4 years ago|reply
You ABI could say a pointer may only be accessed on Wednesdays if it really wanted to.
[+] [-] legulere|4 years ago|reply
[+] [-] mssundaram|4 years ago|reply
[+] [-] SixDouble5321|4 years ago|reply
[+] [-] Narishma|4 years ago|reply
[+] [-] legulere|4 years ago|reply
The problem I see with that proposal is that it introduces a new type of pointer, the immutable pointer. That seems like equal to a const pointer, but it's not. With a const pointer the callee can not mutate the pointee. But it can still be mutated from the outside. That means that any time you handed out a mutable pointer to something you have to make a copy for handing out an immutable pointer to it. An ABI like this would probably be much more complex to implement for a small gain.
You would end up with hardly predictable behaviour wether the struct gets copied or not. C# structs suffer a lot from this, because methods are mutating by default (https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-t...). The biggest problem is simply that this is not explicit.
Also there is a case where a calling convention like this can make things worse, as you will have to make two copies:
[+] [-] moonchild|4 years ago|reply
That's incorrect. In c semantics, at least, it is legal to take a pointer to non-const, cast it to a pointer to const, pass it back, and mutate the object pointed to. It is only illegal to mutate if the pointer actually points to const memory.
Which means that, in a general sense, if you're given a const pointer, since you can't know if it actually points at const memory, you shouldn't mutate through it. But if you're handing out const pointers to non-const memory, you shouldn't count on the memory not being changed through those pointers.
IOW const is all but useless in c (except as a declaration of intent).
> An ABI like this would probably be much more complex to implement for a small gain
Why? Mechanically, it's almost the same as the ms/arm/riscv abi, except that a copy is made by the callee rather than the caller.
[+] [-] gumby|4 years ago|reply
Let's be specific: compilers for languages that support aliasing. For example, FORTRAN does not permit aliasing and therefore has all sorts of optimizations that languages that do permit aliasing cannot have.
It's a tradeoff like any other, and isn't specific to a compiler beyond the fact that a given compiler X can compile language Y.
[+] [-] varajelle|4 years ago|reply
[+] [-] colour|4 years ago|reply
[+] [-] re|4 years ago|reply
See also https://en.wikipedia.org/wiki/Aliasing_(computing)#Conflicts...
[+] [-] haneefmubarak|4 years ago|reply
Still, definitely an interesting optimization. I can definitely see a place to use this optimization myself in the near future (custom new ABI) either way.
[+] [-] zabzonk|4 years ago|reply
[+] [-] gpderetta|4 years ago|reply
[+] [-] leni536|4 years ago|reply
Having said that I think the suggestion would be observably break both the current C and C++ standards, so something like this shouldn't be done without an explicit attribute.
[+] [-] neerajsi|4 years ago|reply
[+] [-] lerno|4 years ago|reply
[+] [-] gpderetta|4 years ago|reply
[+] [-] Negitivefrags|4 years ago|reply
This is something the compiler already keeps track of anyway in order to be able to know if it needs to re-load variables from memory when they are used again after a function call.
Basically the optimsiation degrades to exactly what happened before in the case where you do this.
[+] [-] mort96|4 years ago|reply
[+] [-] jheriko|4 years ago|reply
a much better solution of course is to only apply it when strictly necessary, which seems not to be the case de facto in the wild
this is one very easy way to be 'faster than c' in practise
[+] [-] stephc_int13|4 years ago|reply
[+] [-] genrez|4 years ago|reply
Could theoretically be for Rust, but it sounds like Rust doesn't have the aliasing optimizations working yet. Based off [1], it looks like Swift's ABI might be able to take advantage of these things. (I don't know much about Swift) Based off [2], it looks like Zig definitely can do these sorts of optimizations. Ada also has aliasing constraints, (you need to specify to the compiler that you want a thing to be aliasable) but I don't know if any compilers use them for optimizations. I know that Fortran can do these sorts of optimizations. Both Ada nor Fortran seem to be undergoing a PR renaissance, so they might be "trendy new languages" for that purpose.
However, given that the other two articles on the author's website [3] are about Delphi and complaining about AT&T assembly syntax, I'd suspect that the author is just a system's programmer annoyed about ABIs.
[1] https://gankra.github.io/blah/swift-abi/#ownership [2] https://ziglang.org/documentation/0.2.0/#Type-Based-Alias-An... [3] https://elronnd.net/writ/
[+] [-] mort96|4 years ago|reply