Since version 0.9.16, TinyCC integrates its own assembler supporting GNU gas-like syntax. The assembler handles both standalone assembly files and inline assembly within C code.
TCC’s assembler enables you to write low-level code without requiring an external assembler, maintaining TCC’s self-reliant philosophy.
Overview
TinyCC Assembler is used to handle:
.S files : C preprocessed assembler source
.s files : Raw assembler source (no preprocessing)
Inline assembly : Using the asm or __asm__ keyword in C code
You can deactivate assembler support for a smaller TCC executable. The C compiler does not rely on the assembler.
Assembly syntax
The TCC assembler supports most of the GNU as (gas) syntax with a few differences.
# This is a comment
// C++ style comments also work
/* C style comments work too */
mov % eax , % ebx # Move ebx to eax
Identifiers follow C rules:
Cannot use . or $ in identifiers (unlike gas)
Must start with a letter or underscore
Can contain letters, numbers, and underscores
my_label: # Valid
_start: # Valid
loop . 1: # Invalid (contains .)
temp$var: # Invalid (contains $)
mov $42 , % eax # Decimal
mov $0x2A, % eax # Hexadecimal
mov $052 , % eax # Octal
Only 32-bit integer numbers are supported in expressions.
Expressions
Operators
mov $+ 10 , % eax # Positive (unary +)
mov $- 10 , % eax # Negative (unary -)
mov $~ 0xFF , % eax # Bitwise NOT
Binary operators (by precedence)
Highest precedence: mov $( 4 * 5 ), % eax # Multiplication
mov $( 20 / 4 ), % eax # Division
mov $( 17 % 5 ), % eax # Modulo
Medium precedence: mov $( 0xFF & 0x0F ), % eax # Bitwise AND
mov $( 0xF0 | 0x0F ), % eax # Bitwise OR
mov $( 0xFF ^ 0x0F ), % eax # Bitwise XOR
Lowest precedence: mov $(label + 4 ), % eax # Addition
mov $(label2 - label1), % eax # Subtraction
Label arithmetic
All operators accept absolute values except + and -. These can add offsets to labels or compute differences between labels in the same section.
.text
start:
nop
end:
mov $(end - start), % eax # Size of code between labels
mov $(buffer + 16 ), % edx # Address offset
Labels
Named labels
.globl main
main:
push % ebp
mov % esp , % ebp
# Function code
leave
ret
local_label:
# Local code
ret
All labels are considered local by default, except undefined ones.
Numeric labels
Numeric labels can be defined multiple times and referenced with b (backward) or f (forward):
1 :
cmp % eax , % ebx
jne 1f # Jump forward to next "1:" label
inc % eax
jmp 1b # Jump backward to previous "1:" label
1 :
ret
Numeric labels are useful for local jumps within a function without polluting the namespace.
Directives
All directives are preceded by a . character.
Data definition
Byte and word data
Integer data
String data
. byte 0x10 , 0x20 , 0x30 # Define bytes
. word 0x1234 , 0x5678 # Define words (2 bytes)
.short 100 , 200 # Define shorts (2 bytes)
Alignment and spacing
Align to a power-of-two boundary: .align 16 # Align to 16-byte boundary
.align 8 , 0x90 # Align to 8 bytes, fill with 0x90 (NOP)
.balign 4 # Same as .align 4
Align using power-of-two exponent: .p2align 3 # Align to 2^3 = 8 bytes
.p2align 4 , 0x90 # Align to 16 bytes, fill with 0x90
Reserve space: .skip 10 # Skip 10 bytes (filled with 0)
.skip 16 , 0xFF # Skip 16 bytes filled with 0xFF
.space 100 # Reserve 100 bytes
Repeat a pattern: .fill 10 , 4 , 0x12345678 # Repeat 0x12345678 (4 bytes) 10 times
.fill 5 , 1 , 0xAA # Repeat 0xAA (1 byte) 5 times
Section control
Standard sections
Custom sections
Section with flags
Section stack
.text # Switch to code section
.data # Switch to initialized data section
.bss # Switch to uninitialized data section
Symbol visibility
.globl symbol_name # Make symbol visible externally
.global main # Same as .globl
.weak weak_symbol # Declare weak symbol
.hidden int_func # Hide symbol (not exported)
Advanced directives
.org - Set location counter
.org 0x1000 # Set address to 0x1000
# Code at absolute address 0x1000
The .org directive can only move forward and must stay within reasonable bounds (0 to 0x100000).
.rept and .endr - Repeat block
.rept 4
nop # Repeat 4 NOPs
.endr
.rept 3
push % eax
call function
pop % eax
.endr
.set BUFFER_SIZE, 1024
.set MAX_ITEMS, 100
mov $BUFFER_SIZE, % eax
X86 assembly
All x86 opcodes are supported with ATT syntax (source then destination operand order).
Syntax differences
ATT syntax (TCC)
Intel syntax (not supported)
mov % eax , % ebx # Move eax to ebx (source, dest)
add $10 , % eax # Add 10 to eax
mov (% ebx ), % eax # Load from address in ebx
Size suffixes
movb $0x42, % al # Move byte
movw $0x1234, % ax # Move word
movl $0x12345678, % eax # Move long (32-bit)
# TCC can infer size from operands:
mov $10 , % eax # Inferred as movl
mov % al , % bl # Inferred as movb
Addressing modes
Register direct
Immediate
Register indirect
Base + displacement
Base + index
Complex (base + index*scale + disp)
Inline assembly in C
TCC supports GCC-style inline assembly with full constraint syntax.
Basic inline assembly
int main () {
__asm__ ( " nop " ); // Single instruction
__asm__ ( " mov $1, %eax \n\t " // Multiple instructions
" xor %ebx, %ebx " );
return 0 ;
}
Extended inline assembly
static inline int atomic_add ( int * ptr , int value ) {
int result;
__asm__ _ _volatile__ (
"lock; xaddl % 0, % 1"
: "=r" (result), "+m" ( * ptr)
: "0" (value)
: "memory"
);
return result;
}
Constraint examples
Output operands
Input operands
Named operands (GCC 3.x)
int result;
__asm__ ( " movl %%eax, %0 " : " =r " (result));
// "=r" means write-only register
// "+r" means read-write register
TCC generates the assembly code directly without intermediate files and supports GCC 3.x named operands for better readability.
Limitations
Currently not supported:
SSE instructions (MMX opcodes are supported)
Intel syntax (only ATT syntax)
64-bit operand sizes in 32-bit mode
Some advanced gas directives
The assembler is available on:
i386 : Full support (Linux and Windows)
x86_64 : Full support (Linux and Windows)
ARM : Supported
ARM64 : Supported
RISC-V : Supported
For cross-platform code, use C with inline assembly only when necessary for performance-critical or hardware-specific operations.