Con il recente supporto Rust nel kernel Linux, gli sviluppatori stanno affrontando nuove sfide nell’integrazione dei linguaggi e nell’ottimizzazione delle prestazioni. Una di queste sfide riguarda l’inlining dei C helpers, ossia piccole funzioni scritte in C già presenti nel kernel, che Rust chiama tramite binding.
Rust, grazie al suo sistema di gestione della memoria e alle garanzie di sicurezza, permette di scrivere parti critiche del kernel riducendo drasticamente il rischio di bug, comuni in C, come buffer overflow o accessi a porzioni di memoria non valide. Tuttavia, integrare Rust in un sistema complesso come il kernel Linux comporta sfide importanti, soprattutto in termini di interoperabilità con il codice C esistente e di ottimizzazione delle prestazioni.
Alice Ryhl di Google ha sviluppato un approccio che consente di integrare correttamente questi C helpers nel codice Rust quando il kernel è compilato con Link-Time Optimization (LTO), una tecnica avanzata dei compilatori LLVM/Clang che ottimizza il codice durante il linking, ossia la fase finale in cui tutte le parti del programma sono unite in un unico eseguibile.
Grazie a queste modifiche, Linux 7.0 sarà probabilmente la prima versione del kernel a integrare pienamente il nuovo supporto, segnando una tappa importante nell’adozione di Rust e nella modernizzazione del kernel, sia dal punto di vista della sicurezza che delle prestazioni.
Cosa sono i C helpers e i binding Rust
I C helpers, come anticipato in precedenza, sono piccole funzioni di utilità scritte in C, molto usate nel kernel Linux per operazioni comuni, come la gestione dei file, la memoria o i filesystem.
Rust non può chiamare direttamente codice C senza un “ponte”, chiamato binding. I binding generano funzioni Rust che internamente invocano le funzioni C. Il problema è che, senza alcune modifiche, i C helpers non possono essere ottimizzati in Rust durante le build con LTO, causando perdite di performance.
Che cos’è LTO (Link-Time Optimization) e perché è utile
LTO è una tecnica dei compilatori moderni che analizza tutto il programma alla fine della compilazione, durante il linking. Normalmente, ogni file sorgente è compilato separatamente in un oggetto binario.
Con LTO, il compilatore vede tutte le unità di compilazione insieme e può decidere di applicare ottimizzazioni distribuendole su diversi file, ad esempio effettuare l’inlining di funzioni, eliminare codice inutilizzato e riorganizzare l’uso della memoria.
In pratica, LTO permette al kernel di essere più veloce e leggero, perché il compilatore può ridurre le chiamate di funzione non necessarie e generare un codice più efficiente.
Quando Rust chiama funzioni C tramite binding, ogni file C e Rust è di fatto un’unità di compilazione separata. LLVM, il compilatore alla base di Rust e Clang, applica opzioni di ottimizzazione diverse per C e Rust.
Questo fa sì che, anche con LTO, il compilatore non riesca a capire che alcune funzioni C possono essere distribuite dentro Rust, cioè copiate direttamente nel punto di chiamata per velocizzare l’esecuzione.
La soluzione __rust_helper arriva dalla sviluppatrice Google
Per risolvere il problema brevemente descritto al precedente paragrafo, Alice Ryhl ha introdotto __rust_helper, una speciale etichetta da applicare a tutte le funzioni Rust che invocano C helpers. Serve a dire al compilatore “questa funzione Rust può contenere C helpers inlined”.
Insieme a __rust_helper, viene usato __always_inline, un attributo che forza il compilatore a inserire direttamente il codice della funzione chiamata nel punto di chiamata, evitando overhead.
Al fine di integrare la soluzione proposta da Ryhl, sono state create 46 patch che applicano __rust_helper nei punti corretti del kernel. Ogni maintainer di sottosistema può svolgere le valutazioni del caso ed eventualmente integrare le patch nel codice sotto la propria responsabilità. Alcune patch sono già previste per Linux 7.0, mentre altre richiedono ulteriori revisioni.