Inline Assembly
Twinkies supports inline assembly, allowing you to embed platform-specific assembly code directly in your programs. This is useful for low-level operations, performance-critical code, or accessing CPU features.
Inline Assembly Syntax
Inline assembly uses GCC-style syntax:
asm {
"assembly code"
: outputs
: inputs
: clobbers
};
Basic Example
func get_value() -> int {
let result: int;
asm {
"mov $42, %%rax\n\t"
"mov %%rax, %0"
: "=r" (result)
:
: "rax"
};
return result;
}
Volatile Assembly
Use volatile to prevent the compiler from optimizing away the assembly:
func volatile_example() -> int {
let value: int = 0;
asm volatile {
"mov $100, %0"
: "=r" (value)
:
: "memory"
};
return value;
}
The volatile modifier is useful for:
- Memory barriers
- CPUID instructions
- Operations with side effects
- Timing operations
Output Operands
Output operands specify variables that receive values from assembly:
func simple_output() -> int {
let result: int;
asm {
"mov $42, %0"
: "=r" (result) // =r means register output
:
:
};
return result;
}
Output Constraints
=r- Register output (any general-purpose register)=a- EAX/RAX register=b- EBX/RBX register=c- ECX/RCX register=d- EDX/RDX register
Input Operands
Input operands pass values from Twinkies variables into assembly:
func add_with_asm(a: int, b: int) -> int {
let result: int;
asm {
"mov %1, %0\n\t" // Move a to result
"addq %2, %0" // Add b to result
: "=r" (result) // Output: result
: "r" (a), "r" (b) // Inputs: a and b
: "cc" // Clobbers: condition codes
};
return result;
}
Input Constraints
r- Register input (any general-purpose register)a- EAX/RAX registerb- EBX/RBX registerc- ECX/RCX registerd- EDX/RDX registeri- Immediate integer constantm- Memory operand
Clobber Lists
Clobber lists declare registers or memory that the assembly modifies:
func example_with_clobbers() -> int {
let result: int;
asm {
"cpuid"
: "=a" (result)
: "a" (1)
: "ebx", "ecx", "edx", "cc" // Clobbers: these registers are modified
};
return result;
}
Common Clobbers
"cc"- Condition codes (flags register)"memory"- Memory is modified"rax","rbx", etc. - Specific registers are modified
Common Inline Assembly Patterns
CPUID Instruction
func get_cpu_info() -> int {
let eax: int;
asm {
"cpuid"
: "=a" (eax)
: "a" (1)
: "ebx", "ecx", "edx", "cc"
};
return eax;
}
RDTSC (Read Time Stamp Counter)
func read_timestamp() -> int64 {
let low: int;
let high: int;
asm {
"rdtsc"
: "=a" (low), "=d" (high)
:
: "cc"
};
// Combine high and low into 64-bit value
let result: int64 = low;
return result;
}
Bit Manipulation
func set_bit(value: int, bit: int) -> int {
let result: int = value;
asm {
"bts %1, %0" // Bit Test and Set
: "+r" (result)
: "r" (bit)
: "cc"
};
return result;
}
Arithmetic Operations
func multiply_asm(a: int, b: int) -> int {
let result: int;
asm {
"mov %1, %0\n\t"
"imulq %2, %0"
: "=r" (result)
: "r" (a), "r" (b)
: "cc"
};
return result;
}
Comparisons
func compare_asm(a: int, b: int) -> int {
let result: int;
asm {
"cmpq %2, %1\n\t"
"setg %b0" // Set if greater (byte register)
: "=r" (result)
: "r" (a), "r" (b)
: "cc"
};
return result;
}
Advanced Examples
CPU Name Extraction
// Get CPU name using CPUID leaves 0x80000002-0x80000004
func get_cpu_name_leaf1_eax() -> int {
let eax: int;
let leaf: int = 2147483650; // 0x80000002
asm {
"cpuid"
: "=a" (eax)
: "a" (leaf)
: "ebx", "ecx", "edx", "cc"
};
return eax;
}
String Length with Assembly
func string_length_asm(str: int) -> int {
let length: int;
asm {
"mov %1, %%rdi\n\t"
"mov $0xFFFFFFFFFFFFFFFF, %%rcx\n\t"
"xor %%rax, %%rax\n\t"
"repne scasb\n\t"
"not %%rcx\n\t"
"dec %%rcx\n\t"
"mov %%rcx, %0"
: "=r" (length)
: "r" (str)
: "rax", "rcx", "rdi", "cc"
};
return length;
}
Memory Operations
func memory_operation(ptr: int) -> int {
let value: int;
asm {
"mov (%1), %0" // Load from memory address
: "=r" (value)
: "r" (ptr)
: "memory"
};
return value;
}
Register Constraints Reference
x86-64 Registers
| Constraint | Register | Description |
|---|---|---|
a | RAX/EAX | Accumulator |
b | RBX/EBX | Base |
c | RCX/ECX | Counter |
d | RDX/EDX | Data |
r | Any GPR | Any general-purpose register |
i | Immediate | Integer constant |
m | Memory | Memory operand |
Modifiers
=- Write-only output+- Read-write operand&- Early clobber%k0- 32-bit version of 64-bit register%b0- 8-bit (byte) version of register
Best Practices
1. Use Volatile for Side Effects
// Good: Volatile prevents optimization
asm volatile {
"cpuid"
: "=a" (eax)
: "a" (1)
: "ebx", "ecx", "edx", "cc"
};
2. Declare All Clobbers
Always list all registers and memory that are modified:
// Good: All clobbers declared
asm {
"cpuid"
: "=a" (eax)
: "a" (1)
: "ebx", "ecx", "edx", "cc"
};
// Bad: Missing clobbers
asm {
"cpuid"
: "=a" (eax)
: "a" (1)
: // Missing clobbers!
};
3. Use Appropriate Constraints
Choose constraints that match your needs:
// Good: Specific register when needed
asm {
"cpuid"
: "=a" (eax), "=b" (ebx)
: "a" (1)
: "ecx", "edx", "cc"
};
// Also good: Let compiler choose when flexible
asm {
"mov %1, %0"
: "=r" (result)
: "r" (value)
:
};
4. Document Assembly Code
Comment complex assembly operations:
// CPUID with EAX=1 returns processor signature in EAX
// and feature flags in EBX, ECX, EDX
func get_cpu_info() -> int {
let eax: int;
asm {
"cpuid"
: "=a" (eax)
: "a" (1)
: "ebx", "ecx", "edx", "cc"
};
return eax;
}
Limitations
- Platform-Specific - Assembly code is platform-specific (x86-64)
- No Portability - Code won't work on other architectures
- Compiler Dependent - Syntax is GCC-specific
- Error-Prone - Easy to make mistakes with register usage
- No Type Safety - Assembly bypasses type checking
Safety Considerations
- Register Conflicts - Be careful not to conflict with compiler register usage
- Stack Alignment - Maintain proper stack alignment
- Calling Conventions - Follow platform calling conventions
- Memory Safety - Ensure memory accesses are valid
- Side Effects - Be aware of all side effects
When to Use Inline Assembly
Use inline assembly when:
- You need access to CPU-specific features (CPUID, RDTSC)
- Performance is critical and C code isn't fast enough
- You need precise control over register usage
- You're implementing low-level system code
Avoid inline assembly when:
- C code is sufficient
- Portability is important
- The operation can be expressed in Twinkies/C
Complete Example
// Comprehensive inline assembly example
func simple_asm() -> int {
let result: int;
asm {
"mov $42, %%rax\n\t"
"mov %%rax, %0"
: "=r" (result)
:
: "rax"
};
return result;
}
func add_with_asm(a: int, b: int) -> int {
let result: int;
asm {
"mov %1, %0\n\t"
"addq %2, %0"
: "=r" (result)
: "r" (a), "r" (b)
: "cc"
};
return result;
}
func get_cpu_info() -> int {
let eax: int;
asm {
"cpuid"
: "=a" (eax)
: "a" (1)
: "ebx", "ecx", "edx", "cc"
};
return eax;
}
func main() -> int {
print(simple_asm()); // 42
print(add_with_asm(10, 20)); // 30
print(get_cpu_info()); // CPU signature
return 0;
}
Next Steps
- Learn about FFI for calling external functions
- Read Functions for function syntax
- Check Examples Guide for more inline assembly examples
