Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

x86 assembly works on which CPUs?

I have spent a few years coding in C and Java. I enjoy how I can make a program display a message box or perform some file operations and it works on every (I think?) CPU without any dramas. Although I have never coded anything advanced.

I want to learn assembly, for Windows only. I believe I should learn something called x86 asm.

My question: provided I do not want to do something too crazy or obscure, will programs I create work on every CPU? My focus is on your average computer at home or perhaps servers.

I have had many people tell me that I have to pick a specific architecture for a specific CPU from many reputable sources, but I am then shown code examples that work on several different CPUs... conflicting information!

EDIT: I just want to write some programs for fun in assembly without having to worry if my friend John Doe will have trouble using it on his computer. I can write a program in C easily enough that will display a message, ping google.com and whatever else without having to worry about CPUs. Is asm really not the same? o.O

EDIT2: Maybe I am getting confused as I do not know which CPUs a normal (nothing fancy) program coded on Windows in C works on... just Intel and AMD?

like image 829
securityauditor Avatar asked Dec 11 '22 06:12

securityauditor


1 Answers

The IBM PC historically evolved around Intel processors, starting with 8086, then going through 80186, 80286 (still 16 bit only), 80386 (32 bit extended), 80486, Pentium (sometimes referred to as 80586), Pentium II (686 sometimes used for this family of CPUs), then later the naming/numbering is not that simple any more.

From these the "x86" platform name was created, as all of the early models did end with "86" and the digit ahead of 86 was modified.

The "x86" family of CPUs is quite specific in the way how the new models were designed - to be [almost] fully backward compatible. So the 80186 and 80286 CPUs can run the 8086 machine code as is, they used all the original 8086 instructions, and just introduced new extension on top of that.

The first considerably different CPU was 80386, which introduced new 32 bit mode with 32 bit registers and protected mode (The 286 "protected mode" got mostly ignored and was only 16 bit). The backward compatibility is still achieved, as the 80386 after power-on will start in 16 bit mode, working as faster 80286 with few more extra instructions, then the code can switch it to 32 bit mode as needed (usually the modern OS bootloader will, very early in the OS loading process).

Then 80486 and next Intel CPUs were again backward compatible with 80386, just extending the original instruction set (and since 80486DX CPUs and Pentiums, every x86 CPU has now the floating point unit built-in by default, although with modern x86 it's obsolete ... for older 80386 and 80486SX CPUs one had to buy separate x87 coprocessor chip for HW FPU).

Meanwhile Intel tried to release itself from the backward-compatibility prison (which makes design of modern x86 CPU very complex and cumbersome), by introducing brand new 64 bit platform ("Itanium" or "IA-64"), which was NOT backward compatible. The market did hesitate with adoption a lot, unsurprisingly, because all the old "x86" SW didn't work on it.

A bit later AMD introduced it's own 64 bit extension, this time designed around "x86" legacy in similar way how the 386 extended 286, which did work much better from customers point of view (although it means for assembly programmer it's a tiny bit more tricky, like for example mov eax,1 will modify whole 64 bits of rax register, zeroing the top 32 bits automatically, etc... some of these quirky "rules" made the 64 bit extension of original 32 bit instruction set feasible, and in the long run it works quite nicely, even if it may feel a bit hack-ish at first read).

So modern "x86" PC does contain like three major different CPUs in single chip, the obsolete 16 bit 80286, the very old 32 bit "686", and current 64 bit "x86-64" variant. On assembly point of view all of them share the basic instructions and syntax, so if you learn "686" instruction set fully, the x86-64 source code will looks mostly familiar to you. And as long as you will use only the basic subset of instruction set (like the "686" subset), your binary will work on the particular OS on all 15-20y old x86 PCs.

Meanwhile the AMD (and Cyrix and other manufacturers who tried to compete with Intel in the "x86" world) are producing CPUs which are binary compatible with Intels. Sometimes they tried to introduce some extensions, like "3Dnow" from AMD, but they were used by programmers only rarely, and usually got abandoned. The only exception is the current 64 bit mode, which was designed by AMD and Intel in the end had to give up and copy it from AMD into their CPUs (as their Itanium IA-64 was not accepted by market, due to missing backward compatibility).

