The JIT that runs makepad apps from source: lexer, front end, type checker, lazy borrow checker (Gandalf; `--check-first` opts in), codegen, session and warm/cold caches keyed by compiler stamp, provided crates bound late, startup images, live patching. User-facing name is `makepad run` (binary `makepad`, MAKEPAD_RUN_* env, ~/.makepad-run); libs/relax stays the internal library name. `makepad check` reports rustc-style diagnostics with rustc error codes and checks a whole workspace (`--workspace`). Runs on macOS, Linux, Windows and Android. makepad-remote: one tool schema for apps and the WM (app/1): grabs, captures, traces, frame profiles and tweaks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
310 lines
9.5 KiB
Rust
310 lines
9.5 KiB
Rust
// Differential regression for loops.rs::vectorize_byte_maps. Run with rustc -O --test
|
|
// --edition 2021, and Relax at O1 and MAKEPAD_RUN_O3=1 MAKEPAD_RUN_O3_CALLS=100.
|
|
// Repeated sweeps keep calling every kernel while asynchronous O3 is installed.
|
|
// Hash the entire allocation (including untouched prefixes, suffixes and zip tails).
|
|
|
|
#[inline(never)]
|
|
fn add_pairs(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((x, y), out) in a.iter().zip(b.iter()).zip(dst.iter_mut()) {
|
|
*out = x.wrapping_add(*y);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn add_nested(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
*out = x.wrapping_add(*y);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn sub_pairs(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((x, y), out) in a.iter().zip(b.iter()).zip(dst.iter_mut()) {
|
|
*out = x.wrapping_sub(*y);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn sub_nested(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
*out = x.wrapping_sub(*y);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn and_pairs(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((x, y), out) in a.iter().zip(b.iter()).zip(dst.iter_mut()) {
|
|
*out = *x & *y;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn and_nested(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
*out = *x & *y;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn or_pairs(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((x, y), out) in a.iter().zip(b.iter()).zip(dst.iter_mut()) {
|
|
*out = *x | *y;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn or_nested(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
*out = *x | *y;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn xor_pairs(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((x, y), out) in a.iter().zip(b.iter()).zip(dst.iter_mut()) {
|
|
*out = *x ^ *y;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn xor_nested(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
*out = *x ^ *y;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn up(cur: &mut [u8], prev: &[u8]) {
|
|
for (out, x) in cur.iter_mut().zip(prev.iter()) {
|
|
*out = out.wrapping_add(*x);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn sub_filter(cur: &mut [u8], bpp: usize) {
|
|
for i in bpp..cur.len() {
|
|
cur[i] = cur[i].wrapping_add(cur[i - bpp]);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn copy_zip(dst: &mut [u8], src: &[u8]) {
|
|
for (out, x) in dst.iter_mut().zip(src.iter()) {
|
|
*out = *x;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn constants(dst: &mut [u8], a: &[u8], b: &[u8]) {
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
*out = (x.wrapping_add(*y) ^ 0xa5).wrapping_sub(37) | 3;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn live_out(dst: &mut [u8], a: &[u8], b: &[u8]) -> u8 {
|
|
let mut last = 0x69;
|
|
for ((out, x), y) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
|
|
last = x.wrapping_sub(*y);
|
|
*out = last;
|
|
}
|
|
last
|
|
}
|
|
|
|
// Iterators yield raw pointers, never overlapping Rust references. All pointers
|
|
// stay in the one live allocation, even for empty ranges and the final increment.
|
|
struct Ptrs {
|
|
ptr: *mut u8,
|
|
left: usize,
|
|
}
|
|
impl Iterator for Ptrs {
|
|
type Item = *mut u8;
|
|
#[inline(always)]
|
|
fn next(&mut self) -> Option<*mut u8> {
|
|
if self.left == 0 {
|
|
None
|
|
} else {
|
|
let p = self.ptr;
|
|
self.ptr = unsafe { p.add(1) };
|
|
self.left -= 1;
|
|
Some(p)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
unsafe fn copy_index(dst: *mut u8, src: *mut u8, n: usize) {
|
|
for i in 0..n {
|
|
*dst.add(i) = *src.add(i);
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
unsafe fn copy_ptrs(dst: *mut u8, src: *mut u8, n: usize) {
|
|
for (out, x) in (Ptrs { ptr: dst, left: n }).zip(Ptrs { ptr: src, left: n }) {
|
|
*out = *x;
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
unsafe fn add_index(dst: *mut u8, src: *mut u8, n: usize) {
|
|
for i in 0..n {
|
|
*dst.add(i) = (*dst.add(i)).wrapping_add(*src.add(i));
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
unsafe fn add_ptrs(dst: *mut u8, src: *mut u8, n: usize) {
|
|
for (out, x) in (Ptrs { ptr: dst, left: n }).zip(Ptrs { ptr: src, left: n }) {
|
|
*out = (*out).wrapping_add(*x);
|
|
}
|
|
}
|
|
|
|
// Cell has u8's layout and permits aliased shared references. These are the
|
|
// slice-iterator shape of the raw-pointer kernels: they exercise the SIMD alias
|
|
// guard without ever constructing overlapping &mut [u8] / &[u8] references.
|
|
#[inline(never)]
|
|
unsafe fn copy_cells(dst: *mut u8, src: *mut u8, n: usize) {
|
|
let dst = std::slice::from_raw_parts(dst as *const std::cell::Cell<u8>, n);
|
|
let src = std::slice::from_raw_parts(src as *const std::cell::Cell<u8>, n);
|
|
for (out, x) in dst.iter().zip(src.iter()) {
|
|
out.set(x.get());
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
unsafe fn add_cells(dst: *mut u8, src: *mut u8, n: usize) {
|
|
let dst = std::slice::from_raw_parts(dst as *const std::cell::Cell<u8>, n);
|
|
let src = std::slice::from_raw_parts(src as *const std::cell::Cell<u8>, n);
|
|
for (out, x) in dst.iter().zip(src.iter()) {
|
|
out.set(out.get().wrapping_add(x.get()));
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn input(n: usize, seed: usize) -> Vec<u8> {
|
|
let mut bytes = Vec::new();
|
|
for i in 0..n {
|
|
bytes.push((i.wrapping_mul(197) ^ (i >> 3) ^ seed.wrapping_mul(113)) as u8);
|
|
}
|
|
bytes
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn hash(mut h: u64, bytes: &[u8]) -> u64 {
|
|
for x in bytes {
|
|
h = (h ^ *x as u64).wrapping_mul(0x100000001b3);
|
|
}
|
|
h
|
|
}
|
|
|
|
// Keep the full case in a separate, sufficiently large function: Relax can inline
|
|
// small kernels even with inline(never). Calling this 7,008 times ensures that the
|
|
// installed O3 body (including its inlined byte maps) is actually entered.
|
|
#[inline(never)]
|
|
fn exercise_case(mut h: u64, rep: usize, n: usize, off: usize) -> u64 {
|
|
let a = input(n + 64, rep + 1);
|
|
let b = input(n + 64, rep + 19);
|
|
// Every stream is shortest in turn, including empty/nonempty zips.
|
|
// Rotating independent offsets exercises unaligned loads and stores.
|
|
let ao = (off + 5) % 16;
|
|
let bo = (off + 11) % 16;
|
|
for short in 0..4usize {
|
|
let dn = n + if short == 1 || short == 2 { 7 } else { 0 };
|
|
let an = n + if short == 0 || short == 2 { 5 } else { 0 };
|
|
let bn = n + if short == 0 || short == 1 { 3 } else { 0 };
|
|
let aa = &a[ao..ao + an];
|
|
let bb = &b[bo..bo + bn];
|
|
let mut out = input(n + 64, rep + 37);
|
|
add_pairs(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
add_nested(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
sub_pairs(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
sub_nested(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
and_pairs(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
and_nested(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
or_pairs(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
or_nested(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
xor_pairs(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
xor_nested(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
copy_zip(&mut out[off..off + dn], aa);
|
|
h = hash(h, &out);
|
|
constants(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h, &out);
|
|
let last = live_out(&mut out[off..off + dn], aa, bb);
|
|
h = hash(h ^ last as u64, &out);
|
|
up(&mut out[off..off + dn], aa);
|
|
h = hash(h, &out);
|
|
}
|
|
for bpp in [1usize, 2, 3, 4, 6, 8] {
|
|
let mut out = input(n + 64, rep + bpp);
|
|
sub_filter(&mut out[off..off + n], bpp);
|
|
h = hash(h, &out);
|
|
}
|
|
// These are forward scalar loops, not memmove: dst > src at a
|
|
// distance below 16 must see bytes written earlier in this loop.
|
|
// Include exact alias, both directions, and distances 15/16/17.
|
|
for distance in 0..21usize {
|
|
for direction in 0..2usize {
|
|
let dst = off + if direction == 0 { distance } else { 0 };
|
|
let src = off + if direction == 0 { 0 } else { distance };
|
|
for kernel in 0..6usize {
|
|
let mut out = input(n + 64, rep + 53);
|
|
let p = out.as_mut_ptr();
|
|
unsafe {
|
|
if kernel == 0 {
|
|
copy_index(p.add(dst), p.add(src), n);
|
|
}
|
|
if kernel == 1 {
|
|
copy_ptrs(p.add(dst), p.add(src), n);
|
|
}
|
|
if kernel == 2 {
|
|
add_index(p.add(dst), p.add(src), n);
|
|
}
|
|
if kernel == 3 {
|
|
add_ptrs(p.add(dst), p.add(src), n);
|
|
}
|
|
if kernel == 4 {
|
|
copy_cells(p.add(dst), p.add(src), n);
|
|
}
|
|
if kernel == 5 {
|
|
add_cells(p.add(dst), p.add(src), n);
|
|
}
|
|
}
|
|
h = hash(h, &out);
|
|
}
|
|
}
|
|
}
|
|
h
|
|
}
|
|
|
|
#[test]
|
|
fn byte_map_vectorize() {
|
|
let mut h = 0xcbf29ce484222325u64;
|
|
for rep in 0..6usize {
|
|
for size in 0..73usize {
|
|
let n = if size == 71 {
|
|
1000
|
|
} else if size == 72 {
|
|
4099
|
|
} else {
|
|
size
|
|
};
|
|
for off in 0..16usize {
|
|
h = exercise_case(h, rep, n, off);
|
|
}
|
|
}
|
|
println!("| byte_map_vectorize {} {:016x}", rep, h);
|
|
}
|
|
// Optimized rustc oracle, identical on x86_64 and aarch64.
|
|
assert_eq!(h, 0x8ac0df74dbe3aa71);
|
|
}
|