import more fidget
This commit is contained in:
+95
-52
@@ -6,8 +6,8 @@ use std::simd::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BYTECODE_IMITATES_GLSL, CSG,
|
BYTECODE_IMITATES_GLSL, CSG,
|
||||||
interval::Interval,
|
|
||||||
ssa::{SSAInput, SSAInstruction, SSAOpcode},
|
ssa::{SSAInput, SSAInstruction, SSAOpcode},
|
||||||
|
types::Interval,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Value = std::simd::f32x8;
|
pub type Value = std::simd::f32x8;
|
||||||
@@ -21,7 +21,7 @@ pub const VALUE_M1: Value = Value::splat(-1.0);
|
|||||||
pub const VALUE_2: Value = Value::splat(2.0);
|
pub const VALUE_2: Value = Value::splat(2.0);
|
||||||
pub const VALUE_PI: Value = Value::splat(core::f32::consts::PI);
|
pub const VALUE_PI: Value = Value::splat(core::f32::consts::PI);
|
||||||
|
|
||||||
fn glsign(f: Value) -> Value {
|
pub fn glsign(f: Value) -> Value {
|
||||||
if BYTECODE_IMITATES_GLSL {
|
if BYTECODE_IMITATES_GLSL {
|
||||||
f.simd_eq(VALUE_0).select(f, f.signum())
|
f.simd_eq(VALUE_0).select(f, f.signum())
|
||||||
} else {
|
} else {
|
||||||
@@ -29,23 +29,7 @@ fn glsign(f: Value) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glfract(f: Value) -> Value {
|
pub fn glfract(f: Value) -> Value {
|
||||||
if BYTECODE_IMITATES_GLSL {
|
|
||||||
f - f.floor()
|
|
||||||
} else {
|
|
||||||
f.fract()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interval_glsign(f: Interval) -> Interval {
|
|
||||||
if BYTECODE_IMITATES_GLSL {
|
|
||||||
f.simd_eq(Interval::ZERO).select(f, f.signum())
|
|
||||||
} else {
|
|
||||||
f.signum()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interval_glfract(f: Interval) -> Interval {
|
|
||||||
if BYTECODE_IMITATES_GLSL {
|
if BYTECODE_IMITATES_GLSL {
|
||||||
f - f.floor()
|
f - f.floor()
|
||||||
} else {
|
} else {
|
||||||
@@ -619,18 +603,51 @@ impl<'csg> IntervalInterpreter<'csg> {
|
|||||||
let val_a = (0..instruction.opcode.size)
|
let val_a = (0..instruction.opcode.size)
|
||||||
.map(|i| self.load(instruction.inputs[i as usize]))
|
.map(|i| self.load(instruction.inputs[i as usize]))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let val_a_lowers = val_a
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
(v.lower().simd_le(VALUE_0) & v.upper().simd_ge(VALUE_0))
|
||||||
|
.select(VALUE_0, v.lower().abs().simd_min(v.upper().abs()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let val_a_uppers = val_a
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.lower().abs().simd_max(v.upper().abs()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
self.store(
|
self.store(
|
||||||
instruction.outputs[0],
|
instruction.outputs[0],
|
||||||
match instruction.opcode.size {
|
match instruction.opcode.size {
|
||||||
1 => val_a[0],
|
1 => Interval::new(val_a_lowers[0], val_a_uppers[0]),
|
||||||
2 => ((val_a[0].square()) + (val_a[1].square())).sqrt(),
|
2 => Interval::new(
|
||||||
3 => ((val_a[0].square()) + (val_a[1].square()) + (val_a[2].square()))
|
((val_a_lowers[0] * val_a_lowers[0])
|
||||||
.sqrt(),
|
+ (val_a_lowers[1] * val_a_lowers[1]))
|
||||||
4 => ((val_a[0].square())
|
.sqrt(),
|
||||||
+ (val_a[1].square())
|
((val_a_uppers[0] * val_a_uppers[0])
|
||||||
+ (val_a[2].square())
|
+ (val_a_uppers[1] * val_a_uppers[1]))
|
||||||
+ (val_a[3].square()))
|
.sqrt(),
|
||||||
.sqrt(),
|
),
|
||||||
|
3 => Interval::new(
|
||||||
|
((val_a_lowers[0] * val_a_lowers[0])
|
||||||
|
+ (val_a_lowers[1] * val_a_lowers[1])
|
||||||
|
+ (val_a_lowers[2] * val_a_lowers[2]))
|
||||||
|
.sqrt(),
|
||||||
|
((val_a_uppers[0] * val_a_uppers[0])
|
||||||
|
+ (val_a_uppers[1] * val_a_uppers[1])
|
||||||
|
+ (val_a_uppers[2] * val_a_uppers[2]))
|
||||||
|
.sqrt(),
|
||||||
|
),
|
||||||
|
4 => Interval::new(
|
||||||
|
((val_a_lowers[0] * val_a_lowers[0])
|
||||||
|
+ (val_a_lowers[1] * val_a_lowers[1])
|
||||||
|
+ (val_a_lowers[2] * val_a_lowers[2])
|
||||||
|
+ (val_a_lowers[3] * val_a_lowers[3]))
|
||||||
|
.sqrt(),
|
||||||
|
((val_a_uppers[0] * val_a_uppers[0])
|
||||||
|
+ (val_a_uppers[1] * val_a_uppers[1])
|
||||||
|
+ (val_a_uppers[2] * val_a_uppers[2])
|
||||||
|
+ (val_a_uppers[3] * val_a_uppers[3]))
|
||||||
|
.sqrt(),
|
||||||
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -642,24 +659,56 @@ impl<'csg> IntervalInterpreter<'csg> {
|
|||||||
let val_b = (instruction.opcode.size..(instruction.opcode.size * 2))
|
let val_b = (instruction.opcode.size..(instruction.opcode.size * 2))
|
||||||
.map(|i| self.load(instruction.inputs[i as usize]))
|
.map(|i| self.load(instruction.inputs[i as usize]))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let val_a = val_b
|
||||||
|
.into_iter()
|
||||||
|
.zip(val_a.into_iter())
|
||||||
|
.map(|(a, b)| a - b)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let val_a_lowers = val_a
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
(v.lower().simd_le(VALUE_0) & v.upper().simd_ge(VALUE_0))
|
||||||
|
.select(VALUE_0, v.lower().abs().simd_min(v.upper().abs()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let val_a_uppers = val_a
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.lower().abs().simd_max(v.upper().abs()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
self.store(
|
self.store(
|
||||||
instruction.outputs[0],
|
instruction.outputs[0],
|
||||||
match instruction.opcode.size {
|
match instruction.opcode.size {
|
||||||
1 => val_b[0] - val_a[0],
|
1 => Interval::new(val_a_lowers[0], val_a_uppers[0]),
|
||||||
2 => (((val_a[0] - val_b[0]).square())
|
2 => Interval::new(
|
||||||
+ ((val_a[1] - val_b[1]).square()))
|
((val_a_lowers[0] * val_a_lowers[0])
|
||||||
.sqrt(),
|
+ (val_a_lowers[1] * val_a_lowers[1]))
|
||||||
3 => (((val_a[0] - val_b[0]).square())
|
.sqrt(),
|
||||||
+ ((val_a[1] - val_b[1]).square())
|
((val_a_uppers[0] * val_a_uppers[0])
|
||||||
+ ((val_a[2] - val_b[2]).square())
|
+ (val_a_uppers[1] * val_a_uppers[1]))
|
||||||
+ ((val_a[3] - val_b[3]).square()))
|
.sqrt(),
|
||||||
.sqrt(),
|
),
|
||||||
4 => (((val_a[0] - val_b[0]).square())
|
3 => Interval::new(
|
||||||
+ ((val_a[1] - val_b[1]).square())
|
((val_a_lowers[0] * val_a_lowers[0])
|
||||||
+ ((val_a[2] - val_b[2]).square())
|
+ (val_a_lowers[1] * val_a_lowers[1])
|
||||||
+ ((val_a[3] - val_b[3]).square())
|
+ (val_a_lowers[2] * val_a_lowers[2]))
|
||||||
+ ((val_a[4] - val_b[4]).square()))
|
.sqrt(),
|
||||||
.sqrt(),
|
((val_a_uppers[0] * val_a_uppers[0])
|
||||||
|
+ (val_a_uppers[1] * val_a_uppers[1])
|
||||||
|
+ (val_a_uppers[2] * val_a_uppers[2]))
|
||||||
|
.sqrt(),
|
||||||
|
),
|
||||||
|
4 => Interval::new(
|
||||||
|
((val_a_lowers[0] * val_a_lowers[0])
|
||||||
|
+ (val_a_lowers[1] * val_a_lowers[1])
|
||||||
|
+ (val_a_lowers[2] * val_a_lowers[2])
|
||||||
|
+ (val_a_lowers[3] * val_a_lowers[3]))
|
||||||
|
.sqrt(),
|
||||||
|
((val_a_uppers[0] * val_a_uppers[0])
|
||||||
|
+ (val_a_uppers[1] * val_a_uppers[1])
|
||||||
|
+ (val_a_uppers[2] * val_a_uppers[2])
|
||||||
|
+ (val_a_uppers[3] * val_a_uppers[3]))
|
||||||
|
.sqrt(),
|
||||||
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -716,7 +765,7 @@ impl<'csg> IntervalInterpreter<'csg> {
|
|||||||
self.param_one(instruction, |val_a| val_a.ceil());
|
self.param_one(instruction, |val_a| val_a.ceil());
|
||||||
},
|
},
|
||||||
SSAFract => {
|
SSAFract => {
|
||||||
self.param_one(instruction, |val_a| interval_glfract(val_a));
|
self.param_one(instruction, |val_a| val_a.fract());
|
||||||
},
|
},
|
||||||
SSASin => {
|
SSASin => {
|
||||||
self.param_one(instruction, |val_a| val_a.sin());
|
self.param_one(instruction, |val_a| val_a.sin());
|
||||||
@@ -770,9 +819,7 @@ impl<'csg> IntervalInterpreter<'csg> {
|
|||||||
SSASmoothMinMaterial => todo!(),
|
SSASmoothMinMaterial => todo!(),
|
||||||
SSASmoothMaxMaterial => todo!(),
|
SSASmoothMaxMaterial => todo!(),
|
||||||
SSAClamp => {
|
SSAClamp => {
|
||||||
self.param_three(instruction, |val_a, val_b, val_c| {
|
self.param_three(instruction, |val_a, val_b, val_c| val_a.clamp(val_b, val_c));
|
||||||
val_a.clamp(val_b, val_c)
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
SSAMix => {
|
SSAMix => {
|
||||||
self.param_three(instruction, |val_a, val_b, val_c| {
|
self.param_three(instruction, |val_a, val_b, val_c| {
|
||||||
@@ -827,11 +874,7 @@ impl<'csg> IntervalInterpreter<'csg> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
SSACompare => {
|
SSACompare => {
|
||||||
self.param_two(instruction, |val_a, val_b| {
|
self.param_two(instruction, |val_a, val_b| val_a.compare(val_b));
|
||||||
val_a
|
|
||||||
.simd_gt(val_b)
|
|
||||||
.select(VALUE_1, val_a.simd_lt(val_b).select(VALUE_M1, VALUE_0))
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
SSAAnd => {
|
SSAAnd => {
|
||||||
self.param_two(instruction, |val_a, val_b| val_a.and_choice(val_b).0);
|
self.param_two(instruction, |val_a, val_b| val_a.and_choice(val_b).0);
|
||||||
|
|||||||
+32
-27
@@ -88,7 +88,7 @@ use winit::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod gui;
|
mod gui;
|
||||||
use crate::gui::*;
|
use crate::{gui::*, implicit_vs::PushConstantData};
|
||||||
mod objects;
|
mod objects;
|
||||||
use crate::objects::*;
|
use crate::objects::*;
|
||||||
|
|
||||||
@@ -97,9 +97,11 @@ mod ssa;
|
|||||||
mod instruction_set;
|
mod instruction_set;
|
||||||
|
|
||||||
mod interpreter;
|
mod interpreter;
|
||||||
mod interval;
|
|
||||||
use crate::interpreter::{IntervalInterpreter, PointInterpreter};
|
use crate::interpreter::{IntervalInterpreter, PointInterpreter};
|
||||||
|
|
||||||
|
mod types;
|
||||||
|
mod vm;
|
||||||
|
|
||||||
const MSAA_SAMPLES_ACTUAL: u32 = if MSAA_ENABLE { MSAA_SAMPLES } else { 1 };
|
const MSAA_SAMPLES_ACTUAL: u32 = if MSAA_ENABLE { MSAA_SAMPLES } else { 1 };
|
||||||
|
|
||||||
fn main() -> Result<(), impl Error> {
|
fn main() -> Result<(), impl Error> {
|
||||||
@@ -292,6 +294,32 @@ fn create_csg() -> SSATape {
|
|||||||
tape
|
tape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interval_check(csg: &CSG, push_constants: &mut PushConstantData) {
|
||||||
|
const INTERPRET_INPUT_X: interpreter::Value =
|
||||||
|
interpreter::Value::from_array([10000.0, 0.0, 0.0, -10000.0, 0.0, 0.0, 0.0, 0.0]);
|
||||||
|
const INTERPRET_INPUT_Y: interpreter::Value =
|
||||||
|
interpreter::Value::from_array([0.0, 10000.0, 0.0, 0.0, -10000.0, 0.0, 0.0, 0.0]);
|
||||||
|
const INTERPRET_INPUT_Z: interpreter::Value =
|
||||||
|
interpreter::Value::from_array([0.0, 0.0, 10000.0, 0.0, 0.0, -10000.0, 0.0, 0.0]);
|
||||||
|
const INTERPRET_MUL: interpreter::Value =
|
||||||
|
interpreter::Value::from_array([-1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 0.0]);
|
||||||
|
const INTERPRET_ADD: interpreter::Value = interpreter::Value::from_array([
|
||||||
|
10000.0, 10000.0, 10000.0, -10000.0, -10000.0, -10000.0, 0.0, 0.0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mut interpreter = PointInterpreter::new(csg);
|
||||||
|
let interpreter_out = interpreter
|
||||||
|
.scene(INTERPRET_INPUT_X, INTERPRET_INPUT_Y, INTERPRET_INPUT_Z)
|
||||||
|
.mul_add(INTERPRET_MUL, INTERPRET_ADD)
|
||||||
|
.to_array();
|
||||||
|
|
||||||
|
let highest_corner = Vec3::new(interpreter_out[0], interpreter_out[1], interpreter_out[2]);
|
||||||
|
let lowest_corner = Vec3::new(interpreter_out[3], interpreter_out[4], interpreter_out[5]);
|
||||||
|
|
||||||
|
push_constants.dimensions = (highest_corner - lowest_corner).to_array().into();
|
||||||
|
push_constants.lowest_corner = lowest_corner.into();
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new(event_loop: &EventLoop<()>) -> Self {
|
fn new(event_loop: &EventLoop<()>) -> Self {
|
||||||
let library = VulkanLibrary::new().expect("Vulkan is not installed???");
|
let library = VulkanLibrary::new().expect("Vulkan is not installed???");
|
||||||
@@ -1166,36 +1194,13 @@ impl App {
|
|||||||
* Mat4::from_scale(csg.scale * 2.0))
|
* Mat4::from_scale(csg.scale * 2.0))
|
||||||
.to_cols_array_2d();
|
.to_cols_array_2d();
|
||||||
|
|
||||||
const INTERPRET_INPUT_X: interpreter::Value =
|
interval_check(csg, &mut push_constants);
|
||||||
interpreter::Value::from_array([10000.0, 0.0, 0.0, -10000.0, 0.0, 0.0, 0.0, 0.0]);
|
|
||||||
const INTERPRET_INPUT_Y: interpreter::Value =
|
|
||||||
interpreter::Value::from_array([0.0, 10000.0, 0.0, 0.0, -10000.0, 0.0, 0.0, 0.0]);
|
|
||||||
const INTERPRET_INPUT_Z: interpreter::Value =
|
|
||||||
interpreter::Value::from_array([0.0, 0.0, 10000.0, 0.0, 0.0, -10000.0, 0.0, 0.0]);
|
|
||||||
const INTERPRET_MUL: interpreter::Value =
|
|
||||||
interpreter::Value::from_array([-1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 0.0]);
|
|
||||||
const INTERPRET_ADD: interpreter::Value = interpreter::Value::from_array([
|
|
||||||
10000.0, 10000.0, 10000.0, -10000.0, -10000.0, -10000.0, 0.0, 0.0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mut interpreter = PointInterpreter::new(csg);
|
|
||||||
let interpreter_out = interpreter
|
|
||||||
.scene(INTERPRET_INPUT_X, INTERPRET_INPUT_Y, INTERPRET_INPUT_Z)
|
|
||||||
.mul_add(INTERPRET_MUL, INTERPRET_ADD)
|
|
||||||
.to_array();
|
|
||||||
|
|
||||||
let highest_corner =
|
|
||||||
Vec3::new(interpreter_out[0], interpreter_out[1], interpreter_out[2]);
|
|
||||||
let lowest_corner =
|
|
||||||
Vec3::new(interpreter_out[3], interpreter_out[4], interpreter_out[5]);
|
|
||||||
|
|
||||||
push_constants.dimensions = (highest_corner - lowest_corner).to_array().into();
|
|
||||||
push_constants.lowest_corner = lowest_corner.into();
|
|
||||||
builder
|
builder
|
||||||
.push_constants(rcx.implicit_pipeline.layout().clone(), 0, push_constants)
|
.push_constants(rcx.implicit_pipeline.layout().clone(), 0, push_constants)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
unsafe { builder.draw(VERTEX_COUNT as u32, (VERTEX_COUNT / 3) as u32, 0, 0) }.unwrap();
|
unsafe { builder.draw(VERTEX_COUNT as u32, 1, 0, 0) }.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
|
|||||||
@@ -0,0 +1,365 @@
|
|||||||
|
use glam::Vec4;
|
||||||
|
|
||||||
|
/// A point in space with associated partial derivatives.
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Grad {
|
||||||
|
/// Value of the distance field at this point
|
||||||
|
pub v: f32,
|
||||||
|
/// Partial derivative with respect to `x`
|
||||||
|
pub dx: f32,
|
||||||
|
/// Partial derivative with respect to `y`
|
||||||
|
pub dy: f32,
|
||||||
|
/// Partial derivative with respect to `z`
|
||||||
|
pub dz: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Grad {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "({}, {}, {}, {})", self.v, self.dx, self.dy, self.dz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grad {
|
||||||
|
/// Constructs a new gradient
|
||||||
|
#[inline]
|
||||||
|
pub fn new(v: f32, dx: f32, dy: f32, dz: f32) -> Self {
|
||||||
|
Self { v, dx, dy, dz }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks up a gradient by index (0 = x, 1 = y, 2 = z)
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If the index is not in the 0-2 range
|
||||||
|
#[inline]
|
||||||
|
pub fn d(&self, i: usize) -> f32 {
|
||||||
|
match i {
|
||||||
|
0 => self.dx,
|
||||||
|
1 => self.dy,
|
||||||
|
2 => self.dz,
|
||||||
|
_ => panic!("invalid index {i}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Absolute value
|
||||||
|
#[inline]
|
||||||
|
pub fn abs(self) -> Self {
|
||||||
|
if self.v < 0.0 {
|
||||||
|
Grad {
|
||||||
|
v: -self.v,
|
||||||
|
dx: -self.dx,
|
||||||
|
dy: -self.dy,
|
||||||
|
dz: -self.dz,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Square root
|
||||||
|
#[inline]
|
||||||
|
pub fn sqrt(self) -> Self {
|
||||||
|
let v = self.v.sqrt();
|
||||||
|
Grad {
|
||||||
|
v,
|
||||||
|
dx: self.dx / (2.0 * v),
|
||||||
|
dy: self.dy / (2.0 * v),
|
||||||
|
dz: self.dz / (2.0 * v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sine
|
||||||
|
#[inline]
|
||||||
|
pub fn sin(self) -> Self {
|
||||||
|
let c = self.v.cos();
|
||||||
|
Grad {
|
||||||
|
v: self.v.sin(),
|
||||||
|
dx: self.dx * c,
|
||||||
|
dy: self.dy * c,
|
||||||
|
dz: self.dz * c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cosine
|
||||||
|
#[inline]
|
||||||
|
pub fn cos(self) -> Self {
|
||||||
|
let s = -self.v.sin();
|
||||||
|
Grad {
|
||||||
|
v: self.v.cos(),
|
||||||
|
dx: self.dx * s,
|
||||||
|
dy: self.dy * s,
|
||||||
|
dz: self.dz * s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tangent
|
||||||
|
#[inline]
|
||||||
|
pub fn tan(self) -> Self {
|
||||||
|
let c = self.v.cos().powi(2);
|
||||||
|
Grad {
|
||||||
|
v: self.v.tan(),
|
||||||
|
dx: self.dx / c,
|
||||||
|
dy: self.dy / c,
|
||||||
|
dz: self.dz / c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arcsin
|
||||||
|
#[inline]
|
||||||
|
pub fn asin(self) -> Self {
|
||||||
|
let r = (1.0 - self.v.powi(2)).sqrt();
|
||||||
|
Grad {
|
||||||
|
v: self.v.asin(),
|
||||||
|
dx: self.dx / r,
|
||||||
|
dy: self.dy / r,
|
||||||
|
dz: self.dz / r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arccos
|
||||||
|
#[inline]
|
||||||
|
pub fn acos(self) -> Self {
|
||||||
|
let r = (1.0 - self.v.powi(2)).sqrt();
|
||||||
|
Grad {
|
||||||
|
v: self.v.acos(),
|
||||||
|
dx: -self.dx / r,
|
||||||
|
dy: -self.dy / r,
|
||||||
|
dz: -self.dz / r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arctangent
|
||||||
|
#[inline]
|
||||||
|
pub fn atan(self) -> Self {
|
||||||
|
let r = self.v.powi(2) + 1.0;
|
||||||
|
Grad {
|
||||||
|
v: self.v.atan(),
|
||||||
|
dx: self.dx / r,
|
||||||
|
dy: self.dy / r,
|
||||||
|
dz: self.dz / r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exponential function
|
||||||
|
#[inline]
|
||||||
|
pub fn exp(self) -> Self {
|
||||||
|
let v = self.v.exp();
|
||||||
|
Grad {
|
||||||
|
v,
|
||||||
|
dx: v * self.dx,
|
||||||
|
dy: v * self.dy,
|
||||||
|
dz: v * self.dz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Natural log
|
||||||
|
#[inline]
|
||||||
|
pub fn ln(self) -> Self {
|
||||||
|
Grad {
|
||||||
|
v: self.v.ln(),
|
||||||
|
dx: self.dx / self.v,
|
||||||
|
dy: self.dy / self.v,
|
||||||
|
dz: self.dz / self.v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reciprocal
|
||||||
|
#[inline]
|
||||||
|
pub fn recip(self) -> Self {
|
||||||
|
let v2 = -self.v.powi(2);
|
||||||
|
Grad {
|
||||||
|
v: 1.0 / self.v,
|
||||||
|
dx: self.dx / v2,
|
||||||
|
dy: self.dy / v2,
|
||||||
|
dz: self.dz / v2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimum of two values
|
||||||
|
#[inline]
|
||||||
|
pub fn min(self, rhs: Self) -> Self {
|
||||||
|
if self.v < rhs.v { self } else { rhs }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum of two values
|
||||||
|
#[inline]
|
||||||
|
pub fn max(self, rhs: Self) -> Self {
|
||||||
|
if self.v > rhs.v { self } else { rhs }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Least non-negative remainder
|
||||||
|
#[inline]
|
||||||
|
pub fn rem_euclid(&self, rhs: Grad) -> Self {
|
||||||
|
let e = self.v.div_euclid(rhs.v);
|
||||||
|
Grad {
|
||||||
|
v: self.v.rem_euclid(rhs.v),
|
||||||
|
dx: self.dx - rhs.dx * e,
|
||||||
|
dy: self.dy - rhs.dy * e,
|
||||||
|
dz: self.dz - rhs.dz * e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snap to the largest less-than-or-equal value
|
||||||
|
#[inline]
|
||||||
|
pub fn floor(&self) -> Self {
|
||||||
|
Grad {
|
||||||
|
v: self.v.floor(),
|
||||||
|
dx: 0.0,
|
||||||
|
dy: 0.0,
|
||||||
|
dz: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snap to the smallest greater-than-or-equal value
|
||||||
|
#[inline]
|
||||||
|
pub fn ceil(&self) -> Self {
|
||||||
|
Grad {
|
||||||
|
v: self.v.ceil(),
|
||||||
|
dx: 0.0,
|
||||||
|
dy: 0.0,
|
||||||
|
dz: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rounds to the nearest integer
|
||||||
|
#[inline]
|
||||||
|
pub fn round(&self) -> Self {
|
||||||
|
Grad {
|
||||||
|
v: self.v.round(),
|
||||||
|
dx: 0.0,
|
||||||
|
dy: 0.0,
|
||||||
|
dz: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Four-quadrant arctangent
|
||||||
|
#[inline]
|
||||||
|
pub fn atan2(self, x: Self) -> Self {
|
||||||
|
let y = self;
|
||||||
|
|
||||||
|
let d = x.v.powi(2) + y.v.powi(2);
|
||||||
|
Grad {
|
||||||
|
v: y.v.atan2(x.v),
|
||||||
|
dx: (x.v * y.dx - y.v * x.dx) / d,
|
||||||
|
dy: (x.v * y.dy - y.v * x.dy) / d,
|
||||||
|
dz: (x.v * y.dz - y.v * x.dz) / d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that the two values are roughly equal, panicking otherwise
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn compare_eq(&self, other: Self) {
|
||||||
|
let d = (self.v - other.v)
|
||||||
|
.abs()
|
||||||
|
.max((self.dx - other.dx).abs())
|
||||||
|
.max((self.dy - other.dy).abs())
|
||||||
|
.max((self.dz - other.dz).abs());
|
||||||
|
if d >= 1e-6 {
|
||||||
|
panic!("lhs != rhs ({self:?} != {other:?})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f32> for Grad {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: f32) -> Self {
|
||||||
|
Grad {
|
||||||
|
v,
|
||||||
|
dx: 0.0,
|
||||||
|
dy: 0.0,
|
||||||
|
dz: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Grad> for Vec4 {
|
||||||
|
#[inline]
|
||||||
|
fn from(g: Grad) -> Self {
|
||||||
|
Vec4::new(g.dx, g.dy, g.dz, g.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add<Grad> for Grad {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Grad {
|
||||||
|
v: self.v + rhs.v,
|
||||||
|
dx: self.dx + rhs.dx,
|
||||||
|
dy: self.dy + rhs.dy,
|
||||||
|
dz: self.dz + rhs.dz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Grad> for Grad {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mul(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
v: self.v * rhs.v,
|
||||||
|
dx: self.v * rhs.dx + rhs.v * self.dx,
|
||||||
|
dy: self.v * rhs.dy + rhs.v * self.dy,
|
||||||
|
dz: self.v * rhs.dz + rhs.v * self.dz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<f32> for Grad {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mul(self, rhs: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
v: self.v * rhs,
|
||||||
|
dx: self.dx * rhs,
|
||||||
|
dy: self.dy * rhs,
|
||||||
|
dz: self.dz * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div<Grad> for Grad {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn div(self, rhs: Self) -> Self {
|
||||||
|
let d = rhs.v.powi(2);
|
||||||
|
Self {
|
||||||
|
v: self.v / rhs.v,
|
||||||
|
dx: (rhs.v * self.dx - self.v * rhs.dx) / d,
|
||||||
|
dy: (rhs.v * self.dy - self.v * rhs.dy) / d,
|
||||||
|
dz: (rhs.v * self.dz - self.v * rhs.dz) / d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<Grad> for Grad {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn sub(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
v: self.v - rhs.v,
|
||||||
|
dx: self.dx - rhs.dx,
|
||||||
|
dy: self.dy - rhs.dy,
|
||||||
|
dz: self.dz - rhs.dz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Neg for Grad {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn neg(self) -> Self {
|
||||||
|
Self {
|
||||||
|
v: -self.v,
|
||||||
|
dx: -self.dx,
|
||||||
|
dy: -self.dy,
|
||||||
|
dz: -self.dz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// Based on https://github.com/mkeeter/fidget/blob/main/fidget/src/core/types/interval.rs
|
|
||||||
|
|
||||||
use std::simd::{
|
use std::simd::{
|
||||||
StdFloat,
|
StdFloat,
|
||||||
cmp::{SimdPartialEq, SimdPartialOrd},
|
cmp::{SimdPartialEq, SimdPartialOrd},
|
||||||
@@ -7,100 +5,14 @@ use std::simd::{
|
|||||||
u32x8,
|
u32x8,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::interpreter::{
|
use crate::{
|
||||||
Mask, VALUE_0, VALUE_1, VALUE_2, VALUE_05, VALUE_M1, VALUE_NAN, VALUE_PI, Value,
|
interpreter::{
|
||||||
|
Mask, VALUE_0, VALUE_1, VALUE_2, VALUE_05, VALUE_M1, VALUE_NAN, VALUE_PI, Value, glfract,
|
||||||
|
glsign,
|
||||||
|
},
|
||||||
|
vm::choice::{Choice, VChoice},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A single choice made at a min/max node.
|
|
||||||
///
|
|
||||||
/// Explicitly stored in a `u8` so that this can be written by JIT functions,
|
|
||||||
/// which have no notion of Rust enums.
|
|
||||||
///
|
|
||||||
/// Note that this is a bitfield such that
|
|
||||||
/// ```rust
|
|
||||||
/// # use fidget::vm::Choice;
|
|
||||||
/// # assert!(
|
|
||||||
/// Choice::Both as u8 == Choice::Left as u8 | Choice::Right as u8
|
|
||||||
/// # );
|
|
||||||
/// ```
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Choice {
|
|
||||||
/// This choice has not yet been assigned
|
|
||||||
///
|
|
||||||
/// A value of `Unknown` is invalid after evaluation
|
|
||||||
Unknown = 0,
|
|
||||||
|
|
||||||
/// The operation always picks the left-hand input
|
|
||||||
Left = 1,
|
|
||||||
|
|
||||||
/// The operation always picks the right-hand input
|
|
||||||
Right = 2,
|
|
||||||
|
|
||||||
/// The operation may pick either input
|
|
||||||
Both = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::BitOrAssign<Choice> for Choice {
|
|
||||||
fn bitor_assign(&mut self, other: Self) {
|
|
||||||
*self = match (*self as u8) | (other as u8) {
|
|
||||||
0 => Self::Unknown,
|
|
||||||
1 => Self::Left,
|
|
||||||
2 => Self::Right,
|
|
||||||
3 => Self::Both,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Not for Choice {
|
|
||||||
type Output = Choice;
|
|
||||||
|
|
||||||
fn not(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Unknown => Self::Both,
|
|
||||||
Self::Left => Self::Right,
|
|
||||||
Self::Right => Self::Left,
|
|
||||||
Self::Both => Self::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::BitAndAssign<Choice> for Choice {
|
|
||||||
fn bitand_assign(&mut self, other: Self) {
|
|
||||||
*self = match (*self as u8) | ((!other as u8) & 0b11) {
|
|
||||||
0 => Self::Unknown,
|
|
||||||
1 => Self::Left,
|
|
||||||
2 => Self::Right,
|
|
||||||
3 => Self::Both,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VChoice(u32x8);
|
|
||||||
|
|
||||||
impl VChoice {
|
|
||||||
pub const BOTH: Self = Self(u32x8::splat(Choice::Both as u32));
|
|
||||||
pub const LEFT: Self = Self(u32x8::splat(Choice::Left as u32));
|
|
||||||
pub const RIGHT: Self = Self(u32x8::splat(Choice::Right as u32));
|
|
||||||
pub const UNKNOWN: Self = Self(u32x8::splat(Choice::Unknown as u32));
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Not for VChoice {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn not(self) -> Self {
|
|
||||||
VChoice(u32x8::splat(3) - self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::BitAndAssign<VChoice> for VChoice {
|
|
||||||
fn bitand_assign(&mut self, other: Self) {
|
|
||||||
self.0 = (self.0) | ((!other.0) & u32x8::splat(3))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a range, with conservative calculations to guarantee that it always
|
/// Stores a range, with conservative calculations to guarantee that it always
|
||||||
/// contains the actual value.
|
/// contains the actual value.
|
||||||
///
|
///
|
||||||
@@ -602,6 +514,56 @@ impl Interval {
|
|||||||
has_nan.select(VALUE_NAN, VALUE_PI),
|
has_nan.select(VALUE_NAN, VALUE_PI),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sign(self) -> Interval {
|
||||||
|
Interval::new(glsign(self.lower), glsign(self.upper))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fract(self) -> Interval {
|
||||||
|
let ge1 = (self.upper - self.lower).simd_ge(VALUE_1);
|
||||||
|
Interval::new(
|
||||||
|
ge1.select(VALUE_0, glfract(self.lower)),
|
||||||
|
ge1.select(VALUE_1, glfract(self.upper)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compare(self, other: Interval) -> Interval {
|
||||||
|
let has_nan = self.has_nan() | other.has_nan();
|
||||||
|
let check1 = self.upper.simd_lt(other.lower);
|
||||||
|
let check2 = other.upper.simd_lt(self.lower);
|
||||||
|
let check3 = other.upper.simd_eq(self.lower);
|
||||||
|
let check4 = self.upper.simd_eq(other.lower);
|
||||||
|
let lower = has_nan.select(
|
||||||
|
VALUE_NAN,
|
||||||
|
check1.select(
|
||||||
|
VALUE_M1,
|
||||||
|
check2.select(
|
||||||
|
VALUE_1,
|
||||||
|
check3.select(VALUE_0, check4.select(VALUE_M1, VALUE_M1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let upper = has_nan.select(
|
||||||
|
VALUE_NAN,
|
||||||
|
check1.select(
|
||||||
|
VALUE_M1,
|
||||||
|
check2.select(
|
||||||
|
VALUE_1,
|
||||||
|
check3.select(VALUE_1, check4.select(VALUE_0, VALUE_1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Interval::new(lower, upper)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp(self, min: Interval, max: Interval) -> Interval {
|
||||||
|
Interval::new(
|
||||||
|
self.lower.simd_clamp(min.lower, max.lower),
|
||||||
|
self.upper.simd_clamp(min.upper, max.upper),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Interval {
|
impl std::fmt::Display for Interval {
|
||||||
@@ -624,6 +586,19 @@ impl From<Value> for Interval {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Not for Interval {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn not(self) -> Self::Output {
|
||||||
|
let has_nan = self.has_nan();
|
||||||
|
let is_zero = self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0);
|
||||||
|
let crosses_zero = self.lower.simd_le(VALUE_0) & self.upper.simd_ge(VALUE_0);
|
||||||
|
let lower = has_nan.select(VALUE_NAN, is_zero.select(VALUE_1, VALUE_0));
|
||||||
|
let upper = has_nan.select(VALUE_NAN, crosses_zero.select(VALUE_1, VALUE_0));
|
||||||
|
Interval::new(lower, upper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Rem<Interval> for Interval {
|
impl std::ops::Rem<Interval> for Interval {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
@@ -743,17 +718,3 @@ impl std::ops::Neg for Interval {
|
|||||||
Interval::new(-self.upper, -self.lower)
|
Interval::new(-self.upper, -self.lower)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_interval() {
|
|
||||||
let a = Interval::new(Value::splat(0.0), Value::splat(1.0));
|
|
||||||
let b = Interval::new(Value::splat(0.5), Value::splat(1.5));
|
|
||||||
let (v, c) = a.min_choice(b);
|
|
||||||
assert_eq!(v, [Value::splat(0.0), Value::splat(1.0)].into());
|
|
||||||
assert_eq!(c.0, VChoice::BOTH.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//! Custom types used during evaluation
|
||||||
|
|
||||||
|
mod grad;
|
||||||
|
mod interval;
|
||||||
|
pub use grad::Grad;
|
||||||
|
pub use interval::Interval;
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use std::simd::{
|
||||||
|
StdFloat,
|
||||||
|
cmp::{SimdPartialEq, SimdPartialOrd},
|
||||||
|
num::SimdFloat,
|
||||||
|
u32x8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A single choice made at a min/max node.
|
||||||
|
///
|
||||||
|
/// Explicitly stored in a `u8` so that this can be written by JIT functions,
|
||||||
|
/// which have no notion of Rust enums.
|
||||||
|
///
|
||||||
|
/// Note that this is a bitfield such that
|
||||||
|
/// ```rust
|
||||||
|
/// # use fidget::vm::Choice;
|
||||||
|
/// # assert!(
|
||||||
|
/// Choice::Both as u8 == Choice::Left as u8 | Choice::Right as u8
|
||||||
|
/// # );
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Choice {
|
||||||
|
/// This choice has not yet been assigned
|
||||||
|
///
|
||||||
|
/// A value of `Unknown` is invalid after evaluation
|
||||||
|
Unknown = 0,
|
||||||
|
|
||||||
|
/// The operation always picks the left-hand input
|
||||||
|
Left = 1,
|
||||||
|
|
||||||
|
/// The operation always picks the right-hand input
|
||||||
|
Right = 2,
|
||||||
|
|
||||||
|
/// The operation may pick either input
|
||||||
|
Both = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::BitOrAssign<Choice> for Choice {
|
||||||
|
fn bitor_assign(&mut self, other: Self) {
|
||||||
|
*self = match (*self as u8) | (other as u8) {
|
||||||
|
0 => Self::Unknown,
|
||||||
|
1 => Self::Left,
|
||||||
|
2 => Self::Right,
|
||||||
|
3 => Self::Both,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Not for Choice {
|
||||||
|
type Output = Choice;
|
||||||
|
|
||||||
|
fn not(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Unknown => Self::Both,
|
||||||
|
Self::Left => Self::Right,
|
||||||
|
Self::Right => Self::Left,
|
||||||
|
Self::Both => Self::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::BitAndAssign<Choice> for Choice {
|
||||||
|
fn bitand_assign(&mut self, other: Self) {
|
||||||
|
*self = match (*self as u8) | ((!other as u8) & 0b11) {
|
||||||
|
0 => Self::Unknown,
|
||||||
|
1 => Self::Left,
|
||||||
|
2 => Self::Right,
|
||||||
|
3 => Self::Both,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VChoice(pub u32x8);
|
||||||
|
|
||||||
|
impl VChoice {
|
||||||
|
pub const BOTH: Self = Self(u32x8::splat(Choice::Both as u32));
|
||||||
|
pub const LEFT: Self = Self(u32x8::splat(Choice::Left as u32));
|
||||||
|
pub const RIGHT: Self = Self(u32x8::splat(Choice::Right as u32));
|
||||||
|
pub const UNKNOWN: Self = Self(u32x8::splat(Choice::Unknown as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Not for VChoice {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn not(self) -> Self {
|
||||||
|
VChoice(u32x8::splat(3) - self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::BitAndAssign<VChoice> for VChoice {
|
||||||
|
fn bitand_assign(&mut self, other: Self) {
|
||||||
|
self.0 = (self.0) | ((!other.0) & u32x8::splat(3))
|
||||||
|
}
|
||||||
|
}
|
||||||
+418
@@ -0,0 +1,418 @@
|
|||||||
|
//! General-purpose tapes for use during evaluation or further compilation
|
||||||
|
use crate::{
|
||||||
|
Error,
|
||||||
|
compiler::{RegOp, RegTape, RegisterAllocator, SsaOp, SsaTape},
|
||||||
|
context::{Context, Node},
|
||||||
|
var::VarMap,
|
||||||
|
vm::Choice,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A flattened math expression, ready for evaluation or further compilation.
|
||||||
|
///
|
||||||
|
/// Under the hood, [`VmData`] stores two different representations:
|
||||||
|
/// - A tape in [single static assignment form](https://en.wikipedia.org/wiki/Static_single-assignment_form)
|
||||||
|
/// ([`SsaTape`]), which is suitable for use during tape simplification
|
||||||
|
/// - A tape in register-allocated form ([`RegTape`]), which can be efficiently
|
||||||
|
/// evaluated or lowered into machine assembly
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// Consider the expression `x + y`. The SSA tape will look something like
|
||||||
|
/// this:
|
||||||
|
/// ```text
|
||||||
|
/// $0 = INPUT 0 // X
|
||||||
|
/// $1 = INPUT 1 // Y
|
||||||
|
/// $2 = ADD $0 $1 // (X + Y)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This will be lowered into a tape using real (or VM) registers:
|
||||||
|
/// ```text
|
||||||
|
/// r0 = INPUT 0 // X
|
||||||
|
/// r1 = INPUT 1 // Y
|
||||||
|
/// r0 = ADD r0 r1 // (X + Y)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that in this form, registers are reused (e.g. `r0` stores both `X` and
|
||||||
|
/// `X + Y`).
|
||||||
|
///
|
||||||
|
/// We can peek at the internals and see this register-allocated tape:
|
||||||
|
/// ```
|
||||||
|
/// use fidget::{
|
||||||
|
/// compiler::RegOp,
|
||||||
|
/// context::{Context, Tree},
|
||||||
|
/// vm::VmData,
|
||||||
|
/// var::Var,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let tree = Tree::x() + Tree::y();
|
||||||
|
/// let mut ctx = Context::new();
|
||||||
|
/// let sum = ctx.import(&tree);
|
||||||
|
/// let data = VmData::<255>::new(&ctx, &[sum])?;
|
||||||
|
/// assert_eq!(data.len(), 4); // X, Y, (X + Y), and output
|
||||||
|
///
|
||||||
|
/// let mut iter = data.iter_asm();
|
||||||
|
/// let vars = &data.vars; // map from var to index
|
||||||
|
/// assert_eq!(iter.next().unwrap(), RegOp::Input(0, vars[&Var::X] as u32));
|
||||||
|
/// assert_eq!(iter.next().unwrap(), RegOp::Input(1, vars[&Var::Y] as u32));
|
||||||
|
/// assert_eq!(iter.next().unwrap(), RegOp::AddRegReg(0, 0, 1));
|
||||||
|
/// # Ok::<(), fidget::Error>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Despite this peek at its internals, users are unlikely to touch `VmData`
|
||||||
|
/// directly; a [`VmShape`](crate::vm::VmShape) wraps the `VmData` and
|
||||||
|
/// implements our common traits.
|
||||||
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
|
pub struct VmData<const N: usize = { u8::MAX as usize }> {
|
||||||
|
ssa: SsaTape,
|
||||||
|
asm: RegTape,
|
||||||
|
|
||||||
|
/// Mapping from variables to indices during evaluation
|
||||||
|
///
|
||||||
|
/// This member is stored in a shared pointer because it's passed down to
|
||||||
|
/// children (constructed with [`VmData::simplify`]).
|
||||||
|
pub vars: Arc<VarMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> VmData<N> {
|
||||||
|
/// Builds a new tape for the given node
|
||||||
|
pub fn new(context: &Context, nodes: &[Node]) -> Result<Self, Error> {
|
||||||
|
let (ssa, vars) = SsaTape::new(context, nodes)?;
|
||||||
|
let asm = RegTape::new::<N>(&ssa);
|
||||||
|
Ok(Self {
|
||||||
|
ssa,
|
||||||
|
asm,
|
||||||
|
vars: vars.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the internal VM tape
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.asm.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the internal VM tape is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.asm.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of choice (min/max) nodes in the tape.
|
||||||
|
///
|
||||||
|
/// This is required because some evaluators pre-allocate spaces for the
|
||||||
|
/// choice array.
|
||||||
|
pub fn choice_count(&self) -> usize {
|
||||||
|
self.ssa.choice_count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of output nodes in the tape.
|
||||||
|
///
|
||||||
|
/// This is required because some evaluators pre-allocate spaces for the
|
||||||
|
/// output array.
|
||||||
|
pub fn output_count(&self) -> usize {
|
||||||
|
self.ssa.output_count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of slots used by the inner VM tape
|
||||||
|
pub fn slot_count(&self) -> usize {
|
||||||
|
self.asm.slot_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simplifies both inner tapes, using the provided choice array
|
||||||
|
///
|
||||||
|
/// To minimize allocations, this function takes a [`VmWorkspace`] and
|
||||||
|
/// spare [`VmData`]; it will reuse those allocations.
|
||||||
|
pub fn simplify<const M: usize>(
|
||||||
|
&self,
|
||||||
|
choices: &[Choice],
|
||||||
|
workspace: &mut VmWorkspace<M>,
|
||||||
|
mut tape: VmData<M>,
|
||||||
|
) -> Result<VmData<M>, Error> {
|
||||||
|
if choices.len() != self.choice_count() {
|
||||||
|
return Err(Error::BadChoiceSlice(
|
||||||
|
choices.len(),
|
||||||
|
self.choice_count(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tape.ssa.reset();
|
||||||
|
|
||||||
|
// Steal `tape.asm` and hand it to the workspace for use in allocator
|
||||||
|
workspace.reset(self.ssa.tape.len(), tape.asm);
|
||||||
|
|
||||||
|
let mut choice_count = 0;
|
||||||
|
let mut output_count = 0;
|
||||||
|
|
||||||
|
// Other iterators to consume various arrays in order
|
||||||
|
let mut choice_iter = choices.iter().rev();
|
||||||
|
|
||||||
|
let mut ops_out = tape.ssa.tape;
|
||||||
|
|
||||||
|
for mut op in self.ssa.tape.iter().cloned() {
|
||||||
|
let index = match &mut op {
|
||||||
|
SsaOp::Output(reg, _i) => {
|
||||||
|
*reg = workspace.get_or_insert_active(*reg);
|
||||||
|
workspace.alloc.op(op);
|
||||||
|
ops_out.push(op);
|
||||||
|
output_count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => op.output().unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if workspace.active(index).is_none() {
|
||||||
|
if op.has_choice() {
|
||||||
|
choice_iter.next().unwrap();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because we reassign nodes when they're used as an *input*
|
||||||
|
// (while walking the tape in reverse), this node must have been
|
||||||
|
// assigned already.
|
||||||
|
let new_index = workspace.active(index).unwrap();
|
||||||
|
|
||||||
|
match &mut op {
|
||||||
|
SsaOp::Output(..) => unreachable!(),
|
||||||
|
SsaOp::Input(index, ..) | SsaOp::CopyImm(index, ..) => {
|
||||||
|
*index = new_index;
|
||||||
|
}
|
||||||
|
SsaOp::NegReg(index, arg)
|
||||||
|
| SsaOp::AbsReg(index, arg)
|
||||||
|
| SsaOp::RecipReg(index, arg)
|
||||||
|
| SsaOp::SqrtReg(index, arg)
|
||||||
|
| SsaOp::SquareReg(index, arg)
|
||||||
|
| SsaOp::FloorReg(index, arg)
|
||||||
|
| SsaOp::CeilReg(index, arg)
|
||||||
|
| SsaOp::RoundReg(index, arg)
|
||||||
|
| SsaOp::SinReg(index, arg)
|
||||||
|
| SsaOp::CosReg(index, arg)
|
||||||
|
| SsaOp::TanReg(index, arg)
|
||||||
|
| SsaOp::AsinReg(index, arg)
|
||||||
|
| SsaOp::AcosReg(index, arg)
|
||||||
|
| SsaOp::AtanReg(index, arg)
|
||||||
|
| SsaOp::ExpReg(index, arg)
|
||||||
|
| SsaOp::LnReg(index, arg)
|
||||||
|
| SsaOp::NotReg(index, arg) => {
|
||||||
|
*index = new_index;
|
||||||
|
*arg = workspace.get_or_insert_active(*arg);
|
||||||
|
}
|
||||||
|
SsaOp::CopyReg(index, src) => {
|
||||||
|
// CopyReg effectively does
|
||||||
|
// dst <= src
|
||||||
|
// If src has not yet been used (as we iterate backwards
|
||||||
|
// through the tape), then we can replace it with dst
|
||||||
|
// everywhere!
|
||||||
|
match workspace.active(*src) {
|
||||||
|
Some(new_src) => {
|
||||||
|
*index = new_index;
|
||||||
|
*src = new_src;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
workspace.set_active(*src, new_index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SsaOp::MinRegImm(index, arg, imm)
|
||||||
|
| SsaOp::MaxRegImm(index, arg, imm)
|
||||||
|
| SsaOp::AndRegImm(index, arg, imm)
|
||||||
|
| SsaOp::OrRegImm(index, arg, imm) => {
|
||||||
|
match choice_iter.next().unwrap() {
|
||||||
|
Choice::Left => match workspace.active(*arg) {
|
||||||
|
Some(new_arg) => {
|
||||||
|
op = SsaOp::CopyReg(new_index, new_arg);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
workspace.set_active(*arg, new_index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Choice::Right => {
|
||||||
|
op = SsaOp::CopyImm(new_index, *imm);
|
||||||
|
}
|
||||||
|
Choice::Both => {
|
||||||
|
choice_count += 1;
|
||||||
|
*index = new_index;
|
||||||
|
*arg = workspace.get_or_insert_active(*arg);
|
||||||
|
}
|
||||||
|
Choice::Unknown => panic!("oh no"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SsaOp::MinRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::MaxRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::AndRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::OrRegReg(index, lhs, rhs) => {
|
||||||
|
match choice_iter.next().unwrap() {
|
||||||
|
Choice::Left => match workspace.active(*lhs) {
|
||||||
|
Some(new_lhs) => {
|
||||||
|
op = SsaOp::CopyReg(new_index, new_lhs);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
workspace.set_active(*lhs, new_index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Choice::Right => match workspace.active(*rhs) {
|
||||||
|
Some(new_rhs) => {
|
||||||
|
op = SsaOp::CopyReg(new_index, new_rhs);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
workspace.set_active(*rhs, new_index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Choice::Both => {
|
||||||
|
choice_count += 1;
|
||||||
|
*index = new_index;
|
||||||
|
*lhs = workspace.get_or_insert_active(*lhs);
|
||||||
|
*rhs = workspace.get_or_insert_active(*rhs);
|
||||||
|
}
|
||||||
|
Choice::Unknown => panic!("oh no"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SsaOp::AddRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::MulRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::SubRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::DivRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::AtanRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::CompareRegReg(index, lhs, rhs)
|
||||||
|
| SsaOp::ModRegReg(index, lhs, rhs) => {
|
||||||
|
*index = new_index;
|
||||||
|
*lhs = workspace.get_or_insert_active(*lhs);
|
||||||
|
*rhs = workspace.get_or_insert_active(*rhs);
|
||||||
|
}
|
||||||
|
SsaOp::AddRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::MulRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::SubRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::SubImmReg(index, arg, _imm)
|
||||||
|
| SsaOp::DivRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::DivImmReg(index, arg, _imm)
|
||||||
|
| SsaOp::AtanImmReg(index, arg, _imm)
|
||||||
|
| SsaOp::AtanRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::CompareRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::CompareImmReg(index, arg, _imm)
|
||||||
|
| SsaOp::ModRegImm(index, arg, _imm)
|
||||||
|
| SsaOp::ModImmReg(index, arg, _imm) => {
|
||||||
|
*index = new_index;
|
||||||
|
*arg = workspace.get_or_insert_active(*arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workspace.alloc.op(op);
|
||||||
|
ops_out.push(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(workspace.count as usize + 1, ops_out.len());
|
||||||
|
let asm_tape = workspace.alloc.finalize();
|
||||||
|
|
||||||
|
Ok(VmData {
|
||||||
|
ssa: SsaTape {
|
||||||
|
tape: ops_out,
|
||||||
|
choice_count,
|
||||||
|
output_count,
|
||||||
|
},
|
||||||
|
asm: asm_tape,
|
||||||
|
vars: self.vars.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces an iterator that visits [`RegOp`] values in evaluation order
|
||||||
|
pub fn iter_asm(&self) -> impl Iterator<Item = RegOp> + '_ {
|
||||||
|
self.asm.iter().cloned().rev()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pretty-prints the inner SSA tape
|
||||||
|
pub fn pretty_print(&self) {
|
||||||
|
self.ssa.pretty_print();
|
||||||
|
for a in self.iter_asm() {
|
||||||
|
println!("{a:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Data structures used during [`VmData::simplify`]
|
||||||
|
///
|
||||||
|
/// This is exposed to minimize reallocations in hot loops.
|
||||||
|
pub struct VmWorkspace<const N: usize> {
|
||||||
|
/// Register allocator
|
||||||
|
pub(crate) alloc: RegisterAllocator<N>,
|
||||||
|
|
||||||
|
/// Current bindings from SSA variables to registers
|
||||||
|
pub(crate) bind: Vec<u32>,
|
||||||
|
|
||||||
|
/// Number of active SSA bindings
|
||||||
|
///
|
||||||
|
/// This value is monotonically increasing; each SSA variable gets the next
|
||||||
|
/// value if it is unassigned when encountered.
|
||||||
|
count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Default for VmWorkspace<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
alloc: RegisterAllocator::empty(),
|
||||||
|
bind: vec![],
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> VmWorkspace<N> {
|
||||||
|
fn active(&self, i: u32) -> Option<u32> {
|
||||||
|
if self.bind[i as usize] != u32::MAX {
|
||||||
|
Some(self.bind[i as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_or_insert_active(&mut self, i: u32) -> u32 {
|
||||||
|
if self.bind[i as usize] == u32::MAX {
|
||||||
|
self.bind[i as usize] = self.count;
|
||||||
|
self.count += 1;
|
||||||
|
}
|
||||||
|
self.bind[i as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_active(&mut self, i: u32, bind: u32) {
|
||||||
|
self.bind[i as usize] = bind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the workspace, preserving allocations and claiming the given
|
||||||
|
/// [`RegTape`].
|
||||||
|
pub fn reset(&mut self, tape_len: usize, tape: RegTape) {
|
||||||
|
self.alloc.reset(tape_len, tape);
|
||||||
|
self.bind.fill(u32::MAX);
|
||||||
|
self.bind.resize(tape_len, u32::MAX);
|
||||||
|
self.count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simplify_reg_count_change() {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
let x = ctx.x();
|
||||||
|
let y = ctx.y();
|
||||||
|
let z = ctx.z();
|
||||||
|
let xy = ctx.add(x, y).unwrap();
|
||||||
|
let xyz = ctx.add(xy, z).unwrap();
|
||||||
|
|
||||||
|
let data = VmData::<3>::new(&ctx, &[xyz]).unwrap();
|
||||||
|
assert_eq!(data.len(), 6); // 3x input, 2x add, 1x output
|
||||||
|
let next = data
|
||||||
|
.simplify::<2>(&[], &mut Default::default(), Default::default())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(next.len(), 8); // extra load + store
|
||||||
|
|
||||||
|
let data = VmData::<2>::new(&ctx, &[xyz]).unwrap();
|
||||||
|
assert_eq!(data.len(), 8);
|
||||||
|
let next = data
|
||||||
|
.simplify::<3>(&[], &mut Default::default(), Default::default())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(next.len(), 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod choice;
|
||||||
|
// mod data;
|
||||||
Reference in New Issue
Block a user