If compiler converts high-level language to machine code, why do we even need assembler? Are there any assembly level language and we can't use compiler for that?
-
Assembler is just a human readable representation of the machine code. What would the compiler compile to without it? – Jeroen3 Aug 10 '18 at 06:26
-
4most modern compilers compile to assembly except tcc which outputs binary directly – phuclv Aug 10 '18 at 06:38
-
See also https://cs.stackexchange.com/questions/13287/why-do-we-need-assembly-language – xmojmr Aug 10 '18 at 06:47
-
@phuclv: clang and MSVC, and I think ICC, all create object files directly, effectively using a built-in assembler. Only gcc *actually* creates a `.s` file and runs a separate program on it. Other compilers have an option to emit asm, though. – Peter Cordes Aug 10 '18 at 06:49
-
@PeterCordes yeah, they still need the final assembling stage even internally. OTOH [tcc emits binary directly](https://github.com/mattgodbolt/compiler-explorer/issues/246#issuecomment-275208613) and you have no way to stop at the asm stage – phuclv Aug 10 '18 at 06:56
-
2@phuclv But AFAIK they don't literally have ASCII strings internally, just an internal representation of instructions using a `struct` or something that they turn directly into machine code, or into asm text. I wouldn't quite call it *actually* internally assembling, except for inline asm. – Peter Cordes Aug 10 '18 at 06:57
-
AFAIK recent MSVC isn't even able to emit syntactically correct assemblable code even if you ask for it. – Matteo Italia Aug 10 '18 at 07:13
-
3Without assembly language to make the job of creating machine code sane, explain how you would create a test a new processor or new modifications to existing instruction sets so that down the road someone can create/tune a compiler? Without the egg there is no chicken. Likewise you want to debug a processor, a human readable version of the machine code makes that much easier. You want to test/debug a compiler, a human readable version of the machine code makes that much easier thus the reason why compilers often compile to asm, not to mention the assembler comes before the compiler. – old_timer Aug 10 '18 at 08:19
5 Answers
Related: Does a compiler always produce an assembly code? - more about why some compilers do compile only to asm, instead of straight to machine code in some object file format. There are several reasons that compiling to asm instead of machine code makes a compiler's job easier and a compiler more easily portable. But compilers aren't the only reason for asm existing.
why do we even need assembler?
Many people don't need to know assembly language.
It exists so we can talk about / analyze machine code, and write/debug compilers more easily.
Compilers have to be written by humans. As @old_timer points out, when designing a new CPU architecture, you always give names to the opcodes and registers so you can talk about the design with other humans, and publish readable manuals.
Or for OS development, some special privileged instructions can't be generated by compilers1. And you can't write a context-switch function that saves registers in pure C.
CPUs run machine-code, not high-level languages directly, so computer security / exploits, and any serious low-level performance analysis / tuning of single loops require looking at the instructions the CPU is running. Mnemonic names for the opcodes are very helpful in thinking and writing about them. mov r32, imm32
is much easier to remember and more expressive than B8+rd imm32
(the range of opcodes for that mnemonic).
Footnote 1: Unless like MSVC you create intrinsics for all the special instructions like __invlpg()
that OSes need to use, so you can write an OS without inline asm. (They still need some stand-alone asm for stuff like entry points, and probably for a context-switch function.) But then those intrinsics still need names in C so you might as well name them in asm.
I regularly use asm for easily creating the machine code I want to test for microbenchmarks. A compiler has to create efficient machine code, not just correct machine code, so it's common for humans to play around with asm to see exactly what's fast and what's not on various CPUs.
See http://agner.org/optimize/, and other performance links in the x86 tag wiki.
e.g. see Can x86's MOV really be "free"? Why can't I reproduce this at all? and Micro fusion and addressing modes for examples of micro-benchmarking to learn something about what's fast.
See C++ code for testing the Collatz conjecture faster than hand-written assembly - why? for more about writing asm by hand that's faster than what I could hand-hold gcc or clang into emitting, even by adjusting the C source to look more like the asm I came up with.
(And obviously I had to know asm to be able to look at the compiler's asm output and see how to do better. Compilers are far from perfect. Sometimes very far. Missed-optimization bugs are common. To think of new optimizations and suggest that compilers look for them, it's a lot easier to think in terms of asm instructions than machine code.)
Wrong-code compiler bugs also sometimes happen, and verifying them basically requires looking at the compiler output.
Stack Overflow has several questions like "what's faster: a++
or ++a
?", and the answer completely depends on exactly how it compiles into asm, not on source-level syntax differences. To understand why some kinds of source differences affect performance, you have to understand how code compiles to asm.
e.g. Adding a redundant assignment speeds up code when compiled without optimization. (People often fail to realize that compiling with/without optimization isn't just a linear speedup, and that it's basically pointless to benchmark un-optimized code. Un-optimized code has different bottlenecks... This is obvious if you look at the asm.)

- 328,167
- 45
- 605
- 847
Quoting from @TylerAndFriends's answer on Why do we need assembly language? on cs.SE (a duplicate of this):
Assembly language was created as an exact shorthand for machine level coding, so that you wouldn't have to count 0s and 1s all day. It works the same as machine level code: with instructions and operands.
Though it's true, you probably won't find yourself writing your next customer's app in assembly, there is still much to gain from learning assembly.
Today, assembly language is used primarily for direct hardware manipulation, access to specialized processor instructions, or to address critical performance issues. Typical uses are device drivers, low-level embedded systems, and real-time systems.
Assembly language is as close to the processor as you can get as a programmer so a well designed algorithm is blazing -- assembly is great for speed optimization. It's all about performance and efficiency. Assembly language gives you complete control over the system's resources. Much like an assembly line, you write code to push single values into registers, deal with memory addresses directly to retrieve values or pointers. (source: codeproject.com)

- 328,167
- 45
- 605
- 847
-
3Seriously, your post is almost a complete copy pasta from [this answer](https://cs.stackexchange.com/a/13289). Please add this as a reference in your post because plagiarism is a big no-no. – KarelG Aug 10 '18 at 06:52
-
2Nice answer except for the quote from codeproject. "push values into registers"? The `push` instruction reads registers and writes the stack, on all architectures that have such an instruction. You use various methods for putting values into registers, but `push 1 / pop rax` is not used unless you're optimizing for code-size over speed. ([Tips for golfing in x86/x64 machine code](https://codegolf.stackexchange.com/a/132985)) – Peter Cordes Aug 10 '18 at 06:55
-
1@PeterCordes: presumably they're using "push" informally here, or talking about, e.g., writing control registers (Vax MTPR instruction, the [MSR on PowerPC](https://en.wikipedia.org/wiki/Machine_state_register), Sparc `WR` instruction, and so on). – torek Aug 10 '18 at 20:21
Some more examples:
- Interacting with the interrupt handler to implement atomic opaerations such Linux's atomic operations on ARMv5 and earlier.
- Call a system call only if the signal handler has not been called in QEMU linux-user.
- Initalising a computer to a state that a compiler can be used, for example configuring the memory controller.
- Entry and exit to/from interrupt handlers and system calls.

- 3,551
- 1
- 14
- 23
TL;DR - if compiler and debugger writers are perfect you probably don't need assembler for application programming. However, your fundamental understanding of computing will not be complete. You will loose the ability to step outside the box.
An assembler tries to map one-to-one the machine mnemonics to the underlying binary op-codes. As such it is the most expressive language to a particular machine. Some languages attempt to hide 'pointers' or memory addresses. All languages hide register allocation and mapping variables to either stack slots or physical registers. The job of the optimizing compiler is to map the high level language to the underlying machine language. The algorithms used can be quite exhaustive as a computer can search a large number of solutions faster than a human and find an optimal solution.
The compiler 'fails' when it does not realize a machine concept will map a problem to the most effective solution. For instance, there is no idea of a 'carry bit' in 'C' and 'C++'. There are several solutions for arbitrary large number types. For problems involving large integers, it is useful to use the 'carry bit' to chain smaller integers into larger integer (number of bits). Compiler developer has realized this issue and implement various solutions. The most trivial is just to add more and more types (long long unsigned, etc). Some compilers will detect idioms in 'C' where a programmer is trying to use the high bit to chain to a low bit. For example,
/* a,b are two parts of one number.
c,d are two parts of another to be added.
*/
void add_big(uint *a, uint *b, const uint c,const uint d) {
unsigned long long tmp;
tmp = *b + d;
if(tmp & CARRY_BIT)
*a += c + 1;
else
*a += c;
*b = (uint)tmp;
}
The complexity demonstrates how hard it can be to do this task which you want to be simple and efficient. In fact most machines allow this to map to only a few assembler instruction. The compiler writer needs to recognize that the pattern the user is using can collapse the several high level constructs to a few assembler instructions. Or, they provide a language escape to the lower level assembly.
Many debugging issues can only be solved more efficiently with knowledge of assembler and machine concepts. If you program in a higher level language such as Python, this will not be pertinent. But then you ultimately rely on other developer to make containers (lists, sets, dictionaries, numpy, etc) to create this code in some lower level language. Many efficient data structures can not be coded without memory addresses.
Even if you never use assembly language, the concepts will help you understand why code is slow. A high level language may mask many details about why things are/are not efficient. Often if you understand how the tool is mapping thing to assembler language your search towards an efficient solution is much faster.
For a security researcher, knowledge of assembler opcodes can be pretty fundamental to understand exploits. For an OS/systems programmer, there are many opcodes that will not map to a higher level language. For compilers and language authors, finding the best mapping to a problem set and ways to express this, you need to understand assembler; or even more machine architectures which includes nuances of memory access.
Ultimately a professional programmer will be confronted with proprietary code which has limitations. This code will not come with source. Often the most effective way to diagnose and overcome the issue is to examine the binary for issues. If you can not understand assembly language, you are stuck.

- 21,212
- 6
- 68
- 105
-
Knowing asm to help your debugging efforts is mostly relevant in "unsafe" languages like C and C++, where buffer overruns, wild pointers, use-after-free, uninitialized data, and other kinds of bugs have effects that make sense in asm but are totally opaque (Undefined Behaviour) in terms of the higher level language. I think that's the really key difference between C and Python here. And it would apply even to C vs. Java. You can use references in safe languages to do basically anything you can with pointers in C, except that overhead may be huge in Python. – Peter Cordes Sep 07 '20 at 18:50
-
@PeterCordes Yes, there is truth to that. Someone writes 'C' code via swig, etc that interfaces with Python. So if you want to write libraries for Python, it will help to know assembler. Also, writing an interpreter will benefit from assembler. People end up making various synthetic opcodes. Like BSD filters, python and java byte code, JIT, etc and all of these developments for those language borrow concepts from assembly languages. I think it is much the same as you can program without knowing mathematics, but you may not be as good of a coder. `sed -e "s/mathematics/assembler/g"` – artless noise Sep 07 '20 at 19:53
-
Sure, agreed with all of those points. Understanding how computers truly run programs is pretty useful at least for performance in any language. My quibble was limited to the specific point about practical benefit *for debugging*. Otherwise nice answer, already upvoted. – Peter Cordes Sep 07 '20 at 19:59
-
@PeterCordes I wouldn't say it is totally useless for Python, etc. Suppose a developer notices that `r = sqrt(A * 1/pi)` is faster than `r =sqrt(A/ pi)` in a language. Assembler knowledge makes it pretty obvious why. Otherwise, it just seems mysterious. I agree that the strength of the statement is much stronger for 'C' than most other languages. You have a great answer too, I was just trying to add some complementary points that may apply. – artless noise Sep 07 '20 at 20:09
-
Maybe you misread my last comment. I said it *is* useful, even for high level languages like Python. Although insane interpreter overhead in CPython makes some things counter-intuitive (e.g. integer division has an interpreter fast-path for 1-limb small integers and is faster than bitwise-and). So yeah, I think we 100% agree with each other. :) And yeah, good additional points, agreed it's a nice complement to my answer. – Peter Cordes Sep 07 '20 at 20:21
The compiler can translate the code written in high level language to a machine code but that's not the only language it can translate to. It can also translate the code into assembly language and more. SEE https://www.quora.com/Does-a-compiler-convert-code-to-assembly
However as mentioned in the above answers we can see why we generally use assembler after compiler.

- 31
- 1
- 3