4 ms·
I'm sorry, but why is this new? x64 calling convention has been to use `RAX` for the return value and `RCX, RDX, R8 & R9` for the first four arguments. Only if
by ayende 5y ago
I'm sorry, but why is this new?
x64 calling convention has been to use `RAX` for the return value and `RCX, RDX, R8 & R9` for the first four arguments. Only if you have more than 4 arguments will you use the stack by default.
That has been the case for a long time. For that matter `__fastcall` calling convention on x86 was the same (for two args) with `ECX, EDX` for args and `EAX` for the return call.
Isn't this just fixing a Go specific limitation?
- wahern 5y ago> Since its initial release, Go has used a stack-based calling convention based on the Plan 9 ABI, in which arguments and result values are passed via memory on the stack. This has significant simplicity benefits: the rules of the calling convention are simple and build on existing struct layout rules; all platforms can use essentially the same conventions, leading to shared, portable compiler and runtime code; and call frames have an obvious first-class representation, which simplifies the implementation of the go and defer statements and reflection calls. Furthermore, the current Go ABI has no callee-save registers, meaning that no register contents live across a function call (any live state in a function must be flushed to the stack before a call). This simplifies stack tracing for garbage collection and stack growth and stack unwinding during panic recovery. https://go.googlesource.com/proposal/+/refs/changes/78/248178/1/design/40724-register-calling.md https://go.googlesource.com/proposal/+/refs/changes/78/24817...
- rwmj 5y agoLots of languages use registers for call and return and have aggressive garbage collection. The techniques for handling this efficiently are well known. Richard Jones [not me! a famous GC researcher with the same name] maintains an excellent list of resources which the golang authors might like to read: https://www.cs.kent.ac.uk/people/staff/rej/gcbib/gcbibA.html https://www.cs.kent.ac.uk/people/staff/rej/gcbib/gcbibA.html
- compiler-guy 5y agoAll true, but Go had previously chosen not to use all that knowledge in favor of simplicity in its calling convention and therefore implementation. But like many systems that did so, real world performance implications end up justifying additional complexity.
- nottorp 5y agoActually this was new in ... Watcom C++ for DOS? Or even earlier.
- gumby 5y agoIt was the calling convention of MACLISP in the 60s and nobody seemed to describe it as exotic.
- tzs 5y agoMACLISP had another register optimization that was possible on the PDP-6 and the earlier models of the PDP-10 (the ones with the KA-10 CPU). Those machines had an address space consisting of 256k 36-bit words. The registers shared that address space with core memory. Addresses 0-15 of the 18-bit address space referred to the registers, and addresses above that referred to core. The core memory had memory for words 0-16 but it was never used because the registers overlaid it. In fact if you wanted to save a little money your could order your PDP-6 or your KA-10 PDP-10 without registers and then register accesses would go the words 0-15 of core. This worked both ways. If you put an address in 0-15 in an 18-bit memory address field it would access registers. In particular if you loaded an address in 0-15 into the program counter it would execute code out of the registers. MACLISP and others would copy some short time-critical loops to the registers and run them there for speed.
- lmilcin 5y ago“Those who cannot remember the past are condemned to repeat it.” I find it funny to talk to much younger developers, they get excited about some "new" technology and are surprised I do not feel the same. Probably they think I am old and grumpy and can't see the value in it. The truth is that a lot of it is just same verbatim or rehashed concepts that have already been known under a lot of guises for a long time. Sometimes discarded by new generation only to be re-discovered after couple of years when their software starts to suck for no obvious reason. I suggest that any person trying to do hardcore performance optimization today should invest some time learning tricks of developers that wrote software in 80s. There is wealth of useful knowledge hiding in plain sight.
- ByteJockey 5y agoDo you have any good links for starting points? I've always been interested in the subject, but articles that cover it competently are usually few and far between.
- lmilcin 5y agoI don't. I have either learned these a long time ago or as a side effect of paying attention to this topic for almost quarter of century. But I think it should still be possible to find old source code for things like games. Back when I was learning that stuff it was very difficult to get access to real game code, but today there is a lot of titles that have released its source code for everybody to pore over.
- SCHiM 5y agoAlthough I can't claim a long tenure in the software development field. One of my favorite tricks/rediscovered performance enhancer can be found in a similarity between very old-school self-modifying code, and modern day profile guided JIT optimizer. I think I read somewhere that, at some point, it was all the rage to have self-modifying code that could patch conditional branch instructions into unconditional ones (I think it was used on game consoles to save space). These days profile guided JIT compilers (or even AOT compiler) will de-virtualize calls to increase performance, which to me looks like a really similar technique.
- bla3 5y agoThe calling convention described in the parent is the Windows 64-bit calling convention. In the 64-bit calling convention used on all other platforms, the first six arguments are passed in registers (RDI, RSI, RDX, RCX, R8, R9). The parent is correct, just incomplete :)
- dundarious 5y agoThat's for Windows x64 (cdecl specifically), which has pretty much the worst calling conventions. You can use rcx/xmm0, rdx/xmm1, r8/xmm2, r9/xmm3, so for a C function signature int64_t func(int64_t a, double b, int64_t c, double d, int64_t e, double f, int64_t g, double h) Even though 4 general purpose registers and 4 floating point registers are available, you end up using 2 general purpose registers (rcx and r8 for a and c) and 2 floating point registers (xmm1 and xmm3 for b and d). rdx and r9, and xmm0 and xmm2 are unused, and the last 4 arguments must use the stack -- note that clustering the ints or doubles doesn't change that. For the same function with the System V x86_64 calling convention, (e.g., Linux, BSD, macOS), you can put everything in registers rdi, rsi, rdx, rcx, r8, r9, xmm0-7, with some to spare. rdi, rsi, rdx, rcx are used for a, c, e, g. xmm0, xmm1, xmm2, xmm3 are used for b, d, f, h. xmm4-7 are still unused, but are available if more parameters are added to the function. I haven't read the literature on this BTW, so I'm not terribly confident, but I'm guessing that on System V there are still enough scratch registers available for "general" code. High performance code where you're actually using all the lanes of your 128 bit xmm registers and so on, they should be fine on System V because you're doubling up or quadrupling up with those lanes, and often accumulating into a small subset of the argument registers anyway. So you just save yourself function call overhead. Getting further off-topic, but if anyone is interested in how to apply "high performance" thinking to the common, mundane problems we have to solve daily, I highly recommend https://media.handmade-seattle.com/context-is-everything/ https://media.handmade-seattle.com/context-is-everything/. In particular, it's a window into practical SIMD, but it also makes some worthwhile "software philosophy" points as well.