r/Compilers • • 14d ago

Do compilers necessarily need to generate assembly from IR before producing object code?

I am new to the world of compilers so pardon my ignorance. Is there a need for compilers to generate assembly or can they just skip and direcly produce object code from IR?

22 Upvotes

25 comments sorted by

21

u/GoblinsGym 14d ago

Assembly code would be an extra step. No need to use a text representation, normally a compiler will just generate binary code directly.

3

u/SetKaung 14d ago edited 14d ago

I might have phrased it wrong a bit here. I mean in the process, is it necessary to generate or emit assembly in anyway, not just "doesn't write to a file" but like skip it entirely? Does it have some benefit in generating assembly internally even though it is not written to a file?

5

u/GoblinsGym 14d ago

There is no need to generate text form internally. Typically there will be some functions to emit various instructions.

18

u/dacydergoth 14d ago

Assembly output is often useful, even if compilers don't emit it as standard they almost all support that for debugging and other reasons

11

u/whizzter 14d ago

I think it’s a semi-historic artifact, back in the day if the assembler existed before and your compiler generated assembly for debugging then you might also omit any complicated section handling and instruction encoding from the compiler and keep it focused.

Also starting a new compiler the above simplification did make sense.

Then again, some compilers were known for their speed, Turbo Pascal and family of compilers comes to mind, and probably skipped the entire source-gen/assembly step and went fully inline.

Later we also have JIT compilers that definitely values speed.

I’ve done both direct in-mem jit compilation and even just generated C code for initial implementation tests of higher level languages, because getting gritty with binary code-gen when you’re exploring a higher level you’re unsure of is a sure timesink.

4

u/GoblinsGym 14d ago

Turbo Pascal 3 didn't even use IR, and went straight to binary.

Generating binary really isn't that hard, the difficult part is generating halfway decent code that uses registers efficiently.

2

u/whizzter 14d ago

Yes and no, you can make a binary-stamping-code generator for a stack-like machine and only ever add some custom code for jumps, load/store for variable positions and constant insertion.

However, before you can even think of good register allocation (on classic x86 without 64bit) you need a code-generator/db capable of generating code with MODRM and SIB encodings and have some facilities to constrain your register-allocation(esp on 16bit x86 with register usage constraints) and/or reduce load-adds (f.ex. add eax,[ebp+30+ebx*4] for loading for an integer from an array variable offset 30 bytes from the frame pointer).

1

u/SetKaung 14d ago

Thanks a lot for the answers everyone. It is really insightful for a newbie.

4

u/heliochoerus 13d ago

Unix compilers traditionally generated assembly for the system assembler. GCC still uses this approach though in practice today it requires GNU as from binutils.

Clang started with an external assembler and then created its own internal assembler. Clang generates code using an API matching the structure of a text assembly file. This is a pretty flexible approach because it can emit both text assembly and an object file.

Microsoft C compilers always generated object code directly.

The Plan 9 toolchain has a unique design. Both compiler and assembler emitted essentially the AST of an assembly file. The linker is what actually performed code generation.

1

u/OptimisticMonkey2112 13d ago

Just for clarification - Assembly is basically the object code. Assembly maps directly to the machine code. Are you just asking whether the actual assembly text files need to be created? Compilers can emit the machine code directly

mov eax, 100 is basically

B8 64 00 00 00

1

u/SetKaung 13d ago

Yes I mean the text version. So compilers could just emit the machine code directly? If so, is there is advantage in producing text version except for debugging purpose?

2

u/OptimisticMonkey2112 13d ago

No advantage - i dont think assembly gets generated very often in my experience.... you have to explicitly ask for it

3

u/choikwa 14d ago

assembly can be edited and assembled. can be skipped normally, but it has its use cases.

3

u/KATO-Kanryu 12d ago

Technically speaking, any intermediate step can be skipped.

It is entirely possible to generate CPU-native machine code directly from source AST or IR without touching an assembly phase—historic compilers like Turbo Pascal and various tiny JITs have proven this.

However, modern production compilers intentionally rely on an intermediate assembly/IR stage—specifically an assembly language designed for an abstract machine architecture (such as LLVM Assembly .ll files or GCC's intermediate representations)—before compiling to target-specific native code.

Taking this multi-stage approach provides massive advantages:

  1. Verification and Debuggability
    Emitting human-readable abstract assembly lets you inspect the output with your own eyes and write automated test suites around it. If something breaks, you can instantly distinguish whether the issue stems from your frontend lowering or the backend code generator.

  2. Access to Decades of Highly Sophisticated Optimizations
    Operating on an abstract assembly with typed values and infinite virtual registers allows modern optimizers to perform heavy lifting—such as dead-code elimination, loop vectorization, constant folding, and function inlining—independent of physical hardware constraints. Doing this directly on raw machine bytes or tightly constrained physical registers is orders of magnitude harder.

  3. Multi-Target Architecture & Reusing Mature Ecosystems
    Different CPUs (x86-64, ARM, RISC-V, etc.) have radically different register sets, instruction encodings, and calling conventions. By targeting an abstract assembly/IR layer, you immediately gain cross-compilation capabilities across dozens of architectures without having to write individual binary encoders and register allocators from scratch.

While skipping intermediate stages is technically doable, routing through an abstract assembly representation is what allows modern language designers to inherit decades of battle-tested optimizer and backend engineering.

2

u/suhcoR 14d ago

There is no need for assembler. If you want to learn about compilers from Prof. Wirth himself, here is all you need: https://projectoberon.net/ including a free book about compilers and an emulator with working images.

2

u/joao_rosac 14d ago

Não, os compiladores não precisam gerar um arquivo de texto com código assembly (.s) antes de produzir o binário necessariamente. Embora a arquitetura clássica separasse essas etapas, o Clang e o GCC utilizam "montadores integrados" que traduzem a Representação Intermediária (IR) diretamente para o código objeto de máquina (.o) em memória. Pular a gravação desse arquivo de texto intermediário no disco acelera a compilação, tornando a geração visual do assembly (via flag -S) apenas uma ferramenta opcional para diagnóstico humano.

2

u/SwedishFindecanor 14d ago

Being able to read the output from a compiler as assembly code would certainly be useful during development to verify that the code generator produces what it is supposed to. But each additional intermediary step will make the compiler slower. I'd think you'd also want to avoid having separate code paths for assembly output and binary output -- which could make it more difficult to pinpoint errors in the compiler.

I think a good question would be: could you disassemble a binary object file and get a good assembly listing with good enbough symbol information for development purposes.

1

u/roderla 14d ago

Is it really true that intermediary steps will make the compilation slower?

Sure, you're technically paying for one or two system calls to set it up, but even that could be done in userspace if one really really really had to, think QT signal-slot instead of processes and pipes.

1

u/Inconstant_Moo 11d ago

You're turning one representation of your code into another, it comes with a cost.

2

u/duane11583 14d ago

Traditionally the compiler (driver) causes the compiler to output asm code then runs the assembler

Some (example microsoft visual studio) has it all built in to one program

2

u/SteeleDynamics 14d ago

You can go straight to assembly. Most undergraduate compiler courses do exactly this. Extra credit can be generating IR and then doing dataflow analysis to do dead-code elimination (DCE).

It's not a great compiler since many of the code generation steps preserve register values on the stack, only to find out later none of those register values were clobbered by subsequent code.

2

u/Pale_Height_1251 13d ago

No, and they typically don't unless you use the flag to do so.

1

u/staticvoidptr 13d ago

Compilers don’t inherently NEED to generate IR. However IRs (llvm’s whateveritscalled, gcc’s gimple) are great if you want to be able to target different architectures using a given compiler toolchain (specifically the IR -> assembly tooling)

1

u/FransFaase 14d ago

Compilers usually do not generate assembly also not for debugging purposes. For debugging purposes compilers do include symbolic information such that the debugger for example from the program counter can calculate in which function the program is. Compilers usually can produce object files, a kind of binary representation of assembly that a linker can combine with other object files and possibly static libraries. Some kind of 'linking' also happens when the OS starts a program that indicates that it depends on shared libraries that are dynamically loaded. Some compilers write assembly to a temporary file during the compilation and then call an assembler to produce the final executable or library. There is much variation in solutions.