Mentranspilasi sistem runtime berbasis C (seperti runtime OCaml) ke Rust sering kali memicu regresi semantik yang sulit dilacak, terutama pada subsistem Garbage Collector (GC). C mengizinkan manipulasi pointer arbitrer dan cast tipe tanpa batas. Rust menuntut invariant strict terkait kepemilikan data, mutabilitas, dan validitas alignment memori. Untuk memastikan hasil translasi akurat, rekayasa pengujian memerlukan pendekatan differential testing yang membandingkan jejak eksekusi (trace) dan snapshot status heap antara biner referensi C dan biner target Rust.
Tantangan Semantik: Tagged Pointer dan Heap Layout
Runtime fungsional mengandalkan tagged pointers untuk membedakan antara integer langsung (immediate values) dan alokasi heap di dalam word memori tunggal (misalnya 64-bit). Di runtime C OCaml klasik, bit paling rendah bernilai 1 menandakan unboxed integer, sedangkan 0 menandakan pointer yang terikat batas alignment 4 atau 8 byte.
// Definisi representasi di C
typedef uintptr_t value;
#define Val_int(x) (((uintptr_t)(x) << 1) | 1)
#define Int_val(x) (((intptr_t)(x)) >> 1)
#define Is_block(x) (((x) & 1) == 0)Di Rust, merepresentasikan pola ini secara naif dengan raw pointer (*mut T) atau usize sering kali memicu Undefined Behavior (UB) saat interaksi translasi terjadi:
// Implementasi aman tagged value di Rust
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Value(pub usize);
impl Value {
#[inline(always)]
pub fn from_int(n: isize) -> Self {
Self(((n as usize) << 1) | 1)
}
#[inline(always)]
pub fn is_block(self) -> bool {
(self.0 & 1) == 0
}
#[inline(always)]
pub unsafe fn as_ptr<T>(self) -> *mut T {
debug_assert!(self.is_block());
self.0 as *mut T
}
}Perbedaan kecil dalam layout struct atau packing padding antara gcc/clang dan rustc dapat merusak perhitungan offset header blok GC. Header GC C umumnya menyimpan ukuran alokasi, warna mark-and-sweep, dan tag objek. Rust harus menggunakan atribut #[repr(C)] secara eksplisit untuk menjaga kesetaraan layout biner.
Arsitektur Differential Testing
Differential testing mengeksekusi workload identik pada implementasi C dan Rust secara paralel. Verifikasi dilakukan pada tiga titik kritis:
- State Heap Pra/Pasca-Koleksi: Dump representasi memori flat setelah alokasi massal.
- Trace GC: Log deterministik urutan pemanggilan tahap marking, sweeping, dan compaction.
- Pointer Root Set: Nilai register dan stack frame yang terdaftar sebagai GC root.
Contoh Skrip Otomasi Differential Snapshot (Python)
Gunakan harness testing berbasis Python untuk membandingkan output JSON/Hexdump dari execution trace biner referensi C dan hasil transpiler Rust:
#!/usr/bin/env python3
import subprocess
import sys
import json
def run_binary(cmd):
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if proc.returncode != 0:
raise RuntimeError(f"Execution failed: {cmd}\n{proc.stderr}")
return json.loads(proc.stdout)
def main():
workload = "tests/workloads/tree_alloc.ml.bin"
# Menjalankan binary referensi C dan target Rust
c_trace = run_binary(["./bin/ocaml_c_runtime", "--trace-gc", workload])
rust_trace = run_binary(["./bin/ocaml_rust_runtime", "--trace-gc", workload])
diff_count = 0
for step, (c_state, rust_state) in enumerate(zip(c_trace, rust_trace)):
if c_state != rust_state:
print(f"[REGRESSION] Mismatch pada step {step}:")
print(f" C : Heap size={c_state['heap_words']}, GC phase={c_state['phase']}")
print(f" Rust : Heap size={rust_state['heap_words']}, GC phase={rust_state['phase']}")
diff_count += 1
if diff_count >= 5:
break
if diff_count > 0 or len(c_trace) != len(rust_trace):
print("Differential test FAILED.")
sys.exit(1)
print("Semantik identik: Lulus.")
if __name__ == "__main__":
main()Mengisolasi Flaky Failure: Unaligned Read dan Batas Unsafe
Flaky failure pada differential test runtime hasil transpilasi umumnya bersumber dari dua faktor utama:
1. Unaligned Memory Access
Runtime C sering melakukan dereferensi pointer tanpa memeriksa alignment memori melalui casting eksplisit (misalnya mengurai header byte stream ke uint64_t*). Arsitektur x86_64 mentoleransi hal ini dengan penalti performa kecil. Namun, Rust memperlakukan pembacaan pointer tidak sejajar via dereferensi *ptr biasa sebagai Immediate Undefined Behavior. Compiler Rust dapat mengasumsikan pointer selalu sejajar dan mengoptimasi instruksi SIMD yang berujung pada crash SIGSEGV tak terduga.
Gunakan API eksplisit berikut di Rust untuk membaca data yang tidak terjamin alignment-nya:
// Hindari: let val = unsafe { *(ptr as *const u64) };
// Gunakan read_unaligned untuk akses heap arbitrer:
let val = unsafe { std::ptr::read_unaligned(ptr as *const u64) };2. Pelanggaran Batas Mutabilitas Unsafe Block
GC C menggunakan mutabilitas bebas selama fase tracing. Pada Rust, jika sebuah blok memori direpresentasikan sebagai referensi bersama (&T) saat mark bit dimodifikasi tanpa primitif mutabilitas internal, rustc alias analysis akan mengasumsikan data tidak berubah dan menghasilkan urutan pembacaan yang keliru. Raw pointer murni (*mut T) atau UnsafeCell<T> wajib digunakan di sepanjang siklus hidup objek yang dijangkau oleh GC allocator untuk mencegah silent memory miscompilation.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!