7 ms·
The register keyword is still useful in compiler specific contexts. e.g., for GCC-ish compilers like gcc, clang: long result; register long nr __asm__(
by dundarious 3y ago
The register keyword is still useful in compiler specific contexts. e.g., for GCC-ish compilers like gcc, clang:
long result;
register long nr __asm__("rax") = __NR_close;
register long rfd __asm__("rdi") = fd;
__asm__ volatile ("syscall\n" : "=a"(result) : "r"(rfd), "0"(nr) : "rcx", "r11", "memory", "cc");
The above is basically how you might implement the close(int) syscall on x86-64.
You don't need to be doing embedded programming to find it useful to dip down into assembly like that (though syscalls are perhaps a bad example, even for a syscall not provided by your C library -- that library probably provides a `syscall` function/macro that does all this in a platform agnostic way).
Also, "%.*" is extremely useful with strings, i.e., "%.*s". Your code base should be using length delimited strings (basically `struct S { int len; char* str; };`) throughout, in which case you can do `printf("%.*s\n", s.len, s.str);`
- messe 3y agoUm, no. You could could remove the register/__asm__("reg") qualifiers entirely and just specify "D" (rfd) as an input parameter and the code would work fine. There is absolutely no need for register there. The only good use of register these days is for project-wide globals in embedded contexts. IIRC one example of this is the decompilation of Mario 64, where a certain register ALWAYS contains the floating point value 1.0.
- dundarious 3y agoBoth are valid, but I much prefer the `register` method I gave (documented in https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables....), as it is far more self-explanatory. GCC's extended asm syntax has too many inscrutable constraint and modifier codes even excluding these GCC-ext-asm-specific codes to reference machine-specific registers by name. As such, I totally disagree with your statement about "the only good use". Given that I first learned about that method of specifying registers by reading linux kernel source code, I think others would disagree as well.
- Dwedit 3y agoI think the GCC ASM syntax for specifying inputs and outputs is quite clear enough, and doesn't require a variable declaration with unusual syntax.
- dundarious 3y agoI'm merely referring to the fact that I specified rdi by writing "rdi" not "D". I can specify r10 by writing "r10", but I can't remember how to specify that directly in the inputs/outputs constraints -- a glance at https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html didn't show me how either, but I'm guessing it's there. [Edit: Although, on second glance, from https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Input-Operands https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Input-O...: > If you must use a specific register, but your Machine Constraints do not provide sufficient control to select the specific register you want, local register variables may provide a solution (see Specifying Registers for Local Variables). indicates in the r10 case maybe you _must_ use the syntax I gave?] My preference is for the syntax that requires looking up fewer tables in GCC docs, but as I said, the version you prefer is fine too.
- LegionMammal978 3y agoIndeed, the register variable syntax is necessary for many of the registers; there are only so many of them that have been stuffed into the one-letter constraint. I've used it before for making raw x86-64 Linux syscalls (which can use r10, r8, and r9) without going through errno, as part of a silly little project ([0]) to read from a file descriptor without writing to process memory. [0] https://pastebin.com/mepsedCC https://pastebin.com/mepsedCC
- dundarious 3y agoNice. Yes, linux/tools/include/nolibc has syscall macros that look near identical.
- asveikau 3y ago
- gpderetta 3y agoEven on x86, There are some registers where there isn't a corresponding exact input operand modifier, so register is the only option. But I forgot which register.
- ufo 3y agoJust wondering... Why do they need to keep 1.0 in a register?
- messe 3y agoIt's used in a lot of contexts, and it's faster to have it on hand rather than loading the constant from memory (which is slow on the N64).
- WalterBright 3y agoD's inline assembler: asm { mov RAX,__NR_close; mov RDI, fd; call syscall; } The compiler automatically keeps track of which registers were modified.
- matheusmoreira 3y agoThat's beautiful... Why can't GCC and clang have this? Small note: your code seems to be calling a wrapper function rather than running the syscall instruction directly so it could probably be even more succinct. Is this valid D? asm { mov RAX, __NR_close; mov RDI, fd; syscall; }
- WalterBright 3y agoSYSCALL is an actual instruction, and it's supported by the D inline assembler. https://www.felixcloutier.com/x86/syscall https://www.felixcloutier.com/x86/syscall > Why can't GCC and clang have this? I don't know. My brain bleeds out my ears every time I have to deal with that.
- dundarious 3y agoIt really is painful.
- pjmlp 3y ago> Why can't GCC and clang have this? Because UNIX culture sucks at inline Assembly. D's approach was widely used across PC, Amiga and Atari ST compilers. Besides D, that is what Delphi, C++ Builder and Visual C++ still use nowadays, for the targets that still support inline Assembly. For 64 bit targets, they use intrinsics, which are still preferable to UNIX's approach.
- mananaysiempre 3y ago> That's beautiful... Why can't GCC and clang have this? There are tricky contingent reasons, like the fact that GCC proper doesn’t try and actually isn’t even able to parse assembly—and it would need to here in order to know that it can’t put fd in RAX or in any location whose address computation includes RAX. (GCC’s codegen in general works by stitching together fragments of assembly as text and feeding them into the system assembler. Which is nice and UNIXy and all, but I’m not sure much is won by making the compiler agnostic to the actual bit patterns of the instructions when these days it is still aware of almost every other facet of the instruction set and quite a few microarchitectural details besides.) But the fact of the matter is that GCC’s approach also permits better optimization, by telling the register allocator exactly which registers are clobbered by the inline assembly snippet and where the computations of the input should aim to place their results. You frequently end up with no MOVs at all, although Clang is better at this part than GCC. (Do not be fooled into thinking that an automatic "register asm" variable is pinned to the specified register for all of its scope—it’s only guaranteed to be placed there at the start of any inline assembly blocks, the register allocator can move it out of the way in between them.) I think the x86 code doesn’t actually need the "register asm" syntax at all, FWIW—you should be able to just add "a"(__NR_close), "d"(fd) to the inputs. GCC on RISC archs doesn’t usually define a separate constraint for each individual register, though, so on those it can be necessary.
- pm215 3y agoclang does not support specific-register variables for registers which are allocatable by its register allocator (https://clang.llvm.org/docs/UsersManual.html#gcc-extensions-not-implemented-yet https://clang.llvm.org/docs/UsersManual.html#gcc-extensions-...) so this is gcc-only. If you care about portability between gcc and clang you'll need some other approach...
- dundarious 3y agoIt doesn't support them for a specific kind of global, but it supports function local use like the one I gave above. Here's another example I tried just now: #include <sys/syscall.h> /* for __NR_exit */ int main(void) { long ret; register long nr __asm__("rax") = __NR_exit; register long ec __asm__("rdi") = 99; __asm__ volatile("syscall\n" : "=a"(ret) : "r"(ec), "0"(nr) : "r11", "rcx", "cc", "memory"); return 0; } Build: clang -s -O0 -o ./t ./t.c Disassemble to double check: objdump -M intel -D ./t Run: ./t || echo $? Output: 99 The docs you link basically say that the special global "1.0" register mentioned in https://news.ycombinator.com/item?id=36551728 https://news.ycombinator.com/item?id=36551728 wouldn't be supported in clang -- so much for the only good usage!
- pm215 3y agoWhoops, thanks for the correction. My main experience with register variables is with the global kind -- QEMU used to use one to hold a CPU environment pointer, and we had to refactor to get rid of it in order to support clang. (By that point in time the register global was more of a legacy bit of design rather than something that was actively useful for performance or convenience, so it was a useful piece of cleanup to do anyway.)
- penguin_booze 3y agoIsn't `register` more a hint than guarantee? Likewise, is binding a variable to a named register (let alone any register) guaranteed to be respected?