Live data from Hacker News

Does a compiler use all x86 instructions? (2010)

pepijndevos.nl

121–130 of 198 posts

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

#122

Earlier quoted context omitted.

We "over-complicate" ISAs for the same reason we're constantly adopting new vocabulary: there is efficiency in specialization. Good design is not about simplicity; it's about managed complexity. > - Why do compilers not strive to simplify their code-gen phase, or enable themselves to do advanced instruction-level program analysis, or both? Because specialized instructions formalize invariants and constraints on behav…

> We "over-complicate" ISAs for the same reason we're constantly adopting new vocabulary: there is efficiency in specialization. You're making broad generalizations, ironically speaking. If there is anything the article of this thread suggests, it is that we have created a needlessly complex instruction set, and that "efficient specialization" is not valuable to the software 99.99% of the time. > Good design is not a…

> it is that we have created a needlessly complex instructions set.

Sounds like the English language.

The words used in this discussion, btw – like instruction set – are also rarely ever used in English, but still necessary.

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

#123

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…

Those 4 cores in your 1024 core processor would be embarrassingly underpowered.

In a recent, high frequency, aggressively OoO CPU, a large part of the die is used by caches, the register file, vector ALUs and the OoO machinery; by comparison scalar ALUs and especially decoders do not take a lot of machinery. In particular microcode for all legacy instructions takes only a tiny amount of space. Legacy support might have a cost on tiny embedded processors, but it just doesn't matter in a large desktop/server class CPU.

And yes, some of those specialized instructions are rarely used (there is a reason they are called dark silicon), but it means that Intel (or ARM, IBM) doesn't need to spin a new CPU for a specialized workload. Intel is even rumored to add custom instructions required by large customers (FB, Google) on all its CPUs, which are left disabled for other customers.

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

#124
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…

"What is the minimum number of instructions a compiler could make use of to get everything done that it needs?"

I didn't go for the absolute minimum, but I did aim for useful and reasonable minimum with the ggx[1] ISA (now called moxie[1]) by defining the ISA incrementally and only adding instructions used by GCC.

The approach I took is described here: [1] https://github.com/atgreen/ggx

[2] http://moxielogic.org/

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

#125
post #46
post #42

My question is if compilers use "new" x86 instructions, as then the program won't work at all on old systems. For example, if Intel decided today that CPUs need a new "fast" hashing opcode (I don't know if they actually do), a compiler can't compiles to it, as programs won't work on older computers. Is it like the API cruft in Android, where "new" Lollipop APIs are introduced for 10 years from now, when no one uses a…

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 other binary.

The reason why JIT environments aren't competitive generally with AOT compiling is because of all the instrumentation necessary. A JIT environment is usually composed of an interpreter[1] which, using various heuristics, decides to compile segments of code to native code. But the logic for deciding what segments to compile, when to reuse a cached chunk, etc is complex, especially the pieces that keep track of data dependencies. Also, each chunk requires instrumentation code for transitioning from and back into the interpreter.[2] For this and other reasons JIT'd environments aren't competitive with AOT environments except for simple programs or programs with very high code and data locality (i.e. spending most of the time inside a single compiled chunk, such as a small loop).

Large or complex programs don't JIT very well. Even if the vast majority of the execution time is spent within a very small portion of the program, if data dependencies or executions paths are complex (which is usually the case) all the work spent managing the JIT'd segments can quickly add up. Programs that would JIT well also tend to be programs that vectorize well, and if they vectorize well AOT compilers also benefit and so JIT compilers are still playing catch-up. (One benefit (albeit only short term) for JIT compilers is that some performance optimizations are easier to add to JIT compilers because you don't have to worry about ABIs and other baggage; you iterate the compiler implementation faster.)

I increasingly hear the term JIT used in the context of GPU programming, where programs are generated and compiled dynamically for execution on SIMD cores. But that's much more like AOT compilation. The implementation stack and code generation rules are basically identical to a traditional AOT compiler and very little like the JIT environments for Java or JavaScript. The only similarity is that compilation happens at run-time, but you can analogize that with, for example, dynamically generating, compiling, and invoking C code. Which, actually, isn't uncommon. It's how Perl's Inline::C works, and how TinyCC is often used.

[1] Mike Pall has said that the secret to a fast JIT environment is a fast interpreter.

[2] So, for example, calling into a module using the Lua C API is faster in PUC Lua than LuaJIT. LuaJIT has to spill more complex state, whereas the PUC Lua interpreter is just calling a C function pointer--the spilling is much simpler and has already been AOT compiled.

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

#126
post #3

Earlier quoted context omitted.

Could you provide some links or examples? I'd love to learn more about this.

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.

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

#127
post #107

Earlier quoted context omitted.

Loading an address is just adding a fixed number to the value in a register. Your two examples are mathematically equivalent to "eax = ebp - 0x80" and "ecx = eax + 8."

Yes, but those would be two assembly instructions each. The basic idea of using lea over 'movl $ebp, $eax; addl 0x80,$eax'ist that we can shave off a cycle because lea can be executed in a single cycle. However I wonder how much any of this still matters in a time where CPUs have developed to include all sorts of complex optimizations.

Modify the backend, or do a binary translation from one to the other and test. If `lea` is the predominate instruction, there might be microcode optimizations that favor `lea` over `movl`. My hunch is that is will be mostly the same barring overflowing the instruction cache. The microps should compile to the same instruction stream.

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

#128
post #107

Earlier quoted context omitted.

Yes, but those would be two assembly instructions each. The basic idea of using lea over 'movl $ebp, $eax; addl 0x80,$eax'ist that we can shave off a cycle because lea can be executed in a single cycle. However I wonder how much any of this still matters in a time where CPUs have developed to include all sorts of complex optimizations.

Modify the backend, or do a binary translation from one to the other and test. If `lea` is the predominate instruction, there might be microcode optimizations that favor `lea` over `movl`. My hunch is that is will be mostly the same barring overflowing the instruction cache. The microps should compile to the same instruction stream.

No, LEA issues as a single micro-op on modern Intel CPUs, but no x86 CPU will merge a sequence of shift and add into a single micro op.

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

#129
post #95
post #4

> but I have no clue why there are so many lea everywhere. Pointer arithmetic? Which is used for well, ... many things.

The lea instruction is designed for array indexing. See http://stackoverflow.com/a/1665570

That's what I mean by pointer arithmetic. It seems it was intended to calculate memory addresses.

It happens to do adds and multiplications as well.

I understand things like a[i] would be equivalent in general to *(a+i)

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

#130
post #19
post #4

> but I have no clue why there are so many lea everywhere. Pointer arithmetic? Which is used for well, ... many things.

LEA (load effective address) can perform computations of the form BASE + SCALE * INDEX + OFFSET, where scale can be 1, 2, 4 or 8. This allows optimisation of multiplications and additions into a single instruction, and the compiler takes advantage of that. So if you write: a = 4 * b + c + 10; It will be optimised to a single instruction like: lea 0xa(%rsi,%rdi,4),%rax Rather than the more naive: imul $0x4,%rdi,%rax a…

Right, that's what I mean by pointer arithmetic -- specialized instruction for calculating memory addresses. It seems it can be co-opted to do math and other calculations as well. But at least that was its intended use?

Also Zen of Assembly mentions that LEA can store its result in any register and doesn't alter flags.

Post reply on HN