Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MIR to Tuffy IR Translation

This document describes how Rust MIR (Mid-level Intermediate Representation) operations are translated to Tuffy IR instructions in the rustc_codegen_tuffy backend.

Operands

MIR operands represent values used in operations. There are three kinds:

MIR OperandTranslation
Copy(place)Read value from place (may generate load if stack-allocated)
Move(place)Read value from place (same as Copy at IR level)
Constant(const)Translate to iconst, bconst, or symbol_addr

Copy vs Move: The distinction between Copy and Move is enforced by MIR’s borrow checker. At the IR level, both translate to reading the value from the place. For register-allocated locals, this is a direct value reference. For stack-allocated locals, this generates a load instruction.

Constants: Constant operands are translated based on their type:

  • Integer/bool literals → iconst / bconst
  • Function pointers → symbol_addr
  • Static references → symbol_addr + optional offset

Binary Operations

Integer Arithmetic

MIR BinOpTuffy IRResult AnnotationExtension
Addadd:dontcare(N)sext (signed) / zext (unsigned)
Subsub:dontcare(N)sext (signed) / zext (unsigned)
Mulmul:dontcare(N)sext (signed) / zext (unsigned)
Divdiv:sN / :uNNone
Remrem:sN / :uNNone
AddUncheckedadd:sN / :uNNone
SubUncheckedsub:sN / :uNNone
MulUncheckedmul:sN / :uNNone

Wrapping arithmetic (Add, Sub, Mul): The IR instruction produces a result with :dontcare(N) annotation, then sext (for signed types) or zext (for unsigned types) is inserted to correctly interpret the low N bits. This prevents high bits from 64-bit operations bleeding through for sub-64-bit types.

Unchecked arithmetic: MIR guarantees no overflow, so the result uses :sN or :uN annotation directly without extension. The optimizer can assume the result is in range.

Floating Point Arithmetic

MIR BinOpTuffy IR
Addfadd
Subfsub
Mulfmul
Divfdiv
Remfrem

Note: Floating point operations do not use annotations or extensions.

Overflow Detection

MIR BinOpTuffy IR (Signed)Tuffy IR (Unsigned)
AddWithOverflowsadd_overflowuadd_overflow
SubWithOverflowssub_overflowusub_overflow
MulWithOverflowsmul_overflowumul_overflow

These return two values: the wrapped result and a boolean overflow flag.

Bitwise Operations

MIR BinOpTuffy IRResult AnnotationNotes
BitAndand:sN / :uNDirect passthrough
BitOror:sN / :uNDirect passthrough
BitXorxor:sN / :uNDirect passthrough
Shlshl:sN / :uNShift amount masked to % bit_width
ShlUncheckedshl:sN / :uNShift amount masked to % bit_width
Shrshr:sN / :uNShift amount masked to % bit_width
ShrUncheckedshr:sN / :uNShift amount masked to % bit_width

Note: Bitwise operations use the result annotation directly without extension. Shift operations mask the shift amount to value & (bit_width - 1) to match Rust semantics.

Comparison Operations

MIR BinOpTuffy IR (Integer)Tuffy IR (Float)
Eqicmp.eqfcmp.oeq
Neicmp.nefcmp.une
Lticmp.ltfcmp.olt
Leicmp.lefcmp.ole
Gticmp.gtfcmp.ogt
Geicmp.gefcmp.oge
CmpSpecial handling*N/A

Note: Cmp (three-way comparison) is lowered to a sequence of comparisons and selects that produce -1, 0, or 1.

Pointer Operations

MIR BinOpTuffy IR
Offsetptradd

Unary Operations

MIR UnOpTuffy IR
Notxor v, -1
Negsub 0, v (int) / fneg v (float)

Note: Bitwise NOT is currently emulated using XOR with -1. A dedicated not instruction may be added in the future.

Statements

MIR statements represent actions within a basic block.

MIR StatementTranslation
Assign(place, rvalue)Translate rvalue, store result to place
StorageLive(local)No-op (ignored)
StorageDead(local)No-op (ignored)
SetDiscriminant { place, variant }store tag value to discriminant field
NopNo-op
Intrinsic(intrinsic)See Intrinsics section

StorageLive/StorageDead: These are lifetime markers used by MIR for stack slot reuse and borrow checking. At the IR level, they are ignored since Tuffy IR uses explicit stack slots and does not perform lifetime-based optimizations.

SetDiscriminant: Writes the discriminant (tag) value for an enum variant. For single-variant enums, this is a no-op. For multi-variant enums:

  • Computes the tag value based on the variant index and tag encoding (Direct or Niche)
  • Calculates the address of the discriminant field (base + offset)
  • Emits a store instruction to write the tag value

Intrinsics

Bit Manipulation

