// Web target differential program (run.sh): the same output from rustc's build of it (a // wasm32-unknown-unknown cdylib run in node for wasm32; a host binary for wasm64, whose data // layout is x86_64's) and from Relax running it as the web target in the trace host's mode // (MAKEPAD_RUN_CHECK_TARGET=wasm{32,64}-unknown-unknown). Output goes through the page-side // import `js_console_log` (a page has no stdout). Pointer-heavy on purpose: fat pointers, // trait objects, vtables, statics holding pointers, Box/Rc/Vec/String, enums with pointer niches, // fn pointers, closures, unaligned/packed fields, 64- and 128-bit math, atomics. #![allow(dead_code)] use std::cell::{Cell, RefCell}; use std::collections::{BTreeMap, VecDeque}; use std::fmt::Write; use std::rc::Rc; use std::sync::atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering}; use std::sync::Mutex; #[cfg(target_family = "wasm")] extern "C" { fn js_console_log(ptr: *const u8, len: usize); } #[cfg(target_family = "wasm")] fn out(s: &str) { unsafe { js_console_log(s.as_ptr(), s.len()) } } #[cfg(not(target_family = "wasm"))] fn out(s: &str) { use std::io::Write; let _ = std::io::stderr().write_all(s.as_bytes()); } macro_rules! say { ($($t:tt)*) => {{ let mut s = String::new(); let _ = writeln!(s, $($t)*); out(&s); }}; } trait Shape { fn area(&self) -> f64; fn name(&self) -> String; } struct Sq(f64); struct Circ { r: f64, label: &'static str } impl Shape for Sq { fn area(&self) -> f64 { self.0 * self.0 } fn name(&self) -> String { format!("sq{}", self.0) } } impl Shape for Circ { fn area(&self) -> f64 { 3.0 * self.r * self.r } fn name(&self) -> String { format!("circ:{}", self.label) } } static NAMES: [&str; 3] = ["alpha", "beta", "gamma"]; static TABLE: &[(&str, fn(u32) -> u32)] = &[("dbl", dbl), ("sq", sqr), ("inc", |x| x + 1)]; static COUNT: AtomicUsize = AtomicUsize::new(0); static BIG: AtomicU64 = AtomicU64::new(1 << 40); static mut CELL: [u32; 4] = [1, 2, 3, 4]; thread_local! { static TL: Cell = const { Cell::new(7) }; } fn dbl(x: u32) -> u32 { x * 2 } fn sqr(x: u32) -> u32 { x * x } struct OnceState([u8; N]); impl Drop for OnceState { fn drop(&mut self) { ONCE_DROPS.fetch_add(1, Ordering::SeqCst); } } static ONCE_DROPS: AtomicUsize = AtomicUsize::new(0); #[inline(never)] fn take_once(state: OnceState) -> usize { state.0.iter().map(|&x| x as usize).sum() } #[inline(never)] fn call_once_box(f: Box usize>) -> usize { f() } fn once_size() -> usize { let state = OnceState([3; N]); call_once_box(Box::new(move || take_once(state))) } #[repr(align(256))] struct OnceAligned(OnceState<24>); #[inline(never)] fn take_aligned(state: OnceAligned) -> usize { take_once(state.0) } #[derive(Debug, Clone, PartialEq)] enum Node { Leaf(i64), Pair(Box, Box), Name(String), Empty, } fn eval(n: &Node) -> i64 { match n { Node::Leaf(v) => *v, Node::Pair(a, b) => eval(a) * 3 + eval(b), Node::Name(s) => s.len() as i64, Node::Empty => -1, } } #[repr(C, packed)] struct Packed { a: u8, b: u32, c: u16 } #[repr(C)] struct Header { tag: u8, ptr: *const u8, len: usize, next: Option<&'static Header> } unsafe impl Sync for Header {} static H2: Header = Header { tag: 2, ptr: b"two".as_ptr(), len: 3, next: None }; static H1: Header = Header { tag: 1, ptr: b"one!".as_ptr(), len: 4, next: Some(&H2) }; fn walk(h: &Header) -> String { let mut s = String::new(); let mut cur = Some(h); while let Some(x) = cur { let bytes = unsafe { std::slice::from_raw_parts(x.ptr, x.len) }; let _ = write!(s, "{}:{} ", x.tag, std::str::from_utf8(bytes).unwrap()); cur = x.next; } s } // C-unwind callbacks have the ScriptProtoPropsFn shape. Keep the table and its // invocation out of line so these exercise the indirect C ABI, including taking // the address of a by-value argument and returning an aggregate by value. #[repr(C)] #[derive(Clone, Copy)] struct Object8 { index: u32, generation: u16 } #[repr(C)] #[derive(Clone, Copy)] struct Object12 { a: u32, b: u32, c: u32 } #[repr(C)] #[derive(Clone, Copy)] struct Object24 { a: u64, b: u64, c: u64 } #[repr(C)] #[derive(Clone, Copy)] struct ObjectMixed { a: f64, b: u32 } #[repr(C)] #[derive(Clone, Copy)] struct ObjectFloat24 { a: f64, b: f64, c: f64 } #[repr(C, packed)] #[derive(Clone, Copy)] struct ObjectPacked { a: u8, b: u32 } #[repr(C)] struct Vm { words: [u64; 96] } #[repr(C)] struct Props { sum: u64, calls: u32 } trait Object: Copy { fn sum(&self) -> u64; fn changed(self) -> Self; } impl Object for Object8 { fn sum(&self) -> u64 { self.index as u64 + self.generation as u64 } fn changed(self) -> Self { Self { index: self.index + 7, generation: self.generation + 3 } } } impl Object for Object12 { fn sum(&self) -> u64 { self.a as u64 + self.b as u64 + self.c as u64 } fn changed(self) -> Self { Self { a: self.a + 1, b: self.b + 2, c: self.c + 3 } } } impl Object for Object24 { fn sum(&self) -> u64 { self.a + self.b + self.c } fn changed(self) -> Self { Self { a: self.a + 4, b: self.b + 5, c: self.c + 6 } } } impl Object for ObjectMixed { fn sum(&self) -> u64 { self.a as u64 + self.b as u64 } fn changed(self) -> Self { Self { a: self.a + 0.5, b: self.b + 9 } } } impl Object for ObjectFloat24 { fn sum(&self) -> u64 { (self.a + self.b + self.c) as u64 } fn changed(self) -> Self { Self { a: self.a + 0.25, b: self.b + 0.5, c: self.c + 0.75 } } } impl Object for ObjectPacked { fn sum(&self) -> u64 { self.a as u64 + self.b as u64 } fn changed(self) -> Self { Self { a: self.a + 1, b: self.b + 19 } } } struct Callbacks { props: extern "C-unwind" fn(&mut Vm, T, &mut Props), result: extern "C-unwind" fn(&mut Vm, T, &mut Props) -> T, } #[inline(never)] fn object_sum(object: &T) -> u64 { object.sum() } #[inline(never)] extern "C-unwind" fn proto_props_c(vm: &mut Vm, object: T, props: &mut Props) { let n = object_sum(&object); vm.words[83] += n; props.sum += n * 3; props.calls += 1; } #[inline(never)] extern "C-unwind" fn proto_return_c(vm: &mut Vm, object: T, props: &mut Props) -> T { proto_props_c(vm, object, props); object.changed() } #[inline(never)] fn call_callbacks(table: &Callbacks, vm: &mut Vm, object: T, props: &mut Props) -> T { (table.props)(vm, object, props); (table.result)(vm, object, props) } struct SpillCallback { f: extern "C-unwind" fn(&mut Vm, u64, u64, u64, u64, u64, Object12, &mut Props) -> Object12, } #[inline(never)] extern "C-unwind" fn proto_spill_c(vm: &mut Vm, a: u64, b: u64, c: u64, d: u64, e: u64, object: Object12, props: &mut Props) -> Object12 { // All six SysV integer registers are occupied before the 12-byte object. vm.words[7] = a + b + c + d + e; proto_return_c(vm, object, props) } #[inline(never)] fn call_spill(table: &SpillCallback, vm: &mut Vm, object: Object12, props: &mut Props) -> Object12 { (table.f)(vm, 101, 203, 307, 409, 503, object, props) } fn c_aggregates() { let mut vm = Vm { words: [0; 96] }; let mut props = Props { sum: 0, calls: 0 }; let a = call_callbacks(&Callbacks { props: proto_props_c, result: proto_return_c }, &mut vm, Object8 { index: 0x12345678, generation: 0x4321 }, &mut props); say!("c8 {} {} {} {} {}", a.index, a.generation, vm.words[83], props.sum, props.calls); let b = call_callbacks(&Callbacks { props: proto_props_c, result: proto_return_c }, &mut vm, Object12 { a: 11, b: 23, c: 47 }, &mut props); say!("c12 {} {} {} {} {} {}", b.a, b.b, b.c, vm.words[83], props.sum, props.calls); let c = call_callbacks(&Callbacks { props: proto_props_c, result: proto_return_c }, &mut vm, Object24 { a: 1 << 40, b: 101, c: 203 }, &mut props); say!("c24 {} {} {} {} {} {}", c.a, c.b, c.c, vm.words[83], props.sum, props.calls); let m = call_callbacks(&Callbacks { props: proto_props_c, result: proto_return_c }, &mut vm, ObjectMixed { a: 17.25, b: 31 }, &mut props); say!("c_mixed {} {} {} {} {}", m.a, m.b, vm.words[83], props.sum, props.calls); let f = call_callbacks(&Callbacks { props: proto_props_c, result: proto_return_c }, &mut vm, ObjectFloat24 { a: 1.5, b: 2.25, c: 3.75 }, &mut props); say!("c_float24 {} {} {} {} {} {}", f.a, f.b, f.c, vm.words[83], props.sum, props.calls); let p = call_callbacks(&Callbacks { props: proto_props_c, result: proto_return_c }, &mut vm, ObjectPacked { a: 3, b: 98765 }, &mut props); let pb = p.b; say!("c_packed {} {} {} {} {}", p.a, pb, vm.words[83], props.sum, props.calls); let s = call_spill(&SpillCallback { f: proto_spill_c }, &mut vm, Object12 { a: 17, b: 37, c: 73 }, &mut props); say!("c_spill {} {} {} {} {} {} {}", s.a, s.b, s.c, vm.words[7], vm.words[83], props.sum, props.calls); } // Coverage probes execute between pointer uses in these small callees and in // the string/lock implementations. Keep them hot and cross call boundaries. #[inline(never)] fn coverage_chars(text: &str, total: &Mutex, count: &AtomicUsize) -> u64 { let mut sum = 0u64; for (offset, ch) in text.char_indices() { sum = sum.wrapping_add(ch as u64 + offset as u64); count.fetch_or(1 << (offset % 16), Ordering::Relaxed); } *total.lock().unwrap() += sum; sum + count.load(Ordering::Acquire) as u64 } fn coverage_hot() { let total = Mutex::new(0u64); let count = AtomicUsize::new(0); let texts = [String::from("ASCII"), String::from("Grüße ✓ 🦀"), String::new()]; let mut sum = 0u64; for i in 0..4096 { sum += coverage_chars(&texts[i % texts.len()], &total, &count); } say!("coverage {} {} {}", sum, *total.lock().unwrap(), count.load(Ordering::Relaxed)); } fn body() { numeric_casts(); web_runtime_cases(); coverage_hot(); c_aggregates(); say!("usize {} ptr {} fat {} dyn {} opt_box {} vec {} string {}", std::mem::size_of::(), std::mem::size_of::<*const u8>(), std::mem::size_of::<&[u8]>(), std::mem::size_of::<&dyn Shape>(), std::mem::size_of::>>(), std::mem::size_of::>(), std::mem::size_of::()); // trait objects through vtables, in a Vec> let shapes: Vec> = vec![Box::new(Sq(2.0)), Box::new(Circ { r: 1.5, label: "c1" }), Box::new(Sq(0.5))]; let total: f64 = shapes.iter().map(|s| s.area()).sum(); let names: Vec = shapes.iter().map(|s| s.name()).collect(); say!("shapes {:.3} {:?}", total, names); // statics of pointers: &str arrays, fn pointer tables, a linked list of statics for (i, n) in NAMES.iter().enumerate() { say!("name {} {} {}", i, n, n.len()); } for (n, f) in TABLE { say!("fn {} {}", n, f(7)); } say!("walk {}", walk(&H1)); // recursive boxed enum let tree = Node::Pair(Box::new(Node::Pair(Box::new(Node::Leaf(4)), Box::new(Node::Name("hello".to_string())))), Box::new(Node::Leaf(-9))); say!("tree {} {:?}", eval(&tree), tree.clone() == tree); say!("empty {} {:?}", eval(&Node::Empty), Node::Name("x".into())); // Rc / RefCell graph let shared = Rc::new(RefCell::new(Vec::::new())); let handles: Vec>>> = (0..4).map(|_| shared.clone()).collect(); for (i, h) in handles.iter().enumerate() { h.borrow_mut().push(i as u32 * 10); } say!("rc {} {:?}", Rc::strong_count(&shared), shared.borrow()); // closures capturing by reference and by move, boxed FnMut let mut acc = 0u64; let mut add = |x: u64| acc += x; for i in 1..=10 { add(i * i); } let k = 5u64; let mut fs: Vec u64>> = Vec::new(); fs.push(Box::new(move |x| x * k)); let mut st = 0u64; fs.push(Box::new(move |x| { st += x; st })); let r: Vec = (0..3).map(|i| { let a = fs[0](i); fs[1](a) }).collect(); say!("closures {} {:?}", acc, r); // Box moves its environment out before freeing the allocation. Its // compiler-called free helper must retain the vtable on both wasm pointer widths. let state = OnceAligned(OnceState([7; 24])); let aligned = call_once_box(Box::new(move || take_aligned(state))); say!("box once {} {} {} {} {} drops {}", once_size::<0>(), once_size::<1>(), once_size::<24>(), once_size::<4096>(), aligned, ONCE_DROPS.load(Ordering::SeqCst)); // strings, chars, formatting let mut s = String::from("Grüße, wasm32!"); s.push_str(" ✓"); let up: String = s.chars().rev().filter(|c| !c.is_whitespace()).collect(); say!("str {} {} {} {:?}", s.len(), s.chars().count(), up, s.find("wasm")); say!("fmt {:>8}|{:<6}|{:^7}|{:08.3}|{:x}|{:#b}|{:e}", "r", "l", "c", -3.14159, 0xdead_beefu32, 5u8, 1234.5f64); // collections let mut m = BTreeMap::new(); for w in "the quick brown fox jumps over the lazy dog the end".split(' ') { *m.entry(w).or_insert(0usize) += 1; } say!("btree {:?}", m); let mut q: VecDeque = (1..=5).collect(); q.rotate_left(2); q.push_front(-1); say!("deque {:?}", q); let mut v: Vec = (0..40).map(|i| (i * 7919) % 101 - 50).collect(); v.sort_unstable(); v.dedup(); say!("sorted {} {:?} {:?}", v.len(), &v[..5], v.binary_search(&0)); // 64/128-bit integer math, wrapping, checked let a: u64 = 0xffff_ffff_0000_0001; let b: u128 = (a as u128) * (a as u128); say!("int {} {} {:?} {:?} {}", a.wrapping_mul(3), b, a.checked_add(u64::MAX), 7usize.checked_sub(9), (-7i64).rem_euclid(3)); say!("usize {} {} {}", usize::MAX, isize::MIN, (usize::MAX as u64).count_ones()); // floats let xs = [0.1f64, 2.5, -3.75, 1e10, 1.0 / 3.0]; let prod: f64 = xs.iter().product(); say!("float {} {:.6} {} {}", prod, xs.iter().map(|x| x.abs().sqrt()).sum::(), (2.5f32).powi(3), f64::EPSILON); // packed and repr(C) fields let p = Packed { a: 1, b: 0x0102_0304, c: 0x0506 }; let pb = p.b; let pc = p.c; say!("packed {} {} {} {}", std::mem::size_of::(), p.a, pb, pc); // atomics on pointers and wide integers let x = Box::into_raw(Box::new(41u32)); let ap = AtomicPtr::new(std::ptr::null_mut()); ap.store(x, Ordering::SeqCst); unsafe { *ap.load(Ordering::SeqCst) += 1 }; COUNT.fetch_add(3, Ordering::Relaxed); BIG.fetch_add(5, Ordering::Relaxed); say!("atomic {} {} {}", unsafe { *x }, COUNT.load(Ordering::Relaxed), BIG.load(Ordering::Relaxed)); unsafe { drop(Box::from_raw(x)) }; // raw pointer arithmetic, pointer <-> integer casts let arr = [10u16, 20, 30, 40]; let p0 = arr.as_ptr(); let p3 = unsafe { p0.add(3) }; let diff = (p3 as usize - p0 as usize) / std::mem::size_of::(); let back = (p0 as usize + 2 * std::mem::size_of::()) as *const u16; say!("ptr {} {} {}", diff, unsafe { *back }, unsafe { p3.offset_from(p0) }); unsafe { let c = &mut *std::ptr::addr_of_mut!(CELL); c[2] += 40; say!("static mut {:?}", c); } TL.with(|t| t.set(t.get() * 6)); say!("tls {}", TL.with(|t| t.get())); // Option<&T> / Option> niches let some: Option<&u32> = arr.iter().map(|_| &7u32).next(); let none: Option> = None; say!("niche {:?} {:?} {}", some, none, std::mem::size_of_val(&none)); say!("done"); } /// rustc's wasm32 build: node calls this (run.sh) #[no_mangle] pub extern "C" fn run() { body() } fn main() { body() } fn web_runtime_cases() { use std::hint::black_box; let unsigned = [0u128, 1, u64::MAX as u128, 1u128 << 64, 1u128 << 127, u128::MAX]; let mut sum = 0u128; for a in unsigned { for b in unsigned { if b != 0 { let a = black_box(a); let b = black_box(b); sum = sum.wrapping_add(a / b).wrapping_add(a % b); } } } let signed = [i128::MIN, -123456789012345678901i128, -1, 0, 1, i128::MAX]; for a in signed { for b in signed { if b != 0 && !(a == i128::MIN && b == -1) { let a = black_box(a); let b = black_box(b); sum = sum.wrapping_add((a / b) as u128).wrapping_add((a % b) as u128); } } } let text = String::from("a runtime string longer than sixteen bytes"); let same = text.clone(); let mut different = text.clone(); different.push('!'); assert!(black_box(text.as_str()) == black_box(same.as_str())); assert!(black_box(text.as_str()) != black_box(different.as_str())); different.pop(); different.replace_range(20..21, "X"); assert!(black_box(text.as_str()) != black_box(different.as_str())); unsafe { use std::alloc::{GlobalAlloc, Layout, System}; let layout = Layout::from_size_align(131072, 256).unwrap(); let p = System.alloc_zeroed(layout); assert!(!p.is_null()); assert_eq!(p as usize % 256, 0); for i in 0..layout.size() { assert_eq!(*p.add(i), 0); *p.add(i) = (i % 251) as u8; } let q = System.realloc(p, layout, 393216); assert!(!q.is_null()); for i in 0..layout.size() { assert_eq!(*q.add(i), (i % 251) as u8); } let large = Layout::from_size_align(393216, 256).unwrap(); let p = System.realloc(q, large, 65536); assert!(!p.is_null()); for i in 0..65536 { assert_eq!(*p.add(i), (i % 251) as u8); } System.dealloc(p, Layout::from_size_align(65536, 256).unwrap()); } say!("web_runtime {sum}"); } fn numeric_casts() { use std::hint::black_box; // Both signednesses and precisions, including halfway values on either side // of an even significand and carries into the next exponent (f32 infinity). for value in [0u128, 1, (1u128 << 24) + 1, (1u128 << 24) + 3, (1u128 << 53) + 1, (1u128 << 53) + 3, 1u128 << 63, u64::MAX as u128, 1u128 << 64, (1u128 << 100) + (1u128 << 76) + 1, (1u128 << 100) + (1u128 << 47) + 1, 1u128 << 127, u128::MAX] { let x = black_box(value); let y = black_box(value as i128); say!("wide_cast {} {} {} {} {}", x, (x as f32).to_bits(), (x as f64).to_bits(), (y as f32).to_bits(), (y as f64).to_bits()); let n = black_box(y.wrapping_neg()); say!("wide_negative {} {} {}", n, (n as f32).to_bits(), (n as f64).to_bits()); let x = black_box(value as u64); let y = black_box(value as i64); say!("int_cast {} {} {} {} {}", x, (x as f32).to_bits(), (x as f64).to_bits(), (y as f32).to_bits(), (y as f64).to_bits()); } for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -0.0, 0.0, -1.0, 1.5, -129.0, 256.0, -32769.0, 65536.0, -2147483649.0, 4294967296.0, 9007199254740992.0, 9223372036854775808.0, 18446744073709551616.0, 170141183460469231731687303715884105728.0, 340282366920938463463374607431768211456.0] { let x = black_box(value); let y = black_box(value as f32); say!("f64_cast {} {} {} {} {} {} {} {} {} {}", x as i8, x as u8, x as i16, x as u16, x as i32, x as u32, x as i64, x as u64, x as i128, x as u128); say!("f32_cast {} {} {} {} {} {} {} {} {} {}", y as i8, y as u8, y as i16, y as u16, y as i32, y as u32, y as i64, y as u64, y as i128, y as u128); } }