Timeline
Timeline
2025-11-23
- init
This article introduces the core principles and working mechanisms of the dynamic binary translation engine TCG (Tiny Code Generator) in QEMU, discussing in detail the differences between Guest and Target architectures, the role of TCG IR, as well as the process of translating basic blocks into translation blocks, variable classification, caching mechanisms, and performance optimization techniques such as direct block chaining.
Environment
1 | wget https://download.qemu.org/qemu-10.1.2.tar.xz |
Create .clangd
1 | CompileFlags: |
gdb
1 | gdb -args ./build/qemu-system-riscv64 -M virt -device edu,id=edu1 -nographic |
QEMU supports multiple accelerators, which can be broadly divided into two types: instruction simulation technology (TCG) and virtualization technology (KVM, HVF), etc.
Common translation techniques:
- Interpreter: Parses and executes one Guest instruction at a time, repeating the cycle.
- Static Binary Translation: Translates before program execution. No translation overhead at runtime, but limited optimization scope.
- Dynamic Binary Translation: Translates dynamically during program execution. Typically translates based on program traces, not the entire code, and can deeply optimize hot code.
TCG (Tiny Code Generator) was originally a C language compiler backend, later evolving into QEMU’sbinary dynamic compilation (translation) engine。
Target & Guest
Guest (virtual machine/simulated architecture)
Refers tothe CPU architecture simulated by QEMU, which is the CPU type of the system you are running.
For example:
riscv64(RISC-V 64-bit)aarch64(ARM 64-bit)i386(x86 32-bit)
Specify the Guest architecture when starting QEMU, for example:
1
qemu-system-riscv64 -machine virt -kernel kernel.elf
The Guest determines the instruction set, registers, etc., emulated by QEMU.
TCG Target
- TCG (Tiny Code Generator) is QEMU’s dynamic translator, used to convertGuest instructionsintonative instructions executable by the Host CPU。
- The TCG Target isthe architecture of the host CPU, which determines on which CPU the machine code generated by QEMU will run.
- For example:
- Host =
x86_64 - Guest =
riscv64 - TCG Target =
x86_64(because QEMU ultimately generates x86_64 machine code to execute on the host)
- Host =
TCR Translation
TCG IR
Similar to LLVM, QEMU also defines its own IR, with the process as follows:
1 | +---------------+ +----------------+ +---------------+ |
Translate guest instructions intoQEMU IR, this is the frontend; the backend translatesQEMU IRinto host instructions
Advantages:
- Good extensibility: supporting a new frontend (Guest) only requires implementing source -> IR;
- Easy to pipeline: similar to LLVM, various passes can be introduced to optimize different stages.
Disadvantages:
- Generally lower performance (relatively speaking)
TCG Translation Process
Basic Process
TCG’s binary translation uses Basic Blocks as the fundamental unit, and the translation output is a Translation Block.
Rules for dividing Basic Blocks:Branch instruction;Privileged instruction/exception;Code segment crossing page。
- Guest PC points to a Basic Block
- **Basic Block(BB)**is a contiguous sequence of instructions without branches.
- For example, a sequence of sequentially executed instructions in x86/RISC-V.
- Check TB Cache, translation cache
- TB(Translated Block)is the result of a Basic Block being translated by DBT intohost executable code.
- DBT maintains a hash table or cache, with the key typically being the Guest PC.
- Hit TB Cache (Y)
- If the TB corresponding to the Guest PC has already been generated, directly jump toExec TBfor execution.
- No need to translate again, improving efficiency.
- TB Cache Miss (N)
- Calltranslationmodule to convert the Guest’s Basic Block into a Translation Block (TB).
- After translation, the TB is saved to the cache for fast execution next time.
- Direct Block Chaining
- Each TB storesits corresponding Guest PC, and usually also recordsthe next Guest PC(the jump target of the last instruction)
- After executing a TB, if the next Guest PC already has a TB, it directly jumps to execute the next TB without returning to
Check TB Cache。 - This is achieved by generating a direct jump instruction at the end of the TB.
- This optimizes execution efficiency by avoiding returning to the interpreter loop each time, especially for loop-intensive code.
1 | +---------------+ |
- Once a Basic Block is converted to a TB by DBT, the next time the same Basic Block is executed, the TB can be directly fetched from the cache for execution without needing conversion again:
Translation Block
TCG’s binary translation uses code blocks (Basic Blocks) as the basic unit, and the product of translation is a Translation Block.
TCG has three types of variables: temporary, local temporary, and global.
- globalGeneral system registers are global variables, which are further divided into two types: register and memory. Memory typically defines registers within the CPU.
For the memory type, during backend translation, the guest CPU register values are first loaded into host registers, and after computation, they are stored back into the guest CPU structure. The simulation process involves memory access operations.
For the register type of TCGv, during backend translation, it is directly mapped to host registers, allowing direct access each time. It is typically used to store the pointer to the guest CPU environment.
temporaryThe variable’s lifetime is only within a single BB.
local temporaryThe variable’s lifetime is within a TB and can span across BBs.
Rules for dividing Basic Blocks:Branch instructions;Privileged instructions/exceptions;Code segment crossing a page。
A BB starts from the end of the previous BB or from a set_label instruction. A BB ends with a branch instruction (brcond_xxx)、goto_tb and exit_tb ends,
Prologue:
Save Guest CPU state (registers, flags, etc.) to host registers or memory
Set up execution environment
Epilogue:
Restore Guest CPU state
Jump back to Dispatcher or directly to the next TB
1 | +---------------------+ |
Direct block chaining
Taking the x86_64 platform as an example, each context switch requires executing about 20 instructions (instructions also perform memory reads and writes), so one of the optimization measures of DBT is toreduce context switches, achieving direct linking between TBs: (for example, direct jump instructions can directly connect two TB blocks, but indirect jump instructions cannot because they depend on runtime computation)
1 | 1) +---------------------+ |
PS: The Guest instructions corresponding to two chained TBs need to be on the same Guest page.
Code Buffer
- code_bufferis the contiguous memory area on the host machine where TCG (Tiny Code Generator) storestranslated Guest instructions (TB).
- All**Translation Block(TB)**are generated, stored, and executed in this buffer.
- SubsequentPrologue / TB.code / Epilogueare all located in code_buffer.
1 | code_buffer = mmap() |
tcg_code_gen_epilogue:
- Generate the epilogue part of TB and update
TB.tc.ptr
tcg_qemu_tb_exec:
- Obtain TB.code according to TB.struct and jump to TB.code
TB.struct:
- Record the meta-information of TB, for example:
- Guest PC (entry address)
- TB length
- Pointer to TB.code
- Next TB (Direct Block Chaining)
TB.code:
- Host machine code generated after TCG translation
- Execution starts here in practice
Return to the QEMU world after epilogue execution
In the early stage of QEMU startup, a function called tcg is executed_init_machine, completing the allocation and initialization of code_buffer.
accel/tcg/tcg-all.c
1 | static int tcg_init_machine(AccelState *as, MachineState *ms) |
- All subsequent code translation and execution work revolves around code_buffer
- The backend management work of TCGContext is also carried out around code_buffer
DecodeTree
1 | +---------------+ +----------------+ |
Decodetree is a mechanism proposed by Bastian Koppelmann in 2017 when porting RISC-V QEMU. It was proposed mainly because previous instruction decoders (e.g., ARM) used a bunch of switch-case statements for judgment, which were not only hard to read but also difficult to maintain.
Therefore, Bastian Koppelmann proposed the Decodetree mechanism, where developers only need todefine the format of each instruction using Decodetree’s syntax, and then Decodetree can dynamically generate the corresponding instruction decoder.c containing switch-case statements.
Decodetree is essentially a Python script that takes a file defining the instruction format of the architecture as input and outputs the instruction decoder source code file.
1 | +-----------+ +-----------+ +-------------------+ |
- input: Instruction encoding format file defined by the architecture
- output: Source code of the instruction decoder (participates in QEMU compilation)
Decodetree Syntax
Decodetree’s syntax is divided into four parts: Fields, Argument Sets, Formats, and Patterns.
- Fields, describing fields such as registers and immediates in the instruction encoding;
- Argument Sets, describing the storage of values extracted from each field in the instruction;
- Formats, describing the instruction format and generating the corresponding decode function;
- Pattern, describing the decode method of an instruction.
Decodetree Field
Field defines how to extract the values of each field (e.g., rd, rs1, rs2, imm) from an instruction.
1 | field_def := '%' identifier ( unnamed_field )* ( !function=identifier )? |
- %identifier
- The name of the field is defined by the developer.
- Example:
%rd,%rs1,%imm
- unnamed_field
- Specifies the bit position of the field in the instruction
- Format:
high_bit : low_bit - Optional
sindicates sign extension - Example:
7:5indicates instruction bits[7:5] - Example:
31:s20indicates bits[31:20] and requires sign extension
- !function=identifier
- After extracting the field value from the instruction, call a function for further processing
- For example, immediates require sign extension, bit concatenation, or address translation
1 | 以 RISC-V 的 U-type 指令为例: |
Decodetree Argument Sets
Argument Set DefinitionUsed to store the values of each field extracted from the instruction。
1 | args_def := '&' identifier ( args_elt )+ ( !extern )? |
- &identifier
- Define a name for the Argument Set, customized by the developer
- Example:
®s,&loadstore
- args_elt
- Elements contained in the Argument Set, usually previously defined fields
- Example:
rd,rs1,imm - Meaning: Save the extracted values of these fields into this Argument Set
- !extern
- Indicates that this Argument Set has already been defined in another Decoder; if this field is present, the corresponding argument set struct will not be generated again
- Avoid generating duplicate structs
1 | // Example of U-type instruction format |
Decodetree Format
Format Defines the instruction format(such as R, I, S, B, U, J-type in RISC-V),and will generate the corresponding decode function。
1 | fmt_def := '@' identifier ( fmt_elt )+ |
The identifier can be customized by the developer, e.g., opr, opi, etc.
fmt_elt can use the following different syntaxes:
- fixedbit_elt contains one or more
0、1、.、-,each representing 1 bit in the instruction。.indicates that the bit can be represented by 0 or 1.-indicates that the bit is completely ignored.
- Declared using Field syntax, e.g., ra:5, rb:5, lit:8
- fixedbit_elt contains one or more
field_ref has the following two formats (the examples below refer to the Fields defined above):
'%' identifier: directly references a defined Field.For example:
%rd, generates:1
a->rd = extract32(insn, 7, 5);
identifier '=' '%' identifier: directlyreferences a defined Field, but renames its corresponding argument name via the first identifier. This method can be used to specify different argument names referencing the same FieldFor example:
my_rd=%rd, generates:1
a->my_rd = extract32(insn, 7, 5)
args_ref specifies the Argument Set passed to the decode function. If args is not specified_For ref, Decodetree will base on field_elt or field_ref to automatically generate an Argument Set. Additionally,A Format can contain at most one args_ref
When fixedbit_elt or field_ref is defined, all bits of the Format must be defined (can be defined viafixedbit_eltor.to define each bit, spaces are ignored).
1 | @opi ...... ra:5 lit:8 1 ....... rc:5 |
- insn[31:26] can be 0 or 1
- insn[25:21] is ra
- insn[20:13] is lit
- insn[12] is fixed to 1
- insn[11:5] can be 0 or 1
- insn[4:0] is rc
This Format will generate the following decode function:
1 | // Since we did not specify args_ref, Decodetree based on the field_definition of elt, automatically generated arg_decode_the Argument Set insn320 |
Take the RISC-V I-type instruction as an example:
1 | 31 20 19 15 14 12 11 7 6 0 |
This example will generate the following decode function:
1 | typedef struct { |
Returning to the previous RISC-V U-type instruction, we can define its format like the I-type instruction:
1 | # Fields: |
will generate the following decode function:
1 | typedef struct { |
Decodetree Pattern
Pattern actually defines the decode method of an instruction. Decodetree will dynamically generate the corresponding switch-case decode decision branches based on the definition of Patterns.
1 | pat_def := identifier ( pat_elt )+ |
- The identifier can be customized by the developer, such as: addl_r, addli, etc.
- pat_elt can use the following different syntaxes:
- fixedbit_elt and fixedbit in Format_have the same definition as elt.
- field_elt is the same as field in Format_The definition of elt is the same.
- field_ref is the same as field in Format_The definition of ref is the same.
- args_ref is the same as args in Format_The definition of ref is the same.
- fmt_ref directly references a previously defined Format.
- const_elt can directly specify the value of a certain argument.
Pattern example:
1 | addl_i 010000 ..... ..... .... 0000000 ..... @opi |
Defines the Pattern for the addl_i instruction, where:
- insn[31:26] is 010000.
- insn[11:5] is 0000000.
- References the @opi Format defined in the Format example.
- Since all bits of the Pattern must beexplicitly defined, therefore @opi must include the format definitions of the remaining insn[25:12] and insn[4:0], otherwise Decodetree will report an error.
Finally addl_the decoder of i will also call trans_addl_i() this translator function
Example
Design a RISC-V arithmetic instruction cube, with the instruction encoding format following R-type, and the semantics as:rd = [rs1] * [rs1] * [rs1]. (Implemented via helper)
1 | 31 25 24 20 19 15 14 12 11 7 6 0 |
The TCG flow of QEMU is:
- Generate TB (Translation Block) from Guest instructions
- Translate Guest instructions to host instruction sequence
Some instructions are complex, or QEMU does not have ready-made TCG opcodes to generate directly. In this case, it is necessary tohelper:
- Function: Implement the instruction semantics using host-executable C code
- TCG translation phase, when encountering this instruction, generate a code snippet that calls the helper
- execution phase, directly execute the helper implementation
Example C code:
1 | static int custom_cube(uintptr_t addr) |
Add instruction decoding for cube in QEMU:
1 | // target/riscv/insn32.decode |
DEF_HELPER_3indicates this is a helper with 3 parametersParameters:
env→ CPU state structure (CPURISCVState *env)tl→ instruction operand (e.g., rd)tl→ instruction operand (e.g., rs1)
Add instruction simulation logic for cube (implemented using helper):
1 | // target/riscv/helper.h |
DEF_HELPER_3 indicates there are three parameters
| Position | Meaning |
|---|---|
cube | helper name → the corresponding function name ishelper_cube |
void | Return type of helper |
env | First parameter type:CPURISCVState *env(i.e., CPU state) |
tl | Second parameter type:target_ulong |
tl | Third parameter type:target_ulong |
env->gpr[rs1]→ Read the value of rs1 from the register file
cpu_ldq_mmu(...)→ Simulate loading the value at the address of rs1 from memory
env->gpr[rd] = val * val * val;→ Perform cube calculation and write the result to rd
arg_cubeis generatedArgument Sets
Write a simple example program:
1 | int main(void) { |
Compile and run the test:
1 | $ riscv64-linux-musl-gcc main.c -o cube_demo --static |
TCG IR
Earlier we discussed how to use QEMU’s helper functions to simulate instruction functionality, but generally, helpers are mainly used when IR implementation is inconvenient.
For better performance, it is recommended to use IR for implementation.
The frontend of TCG is responsible for converting instructions of the target architecture into TCG ops, while the backend of TCG is responsible for converting TCG ops into instructions of the target architecture.
Here we mainly focus on the frontend of TCG and discuss the usage of common TCG ops.
Recommended reading:
The basic format of a TCG op is as follows:
1 | tcg_gen_<op>[i]_<reg_size>(TCGv<reg_size> args, ...) |
Registers
1 | TCGv reg = tcg_global_mem_new(TCG_AREG0, offsetof(CPUState, reg), "reg"); |
Temporaries
1 | // Create a new temporary register |
labels
1 | // Create a new label |
Ops
Operating on a single register:
1 | // ret = arg1 |
Operating on two registers:
1 | // ret = arg1 + arg2 |
Bit Operations
Logic operations on a single register:
1 | // ret = !arg1 |
Logic operations on two registers:
1 | // ret = arg1 & arg2 |
Shift
1 | // ret = arg1 >> arg2 /* Sign fills vacant bits */ |
Rotation
1 | // ret = arg1 rotl arg2 |
Byte
1 | // ret = ((arg1 & 0xff00) >> 8) // ((arg1 & 0xff) << 8) |
Load/Store
These are for moving data between registers and arbitrary host memory.
Typically used for funky CPU state that is not represented by dedicated registers already and thus infrequently used.
These are not for accessing the target’s memory space;
see the QEMU_XX helpers below for that.
1 | // Load an 8bit quantity from host memory and sign extend |
These are for moving data between registers and arbitrary target memory.
The address to load/store via is always the second argument while the first argument is always the value to be loaded/stored.
The third argument (memory index) only makes sense for system targets; user targets will simply specify 0 all the time.
1 | // ret = *(int8_t *)addr |
Code Flow
1 | // if (arg1 <condition> arg2) goto label |
Example
We use IR to implement the cube instruction:
1 | // target/riscv/insn_trans/trans_rvi.c.inc |
Print IR
1 | $ ./build/qemu-system-riscv64 -M virt -d in_asm,op,out_asm -nographic -D cpu.log |
- -d indicates outputting logs
- in_asm indicates input assembly
- op indicates intermediate IR
- out_asm indicates output assembly
- -D indicates output to file or terminal
Reference:

