Live data from Hacker News

Does a compiler use all x86 instructions? (2010)

pepijndevos.nl

151–160 of 198 posts

Re: Does a compiler use all x86 instructions? (2010)

#151
post #7

Earlier quoted context omitted.

There are also AFIAK a few "deprecated" instructions that are implemented for backward compatibility but do not perform well on modern cores or have much better modern alternatives. These would be things like old MMX instructions, cruft left over from the 16-bit DOS days, etc. X86 is crufty. Of course all old architectures are crufty, and using microcode it's probably possible to keep the cruft from taking up much si…

Good examples of (essentially-deprecated) instructions include the rep prefixed instructions for string operations (modern library code for string operations typically involve a mixture of SSE, full-word loads and unrolled loops for speed); the "loop" instruction (compilers usually generate explicit loops for flexibility); pretty much all the BCD arithmetic instructions (since programming languages don't typically us…

The BCD instructions, despite originally conceived for BCD, actually have some other niche uses such as AAM/AAD for a small multiply/divide:

https://news.ycombinator.com/item?id=8477254

In that article I also further refute the common belief that they are significantly slower in this comment, by reasoning and then an actual benchmark: https://news.ycombinator.com/item?id=8477585

And this is where I think a gap between compilers and humans exist --- would a compiler recognise, for example, that if your code, for whatever reason, happened to have this pattern of operations in it...

http://x86.renejeschke.de/html/file_module_x86_id_3.html

...it should emit a single AAS instruction? Or perhaps a simpler example, would it know to emit an AAM for an 8-bit division or modulus with a constant divisor? Maybe it could --- see the other discussion here about LEA --- but the developers just didn't bother to. I wish they would though, because despite how slow these instructions may be (which might not be the case), they are still valuable for size optimisation (-Os), and after all, it is a gap.

Re: Does a compiler use all x86 instructions? (2010)

#152
post #97

In general: * x87 floating point is generally unused (if you have SSE2, which is guaranteed for x86-64) * BCD/ASCII instructions * BTC/BTS/related instructions. These are basically a & (1 * MMX instructions are obsoleted by SSE * There's some legacy cruft (e.g., segment management) that's generally unused by anyone not in 16-bit mode. * There are few odd instructions that are basically no-ops (LFENCE, branch predicto…

> * There's some legacy cruft (e.g., segment management) that's generally unused by anyone not in 16-bit mode. OpenBSD uses segments(while in protected mode!) to implement a line-in-the-sand W^X implementation on i386 systems that don't support anything better. The segment is set just high enough in a processes space to cover the text and libraries but leave the heap and stack unexecutable. This mentions this impleme…

VMware also uses(used?) segments to hide its hypervisor: http://www.pagetable.com/?p=25

Re: Does a compiler use all x86 instructions? (2010)

#153
post #126

Earlier quoted context omitted.

Just read the output of your compiler for simple functions. objdump -d, or, cc -S

objdump -S does the trick too, it even has the C code intermixed if the -g CFLAG was used.

https://gcc.godbolt.org/ may help too

Re: Does a compiler use all x86 instructions? (2010)

#154
post #78

And therein lies the rub. What is the minimum number of instructions a compiler could make use of to get everything done that it needs? I came across an article that says 'mov is turing complete' [1]. But they had to do some convoluted tricks to use mov for all purposes. I think it's safe to say that about 5-7 instructions are all that's needed to perform all computation tasks. But then: - Why do compilers not strive…

You're basically asking something akin to "why didn't MIPS or any other RISC become dominant?" (Yes, I know about ARM. Despite its name and claims, ARM is not really RISC anymore. It has grown instructions and specialised hardware, so it could remain competitive with x86, and they also use micro-op translation: http://techreport.com/review/28189/inside-arm-cortex-a72-mic...)

I think it's safe to say that about 5-7 instructions are all that's needed to perform all computation tasks.

One instruction is needed to be Turing-complete. It's not very practical though, as you need many more simple instructions to do the work of a single complex one.

Consider memcpy(), one of my favourite examples. On a simple architecture like MIPS, the software has to explicitly perform each read and write along with updating the pointers, the address calculations, and the loop control. It also has to take into account alignment and when to use byte/word/dword accesses. This all requires instructions, which occupy precious cache space (and unrolling makes them take even more) and have to be fetched, decoded, and executed. It can only read and write e.g. 32 bits at a time, because that's the only size the architecture supports for individual load and store instructions. If a wider (e.g. 64 bit) revision appears, all existing memcpy() has to be rewritten to take advantage of it.

On the other hand, consider the CISC solution: REP MOVSB. A single two-byte instruction which the hardware decodes into whatever operations are most optimised for it. It handles updating the registers, the copy, and the loop using specialised hardware. It can transfer entire cache lines (64 bytes or more) per clock cycle. Software doesn't need to change to take advantage of e.g. a newer processor with a wider memory bus. It's tiny, so it uses next to no cache space, and once it's been fetched, decoded, and executing internally, the memory/cache buses are free for other purposes like transferring the data to be copied, or for the other cores to use. It's far easier for the CPU to decode a complex instruction internally into micro-ops and/or dispatch it to dedicated hardware than it is to try pattern-matching long sequences of simple instructions into a complex semantic unit for such hardware when it becomes available. It's hard enough for something as simple as memcpy(), never mind AES or SHA1.

- Why do compilers not strive to simplify their code-gen phase, or enable themselves to do advanced instruction-level program analysis, or both?

Compilers are complex because the CPUs they generate code for are also complex, and the CPUs are complex because of the reason above: this complexity is efficency. A compiler for a simple CPU could be simple, but that just means the CPU is so simple that there is nothing to optimise at the software level; hardly an ideal situation. I think it wouldn't be too hard to get GCC to generate only the subset of x86 instructions that most closely resembles MIPS, and compare the resulting binaries for size and speed. It should then be obvious why more complex instructions are good.

Re: Does a compiler use all x86 instructions? (2010)

#156
post #125
post #46

Earlier quoted context omitted.

JIT compilers are able to take advantage of them, because you don't get a binary set in stone that has to run everywhere. This is the main reason why Apple is now pushing for LLVM bitcode, Android still uses dex even when AOT compiling and WP uses MDIL with AOT compilation at the store. So regardless of what an OEM decides for their mobile device, in theory, it is possible to make the best use of the chosen CPU. This…

The AS/400 is more like an AOT than a JIT compiler. When I hear JIT I think opportunistically compiling portions of a program, but falling back to an interpreter. The way AS/400 works, IIUC, is that the compiler compiles to an intermediate byte code, which has remained stable for decades. When the program is first loaded, the entire program is compiled to the native architecture, cached, and then executed like any ot…

I used the term JIT, because many tend to associate AOT to native compilation when it happens before the binary is shipped to the customers.

Regarding AS/400, doesn't the documentation refer to it as kernel JIT?

Thanks for the explanation.

Re: Does a compiler use all x86 instructions? (2010)

#157
post #97

Earlier quoted context omitted.

> * There's some legacy cruft (e.g., segment management) that's generally unused by anyone not in 16-bit mode. OpenBSD uses segments(while in protected mode!) to implement a line-in-the-sand W^X implementation on i386 systems that don't support anything better. The segment is set just high enough in a processes space to cover the text and libraries but leave the heap and stack unexecutable. This mentions this impleme…

VMware also uses(used?) segments to hide its hypervisor: http://www.pagetable.com/?p=25

Used. AMD64 removed segment limit checking. Base offsets are still applied for %fs and %gs, but not other segment registers. We got them to add a flag to re-enable it (and SAHF), but Intel never had it.

Nowadays it is all Vanderpool/Pacifica, aka VT-x/AMD-V.

Re: Does a compiler use all x86 instructions? (2010)

#158

Intel's own optimizing C++ compiler uses more, or well different ones anyway. Its really amazing what it can do. Uses instructions I never heard of.

Then disables any of them from running on AMD processors (and that's still the case. They were told by the courts to either place a warning or stop the practice, so they buried a vague warning in the paperwork).

Re: Does a compiler use all x86 instructions? (2010)

#159
post #28

Earlier quoted context omitted.

Good examples of (essentially-deprecated) instructions include the rep prefixed instructions for string operations (modern library code for string operations typically involve a mixture of SSE, full-word loads and unrolled loops for speed); the "loop" instruction (compilers usually generate explicit loops for flexibility); pretty much all the BCD arithmetic instructions (since programming languages don't typically us…

> the rep prefixed instructions for string operations ... are actually preferred over a hand-written vectorized loop on Ivy Bridge and up (see [1] section 3.7.7, "Enhanced REP MOVSB and STOSB operation (ERMSB)"). It's indicated by a CPUID feature flag bit (edit: grep for "erms" in /proc/cpuinfo to see this). The reason is that microcode knows more about the dcache microachitecture, load/store units, special features…

Do you have benchmarks, supporting "~break-even vs. 128 bit AVX on Ivy Bridge from 128 bytes up to 2KB" as I have not found it to be the case at least on Haskell (my benchmarking code is @ https://bitbucket.org/olegoandreev/scratch/src/dd7ab9008c59c...).

Re: Does a compiler use all x86 instructions? (2010)

#160

Earlier quoted context omitted.

> Why do microprocessors not strive for simplicity, implement only a handful of instructions in an optimized way, with a very small chip footprint, to be followed by proliferation of cores (think 256-core, 512-core, 1024-core). Modern CPU designers have such a larger transistor budget than they need to get creative to make use of all of it, so specialized instructions are pretty much free. And no, you can't just stuf…

> larger transistor budget ... because we (the chip designer) are okay with larger footprint per core. > specialized instructions are pretty much free ... only after we have fixed the footprint per core. But if we're willing to vary that parameter, then the specialized instructions are not free. Not to mention, the main article of this thread is a strong evidence that those specialized instructions are almost never u…

In terms of die area, even for processors that implement the x86 instruction set, the instruction decode engine is smaller than the out-of-order execution logic (register renaming and the retire queue are quite expensive in space). The branch predictor is larger than both if you have dynamic branch prediction (i.e., if you want a branch predictor that works). Load/store units pretty much dwarf any other execution unit (turns out paging isn't cheap in die area), particularly when you include the L1 cache.

Here's an example of die area: http://farm6.staticflickr.com/5321/9104546631_4c7a4a023b_o.j... I don't think it's the best picture, but it's surprisingly hard to find these for recent x86 processors.

If you want to stick more cores on a single die, you have to shrink the size of a core. And looking at die space, the real big wins are the caches, out-of-order execution, and branch predictors--losing all of which will kill your IPC. The other problem with high core counts is memory bandwidth. The instruction fetch bandwidth alone on 1024 cores runs to GB/s. Cache coherency traffic and core communication traffic could likely suck up the rest of the bandwidth.

Proposing to rip out most of the instructions leaves you with the problem that you're ripping out hardware-accelerated instructions, and you have nothing to compensate for the lost speed. You can't increase clock frequencies (power will kill you); you can't increase core counts (the memory bandwidth is insufficient, not to mention Amdahl's law).

Post reply on HN