mod block;
mod helpers;
mod image;
mod index;
mod instructions;
mod layout;
mod ray;
mod recyclable;
mod selection;
mod subgroup;
mod writer;
pub use spirv::{Capability, SourceLanguage};
use alloc::{string::String, vec::Vec};
use core::ops;
use spirv::Word;
use thiserror::Error;
use crate::arena::{Handle, HandleVec};
use crate::path_like::PathLikeRef;
use crate::proc::{BoundsCheckPolicies, TypeResolution};
#[derive(Clone)]
struct PhysicalLayout {
magic_number: Word,
version: Word,
generator: Word,
bound: Word,
instruction_schema: Word,
}
#[derive(Default)]
struct LogicalLayout {
capabilities: Vec<Word>,
extensions: Vec<Word>,
ext_inst_imports: Vec<Word>,
memory_model: Vec<Word>,
entry_points: Vec<Word>,
execution_modes: Vec<Word>,
debugs: Vec<Word>,
annotations: Vec<Word>,
declarations: Vec<Word>,
function_declarations: Vec<Word>,
function_definitions: Vec<Word>,
}
struct Instruction {
op: spirv::Op,
wc: u32,
type_id: Option<Word>,
result_id: Option<Word>,
operands: Vec<Word>,
}
const BITS_PER_BYTE: crate::Bytes = 8;
#[derive(Clone, Debug, Error)]
pub enum Error {
#[error("The requested entry point couldn't be found")]
EntryPointNotFound,
#[error("target SPIRV-{0}.{1} is not supported")]
UnsupportedVersion(u8, u8),
#[error("using {0} requires at least one of the capabilities {1:?}, but none are available")]
MissingCapabilities(&'static str, Vec<Capability>),
#[error("unimplemented {0}")]
FeatureNotImplemented(&'static str),
#[error("module is not validated properly: {0}")]
Validation(&'static str),
#[error("overrides should not be present at this stage")]
Override,
#[error(transparent)]
ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
}
#[derive(Default)]
struct IdGenerator(Word);
impl IdGenerator {
fn next(&mut self) -> Word {
self.0 += 1;
self.0
}
}
#[derive(Debug, Clone)]
pub struct DebugInfo<'a> {
pub source_code: &'a str,
pub file_name: PathLikeRef<'a>,
pub language: SourceLanguage,
}
struct Block {
label_id: Word,
body: Vec<Instruction>,
}
struct TerminatedBlock {
label_id: Word,
body: Vec<Instruction>,
}
impl Block {
const fn new(label_id: Word) -> Self {
Block {
label_id,
body: Vec::new(),
}
}
}
struct LocalVariable {
id: Word,
instruction: Instruction,
}
struct ResultMember {
id: Word,
type_id: Word,
built_in: Option<crate::BuiltIn>,
}
struct EntryPointContext {
argument_ids: Vec<Word>,
results: Vec<ResultMember>,
}
#[derive(Default)]
struct Function {
signature: Option<Instruction>,
parameters: Vec<FunctionArgument>,
variables: crate::FastHashMap<Handle<crate::LocalVariable>, LocalVariable>,
force_loop_bounding_vars: Vec<LocalVariable>,
spilled_composites: crate::FastIndexMap<Handle<crate::Expression>, LocalVariable>,
spilled_accesses: crate::arena::HandleSet<crate::Expression>,
access_uses: crate::FastHashMap<Handle<crate::Expression>, usize>,
blocks: Vec<TerminatedBlock>,
entry_point_context: Option<EntryPointContext>,
}
impl Function {
fn consume(&mut self, mut block: Block, termination: Instruction) {
block.body.push(termination);
self.blocks.push(TerminatedBlock {
label_id: block.label_id,
body: block.body,
})
}
fn parameter_id(&self, index: u32) -> Word {
match self.entry_point_context {
Some(ref context) => context.argument_ids[index as usize],
None => self.parameters[index as usize]
.instruction
.result_id
.unwrap(),
}
}
}
#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
struct LocalImageType {
sampled_type: crate::Scalar,
dim: spirv::Dim,
flags: ImageTypeFlags,
image_format: spirv::ImageFormat,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ImageTypeFlags: u8 {
const DEPTH = 0x1;
const ARRAYED = 0x2;
const MULTISAMPLED = 0x4;
const SAMPLED = 0x8;
}
}
impl LocalImageType {
fn from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self {
let make_flags = |multi: bool, other: ImageTypeFlags| -> ImageTypeFlags {
let mut flags = other;
flags.set(ImageTypeFlags::ARRAYED, arrayed);
flags.set(ImageTypeFlags::MULTISAMPLED, multi);
flags
};
let dim = spirv::Dim::from(dim);
match class {
crate::ImageClass::Sampled { kind, multi } => LocalImageType {
sampled_type: crate::Scalar { kind, width: 4 },
dim,
flags: make_flags(multi, ImageTypeFlags::SAMPLED),
image_format: spirv::ImageFormat::Unknown,
},
crate::ImageClass::Depth { multi } => LocalImageType {
sampled_type: crate::Scalar {
kind: crate::ScalarKind::Float,
width: 4,
},
dim,
flags: make_flags(multi, ImageTypeFlags::DEPTH | ImageTypeFlags::SAMPLED),
image_format: spirv::ImageFormat::Unknown,
},
crate::ImageClass::Storage { format, access: _ } => LocalImageType {
sampled_type: format.into(),
dim,
flags: make_flags(false, ImageTypeFlags::empty()),
image_format: format.into(),
},
}
}
}
#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
enum NumericType {
Scalar(crate::Scalar),
Vector {
size: crate::VectorSize,
scalar: crate::Scalar,
},
Matrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
scalar: crate::Scalar,
},
}
impl NumericType {
const fn from_inner(inner: &crate::TypeInner) -> Option<Self> {
match *inner {
crate::TypeInner::Scalar(scalar) | crate::TypeInner::Atomic(scalar) => {
Some(NumericType::Scalar(scalar))
}
crate::TypeInner::Vector { size, scalar } => Some(NumericType::Vector { size, scalar }),
crate::TypeInner::Matrix {
columns,
rows,
scalar,
} => Some(NumericType::Matrix {
columns,
rows,
scalar,
}),
_ => None,
}
}
const fn scalar(self) -> crate::Scalar {
match self {
NumericType::Scalar(scalar)
| NumericType::Vector { scalar, .. }
| NumericType::Matrix { scalar, .. } => scalar,
}
}
const fn with_scalar(self, scalar: crate::Scalar) -> Self {
match self {
NumericType::Scalar(_) => NumericType::Scalar(scalar),
NumericType::Vector { size, .. } => NumericType::Vector { size, scalar },
NumericType::Matrix { columns, rows, .. } => NumericType::Matrix {
columns,
rows,
scalar,
},
}
}
}
#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
enum LocalType {
Numeric(NumericType),
Pointer {
base: Word,
class: spirv::StorageClass,
},
Image(LocalImageType),
SampledImage {
image_type_id: Word,
},
Sampler,
BindingArray {
base: Handle<crate::Type>,
size: u32,
},
AccelerationStructure,
RayQuery,
}
#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
enum LookupType {
Handle(Handle<crate::Type>),
Local(LocalType),
}
impl From<LocalType> for LookupType {
fn from(local: LocalType) -> Self {
Self::Local(local)
}
}
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
struct LookupFunctionType {
parameter_type_ids: Vec<Word>,
return_type_id: Word,
}
#[derive(Debug)]
enum Dimension {
Scalar,
Vector,
Matrix,
}
#[derive(Debug, Eq, PartialEq, Hash)]
enum WrappedFunction {
BinaryOp {
op: crate::BinaryOperator,
left_type_id: Word,
right_type_id: Word,
},
}
#[derive(Default)]
struct CachedExpressions {
ids: HandleVec<crate::Expression, Word>,
}
impl CachedExpressions {
fn reset(&mut self, length: usize) {
self.ids.clear();
self.ids.resize(length, 0);
}
}
impl ops::Index<Handle<crate::Expression>> for CachedExpressions {
type Output = Word;
fn index(&self, h: Handle<crate::Expression>) -> &Word {
let id = &self.ids[h];
if *id == 0 {
unreachable!("Expression {:?} is not cached!", h);
}
id
}
}
impl ops::IndexMut<Handle<crate::Expression>> for CachedExpressions {
fn index_mut(&mut self, h: Handle<crate::Expression>) -> &mut Word {
let id = &mut self.ids[h];
if *id != 0 {
unreachable!("Expression {:?} is already cached!", h);
}
id
}
}
impl recyclable::Recyclable for CachedExpressions {
fn recycle(self) -> Self {
CachedExpressions {
ids: self.ids.recycle(),
}
}
}
#[derive(Eq, Hash, PartialEq)]
enum CachedConstant {
Literal(crate::proc::HashableLiteral),
Composite {
ty: LookupType,
constituent_ids: Vec<Word>,
},
ZeroValue(Word),
}
#[derive(Clone)]
struct GlobalVariable {
var_id: Word,
handle_id: Word,
access_id: Word,
}
impl GlobalVariable {
const fn dummy() -> Self {
Self {
var_id: 0,
handle_id: 0,
access_id: 0,
}
}
const fn new(id: Word) -> Self {
Self {
var_id: id,
handle_id: 0,
access_id: 0,
}
}
fn reset_for_function(&mut self) {
self.handle_id = 0;
self.access_id = 0;
}
}
struct FunctionArgument {
instruction: Instruction,
handle_id: Word,
}
struct ExpressionConstnessTracker {
inner: crate::arena::HandleSet<crate::Expression>,
}
impl ExpressionConstnessTracker {
fn from_arena(arena: &crate::Arena<crate::Expression>) -> Self {
let mut inner = crate::arena::HandleSet::for_arena(arena);
for (handle, expr) in arena.iter() {
let insert = match *expr {
crate::Expression::Literal(_)
| crate::Expression::ZeroValue(_)
| crate::Expression::Constant(_) => true,
crate::Expression::Compose { ref components, .. } => {
components.iter().all(|&h| inner.contains(h))
}
crate::Expression::Splat { value, .. } => inner.contains(value),
_ => false,
};
if insert {
inner.insert(handle);
}
}
Self { inner }
}
fn is_const(&self, value: Handle<crate::Expression>) -> bool {
self.inner.contains(value)
}
}
struct BlockContext<'w> {
writer: &'w mut Writer,
ir_module: &'w crate::Module,
ir_function: &'w crate::Function,
fun_info: &'w crate::valid::FunctionInfo,
function: &'w mut Function,
cached: CachedExpressions,
temp_list: Vec<Word>,
expression_constness: ExpressionConstnessTracker,
force_loop_bounding: bool,
}
impl BlockContext<'_> {
fn gen_id(&mut self) -> Word {
self.writer.id_gen.next()
}
fn get_type_id(&mut self, lookup_type: LookupType) -> Word {
self.writer.get_type_id(lookup_type)
}
fn get_handle_type_id(&mut self, handle: Handle<crate::Type>) -> Word {
self.writer.get_handle_type_id(handle)
}
fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word {
self.writer.get_expression_type_id(tr)
}
fn get_index_constant(&mut self, index: Word) -> Word {
self.writer.get_constant_scalar(crate::Literal::U32(index))
}
fn get_scope_constant(&mut self, scope: Word) -> Word {
self.writer
.get_constant_scalar(crate::Literal::I32(scope as _))
}
fn get_pointer_type_id(&mut self, base: Word, class: spirv::StorageClass) -> Word {
self.writer.get_pointer_type_id(base, class)
}
fn get_numeric_type_id(&mut self, numeric: NumericType) -> Word {
self.writer.get_numeric_type_id(numeric)
}
}
pub struct Writer {
physical_layout: PhysicalLayout,
logical_layout: LogicalLayout,
id_gen: IdGenerator,
capabilities_available: Option<crate::FastHashSet<Capability>>,
capabilities_used: crate::FastIndexSet<Capability>,
extensions_used: crate::FastIndexSet<&'static str>,
debugs: Vec<Instruction>,
annotations: Vec<Instruction>,
flags: WriterFlags,
bounds_check_policies: BoundsCheckPolicies,
zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode,
force_loop_bounding: bool,
void_type: Word,
lookup_type: crate::FastHashMap<LookupType, Word>,
lookup_function: crate::FastHashMap<Handle<crate::Function>, Word>,
lookup_function_type: crate::FastHashMap<LookupFunctionType, Word>,
wrapped_functions: crate::FastHashMap<WrappedFunction, Word>,
constant_ids: HandleVec<crate::Expression, Word>,
cached_constants: crate::FastHashMap<CachedConstant, Word>,
global_variables: HandleVec<crate::GlobalVariable, GlobalVariable>,
binding_map: BindingMap,
saved_cached: CachedExpressions,
gl450_ext_inst_id: Word,
temp_list: Vec<Word>,
ray_get_committed_intersection_function: Option<Word>,
ray_get_candidate_intersection_function: Option<Word>,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WriterFlags: u32 {
const DEBUG = 0x1;
const ADJUST_COORDINATE_SPACE = 0x2;
const LABEL_VARYINGS = 0x4;
const FORCE_POINT_SIZE = 0x8;
const CLAMP_FRAG_DEPTH = 0x10;
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct BindingInfo {
pub binding_array_size: Option<u32>,
}
pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindingInfo>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ZeroInitializeWorkgroupMemoryMode {
Native,
Polyfill,
None,
}
#[derive(Debug, Clone)]
pub struct Options<'a> {
pub lang_version: (u8, u8),
pub flags: WriterFlags,
pub binding_map: BindingMap,
pub capabilities: Option<crate::FastHashSet<Capability>>,
pub bounds_check_policies: BoundsCheckPolicies,
pub zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode,
pub force_loop_bounding: bool,
pub debug_info: Option<DebugInfo<'a>>,
}
impl Default for Options<'_> {
fn default() -> Self {
let mut flags = WriterFlags::ADJUST_COORDINATE_SPACE
| WriterFlags::LABEL_VARYINGS
| WriterFlags::CLAMP_FRAG_DEPTH;
if cfg!(debug_assertions) {
flags |= WriterFlags::DEBUG;
}
Options {
lang_version: (1, 0),
flags,
binding_map: BindingMap::default(),
capabilities: None,
bounds_check_policies: BoundsCheckPolicies::default(),
zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode::Polyfill,
force_loop_bounding: true,
debug_info: None,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct PipelineOptions {
pub shader_stage: crate::ShaderStage,
pub entry_point: String,
}
pub fn write_vec(
module: &crate::Module,
info: &crate::valid::ModuleInfo,
options: &Options,
pipeline_options: Option<&PipelineOptions>,
) -> Result<Vec<u32>, Error> {
let mut words: Vec<u32> = Vec::new();
let mut w = Writer::new(options)?;
w.write(
module,
info,
pipeline_options,
&options.debug_info,
&mut words,
)?;
Ok(words)
}