MIR IntrinsicTuffy IR
ctpopcount_ones
ctlz / ctlz_nonzeroclz
cttz / cttz_nonzeroctz
bswapbswap
bitreversebitreverse
rotate_leftrotl
rotate_rightrotr

Memory Operations

MIR IntrinsicTuffy IR
copy_nonoverlappingmemcpy
write_bytesmemset
size_oficonst (compile-time constant)
align_oficonst (compile-time constant)

Floating Point

MIR IntrinsicTuffy IR
fabsf32 / fabsf64fabs
copysignf32 / copysignf64copysign
floorf32 / floorf64External call
ceilf32 / ceilf64External call
truncf32 / truncf64External call
sqrtf32 / sqrtf64External call

Note: Transcendental functions (floor, ceil, sqrt, etc.) are not yet implemented as IR instructions and are lowered to external function calls.

Atomic Operations

MIR IntrinsicTuffy IR
atomic_loadload.atomic
atomic_storestore.atomic
atomic_xchgrmw.xchg
atomic_xaddrmw.add
atomic_xsubrmw.sub
atomic_andrmw.and
atomic_orrmw.or
atomic_xorrmw.xor
atomic_cxchg / atomic_cxchgweakcmpxchg
atomic_fencefence

Other Intrinsics

MIR IntrinsicTranslation
black_boxIdentity (no optimization barrier in IR)
assumeNo-op
is_val_statically_knownbconst false
assert_inhabitedNo-op (compile-time check)
assert_zero_validNo-op (compile-time check)

Terminators

MIR TerminatorTuffy IR
Returnret
Gotobr
SwitchIntLowered to brif tree
Callcall
Unreachableunreachable
DropCall to drop glue + br
Assertbrif + trap on failure
InlineAsmPattern-based translation (see below)
UnwindResumecall _Unwind_Resume + unreachable
UnwindTerminatecall panic_cannot_unwind + trap

SwitchInt: Multi-way branch lowered to a binary decision tree using nested brif instructions.

InlineAsm: Inline assembly is handled via pattern recognition, not general-purpose execution. The select_unpredictable pattern (cmovnz/cmovne + cmovz/cmove) is translated to icmp + select. Other patterns use identity copies or zero-initialization of outputs.

UnwindResume: Loads the exception pointer from the landing-pad slot, calls _Unwind_Resume to resume stack unwinding, then emits unreachable.

UnwindTerminate: Resolves and calls core::panicking::panic_cannot_unwind, then emits trap as a safety backstop.

Type Conversions

MIR CastTuffy IR
Integer extension (signed)sext
Integer extension (unsigned)zext
Integer truncationAnnotation change
Float to signed intfp_to_si (saturating)
Float to unsigned intfp_to_ui (saturating)
Signed int to floatsi_to_fp
Unsigned int to floatui_to_fp
Float to float (widen/narrow)fp_convert
Pointer to intptrtoint
Int to pointerinttoptr
FnPtr to ptrsymbol_addr
Ptr to ptrBitwise move
TransmuteVia temporary stack_slot + store/load

Float to int: Follows Rust’s saturating semantics — NaN converts to 0, out-of-range values clamp to the target type’s MIN/MAX. For 128-bit targets, converts to 64-bit first then extends.

Int to float: Narrow integers (< 64 bits) are sign/zero-extended before conversion to ensure correct results.

Transmute: Reinterprets bit patterns via a temporary stack slot. Stores the source value, then loads it as the target type.

Aggregate Construction

Rvalue::Aggregate constructs tuples, structs, closures, and enums.

Strategy: Allocates a stack_slot with the aggregate’s size, then stores each field at its computed offset via ptradd + store.

  • Tuples/Structs: Direct field-by-field storage at layout-computed offsets.
  • Enums: After storing variant fields, writes the discriminant via write_enum_tag() (a store to the tag field using Direct or Niche encoding).
  • Closures/Coroutines: Stores captured variables; initializes the state discriminant to 0.
  • Fat pointers within aggregates: Stores data pointer at offset 0 and metadata at offset +8.

Discriminant Reads

Rvalue::Discriminant reads an enum’s discriminant value.

Three cases based on enum layout:

  • Single variant (Variants::Single): Returns a constant (iconst of the discriminant value).
  • Direct tag (TagEncoding::Direct): Loads the tag field via ptradd (if offset ≠ 0) + load.
  • Niche encoding (TagEncoding::Niche): Loads the niche field, then decodes via arithmetic (sub for relative index, icmp for range check, select for final discriminant). Option-like enums (1 niche variant at offset 0) use a simplified icmp.eq + select.

Not Yet Implemented

The following MIR operations are not yet translated to Tuffy IR:

  • Tail calls: TerminatorKind::TailCall
  • Coroutines: Yield, CoroutineDrop

These operations either require additional IR support or are Rust-specific features that don’t map cleanly to a low-level IR.