# Advanced inline assembly usage in Linux kernel This document covers advanced inline assembly usage in the Linux kernel. The focus is asm-goto (as per project goal), but it extends to a few other asm features used in Linux kernel but it not provided by stable Rust as of today. ## `asm goto` Kernel has hundreds of `asm goto` usage, I compiled them into the following categories: ### Handles exceptions inside exceptions Example is arch/x86/kvm/vmx/vmx.c: ```c asm goto("1: vmxon %[vmxon_pointer]\n\t" _ASM_EXTABLE(1b, %l[fault]) : : [vmxon_pointer] "m"(vmxon_pointer) : : fault); ``` Many other examples in implementing uaccess (user-space access), which can fault and must be handled gracefully. Performance matters a lot in this case too, so using an extra output register and polyfill without asm goto is bad. The corresponding Rust pattern is: ```rust unsafe { asm!("/* do some op, jump to {fault} on fail */", fault = label { // Fault }) } ``` In this use case, an issue with current design is that the `fault` block becomes unsafe. The following workaround can be used to avoid this unsafe: ```rust! 'out: { 'fault: { unsafe { asm!("/* do some op, jump to {fault} on fail */", fault = label { break 'fault; }); } break 'out; } // Fault handling code. } ``` ### Breaking out from loop in asm-implemented computation Example in arch/riscv/lib/csum.c ```c ... asm goto("/*...*/ beqz ..., %[end] /*...*/":/*...*/:/*...*/::end); ... end: return ...; ``` This corresponds to Rust pattern: ```rust asm!("/* ... */ beq, ..., {end} /* ... */", end = label { return ... // or break }) ``` The code inside the block is immediate another control flow transfer. The code inside label block is minimal so current design works fine. ### Alternative mechanism Needed when need to use different code on different CPUs. Too costly to perform function calls. Example in arch/riscv/include/asm/bitops.h: ```c asm goto(ALTERNATIVE("j %l[legacy]", "nop", 0, RISCV_ISA_EXT_ZBB, 1) : : : : legacy); ``` The old content (j legacy) is emitted, but will be replaced with a nop if Zbb ISA extension is detected. The implementation would be more involved in Rust because it can't stringification of constants. It'll probably be something like this: ```rust asm!(alternative!("j {legacy}", "nop"), vendor_id = const 0, patch_id = const RISCV_ISA_EXT_ZBB, config = const 1, legacy = { /* Legacy */ }) ``` This is similar to the fault handling case. ### Static branch Similar to alternative mechanism, but allow run-time switching. ```c static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch) { asm goto( " .align 2 \n\t" " .option push \n\t" " .option norelax \n\t" " .option norvc \n\t" "1: nop \n\t" " .option pop \n\t" " .pushsection __jump_table, \"aw\" \n\t" " .align " RISCV_LGPTR " \n\t" " .long 1b - ., %l[label] - . \n\t" " " RISCV_PTR " %0 - . \n\t" " .popsection \n\t" : : "i"(&((char *)key)[branch]) : : label); return false; label: return true; } ``` This translates to something like this in Rust: ```rust 'outer: { unsafe { asm!(/* use label */, label { break 'outer true; }); false } } ``` Note that this static inline function cannot be translated to a function in Rust, due to Rust's lack of `i` constraints (covered later). The usage here is to return true/false from within block and then immediately use the value with if: ```rust if arch_static_branch!(KEY, BRANCH) { /* */ } ``` This is easily optimised by LLVM's SimplifyCFG pass to remove the additional if. We prefer to have `if` to avoid having custom control flow in macros, e.g. ```rust // Note this still has the unsafety issue. arch_static_branch!(KEY, BRANCH, { /* */ }) ``` This pattern is most common. Another example is the following Rust impl of `ctrl_dep/volatile_cond` (not mainline yet): ```rust /// Enforce a control dependency on `x`. fn ctrl_dep(x: bool) -> bool { unsafe { core::arch::asm!( "test {:e}, 0", "jne {}", in(reg) x as i32, label { return true; } ); } false } ``` ### Restartable sequences (rseq) Userspace, kernel code limited to selftests only. Jump to different cases to handle different rseq results. ### Design decisions? #### `asm goto` with outputs * Kernel uses it if available * GCC doesn't support this * LLVM supports it but can miscompile in some cases: * https://github.com/llvm/llvm-project/issues/74483 Preferred action is to have `asm_goto_outputs` be a separate feature gate, and defer its stabilisation until GCC supports it and LLVM stop miscompiling. #### Return values directly from `asm!` block For the static branch case: ```rust 'outer: { unsafe { asm!(/* use label */, label { break 'outer true; }); false } } ``` can be written as: ```rust unsafe { asm!(/* use label */, label { true }, fallthrough { false }); } ``` However this adds complexity to a rarely (in terms of code appearance frequency not runtime frequency) used asm feature. I am not certain what should happen here, need inputs. #### Fallthrough block Should `asm goto` include a fall-through block? * If `asm!` cannot return value, then adding fall-through block would not help with any of the above use cases. Preferred action would be to not implement it unless a use case arise. ## `i` constraint `i` constraint in C means that a constant is passed to the assembler. This only needs to be assemble-time constant, not compile time. Relocation is also allowed. ```c! int x; static inline void foo(int *ptr, int value) { int y; asm volatile ("/* ... */"::"i"(100)); // OKAY asm volatile ("/* ... */"::"i"(1+1)); // OKAY asm volatile ("/* ... */"::"i"(&x)); // OKAY asm volatile ("/* ... */"::"i"(&y)); // NOT OKAY asm volatile ("/* ... */"::"i"(ptr)); // OKAY if caller uses a constant `ptr`. asm volatile ("/* ... */"::"i"(value)); // OKAY if caller uses a constant `value`. } ``` In contrast Rust `const` is string-interpolation only, and its usage is quite limited. ```rust= static X: (i32, i32) = 1; #[inline] unsafe fn foo<const N: i32>(ptr: *const i32, value: i32) { let y: i32 = 2; asm!("/* ... */", const 100); // OKAY asm!("/* ... */", const 1+1); // OKAY asm!("/* ... */", const &X); // NOT OKAY asm!("/* ... */", const &X.1); // NOT OKAY asm!("/* ... */", sym X); // but this is OKAY asm!("/* ... */", const &y); // NOT OKAY asm!("/* ... */", const ptr); // NOT OKAY asm!("/* ... */", const value); // NOT OKAY asm!("/* ... */", const N); // but this is OKAY } ``` Currently in Rust `sym` can be used to pass address of a `static`, but there is no way to embed address of `&'static Foo` into assembly. The idea is to extend const to any CTFE constants. I don't think Rust needs to support use of assemble-time constants, CTFE constants should be sufficient. This would make line 8 & 9 above legal. Issue: https://github.com/rust-lang/rust/issues/128464 Of course, to be able to pass multiple different pointers to the function still is not ergnomic. This still needs a trait and assoc constant. ```rust trait Foo { const X: *const i32; } unsafe fn foo<F: Foo, const N: usize>() { asm!("/* ... */", const F::X); // OKAY with proposal asm!("/* ... */", const N); // OKAY } ``` ### Constant function arguments If a language feature allows constant function arguments, then this can be: ```rust unsafe fn foo(const ptr: *const i32, const value: i32) { asm!("/* ... */", const ptr); asm!("/* ... */", const value); } ``` These const values do not flow into the type system, so they don't need `adt_const_params`, and can depend on generics, similar to `const {}` block. This is helpful for other scenarios, e.g. atomic ordering: ```rust impl AtomicU32 { fn fetch_add(&self, val: u32, const order: Ordering) -> u32; } ``` or make sure something can be checked in compile time: ```rust impl<const SIZE: usize> IoMem<SIZE> { fn readb(&self, const addr: usize) -> u8 { const { assert!(addr < SIZE); } /* ... */ } } ``` ## Other constraints Rust notably lacks: * `m` memory constraints * Hybrid constraints, e.g. `ri` to allow choose of register or immediate depending on whether the value can be optimised to constant in compile-time. Or `rm` to allow a value to be either in memory or register (useful for x86 assembly).