But if you are like game developer, going after maximum performance, you will have to check the CPU model/features in runtime and depending on the availability of the instruction extensions, you can provide different variants of binary for particular CPUs, like one done with SSE2 instructions at most, being capable to run on all 64 bit CPUs, and other variants using also new extensions like SSE4 and AVX512, which will work only for the minor group of consumers, who have the latest CPUs.

Many C compilers by default on x86 target 32 or 64 bit mode (depending mostly on OS, or your project settings) and use only very limited instruction set, like mostly "686" only, or x86_64 without even SSE1/2, so their binaries will work on any common x86 PC. Although the code is sub-optimal, not using the modern features of current CPUs.

That's also the first thing you can learn, if you are targetting "x86" assembly knowledge, start with the basic instruction set, like maybe only 80386, learn principles on that, then see how it was extended later (skip 16 bit 80286, that's just useless torture, the 32 bit mode is lot more simpler to learn, then if you are really curious, you can try to see how the 16 bit differs, the 32 bit experience will make it somewhat easier, but it's quite pointless).

The 64 bit is tiny bit more tricky/complex than 32 bit mode, but it's not that bad, you can start even with that, if you wish (nowhere as tricky, as 16 bit was).

BTW, you should still start with C-lib for I/O and win API calls, i.e. you will need C compiler. Accessing Win API from pure ASM is somewhat tricky (just a needless distraction, if you are just learning ASM basics), and you can't access HW directly under modern OS, so there's no way to do I/O without OS API services, unlike the old 16 bit mode, where there was no protection and you could access HW peripherals freely. You can call your asm functions from C wrapper, which may then handle the I/O, memory management, and other OS API related things, focusing on pure algorithms and asm programming in your asm functions (that's also how assembly is used in real projects, you don't write whole app in asm today, you keep it in C/C++ and rewrite only the most performance critical parts in ASM, after you have working C/C++ prototype and you did identify particular bottleneck by profiling .. there's no point to write other parts in assembly, like file reading, etc, too cumbersome without any benefit).

BTW, The C is portable language. If you will use only standard C library, it will work (the SOURCE) on pretty much anything, but only after you will COMPILE it for particular target platform BINARY.

The windows is very small world, mostly limited to x86 (there were windows variants for IA-64 and DEC Alpha, which can't run x86 binaries, but those were professional machines at specialized work, not known much to public). So if you compile Win32 x86 executable with default options, in will run (sub-optimally) on 99% of windows computers, using only limited sub-set of x86 instructions. If you will switch on the compiler to use some modern features of your CPU, the resulting binary may not work any more on John Doe's PC, if he has x86 CPU which doesn't support one of the features your machine code does use.

For many applications this default sub-set is more than enough, and you don't need to bother with the extended instructions. Only few applications need maximum performance, like games, CFD or other scientific calculations... your ordinary web browser operating with +5-10% performance penalty can be safely ignored (and there's usually somewhere in driver/etc some fine-tuned piece of code for your current CPU, which does handle the major performance hungry things, like decoding video/etc, so even web browser compiled with generic "x86" target will benefit from those).

You don't need to produce 9999 variants of asm code, if you want just to make it run, basically you need only 32 or 64 bit variant (depending on the target, I don't know about windows world, but with modern OS you can safely target 64 bit only, that's like 90+% of users), using the basic x86 instructions, that will work "everywhere" (on x86 windows).

But there's not much point to use asm like that (except educational purposes, where it makes perfect sense to start exactly with this), because the performance of such asm will be sub-optimal, so you can already use C or C++ to get similar results. For meaningful usage you have to learn also the modern extensions, do the runtime check for available features, and dynamically load the correct variant of your function, using the optimal machine code for particular CPU type. That's the part where you may have to write 9999 variants of same function (it's not that bad, usually 4-7 variants will probably cover most of the available CPUs, one compatibility fallback using only basic instruction set, then few more specialized, like SSE3, +SSE4, AVX1/2, AVX512, etc..). Also the real world SW needing maximum performance is very rare, even most of the simpler games are completely fine with sub-optimal binaries, only if you are working on some bleeding edge like Unreal-engine developer, you have to care about those fine tunings. Most of the time the gains are not worth the investment.

After all, there's now so much SW in use which is written in C#, Java, or even JavaScript (or PHP .. did I really mention it? Feels dirty now), that obviously the performance is not a problem, otherwise most of that would be rewritten into C++ after some time, to get the performance boost.

like image 169
Ped7g Avatar answered Jan 16 '23 10:01

Ped7g