mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-22 06:45:13 +00:00
Sort blocks topologically
Also lots of spirv-val work
This commit is contained in:
parent
89065c1db0
commit
c1134788cd
@ -14,7 +14,8 @@ crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
rspirv = { git = "https://github.com/gfx-rs/rspirv" }
|
||||
topological-sort = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6"
|
||||
tempfile = "3.1"
|
||||
tempfile = "3.1"
|
||||
|
@ -220,10 +220,7 @@ impl<'tcx> ConvSpirvType<'tcx> for FnAbi<'tcx, Ty<'tcx>> {
|
||||
PassMode::Ignore => SpirvType::Void.def(cx),
|
||||
PassMode::Direct(_) | PassMode::Pair(..) => self.ret.layout.spirv_type_immediate(cx),
|
||||
PassMode::Cast(cast_target) => cast_target.spirv_type(cx),
|
||||
PassMode::Indirect(_arg_attributes, wide_ptr_attrs) => {
|
||||
if wide_ptr_attrs.is_some() {
|
||||
panic!("TODO: PassMode::Indirect wide ptr not supported for return type");
|
||||
}
|
||||
PassMode::Indirect(..) => {
|
||||
let pointee = self.ret.layout.spirv_type(cx);
|
||||
let pointer = SpirvType::Pointer {
|
||||
storage_class: StorageClass::Function,
|
||||
@ -300,7 +297,8 @@ fn trans_type_impl<'tcx>(cx: &CodegenCx<'tcx>, ty: TyAndLayout<'tcx>, is_immedia
|
||||
// Note! Do not pass through is_immediate here - they're wrapped in a struct, hence, not immediate.
|
||||
let one_spirv = trans_scalar(cx, ty, one, Some(0), false);
|
||||
let two_spirv = trans_scalar(cx, ty, two, Some(1), false);
|
||||
// TODO: Note: We can't use auto_struct_layout here because the spirv types here might be undefined.
|
||||
// Note: We can't use auto_struct_layout here because the spirv types here might be undefined due to
|
||||
// recursive pointer types.
|
||||
let one_offset = Size::ZERO;
|
||||
let two_offset = one.value.size(cx).align_to(two.value.align(cx).abi);
|
||||
let size = if ty.is_unsized() { None } else { Some(ty.size) };
|
||||
@ -352,7 +350,6 @@ fn trans_scalar<'tcx>(
|
||||
}
|
||||
|
||||
match scalar.value {
|
||||
// TODO: Do we use scalar.valid_range?
|
||||
Primitive::Int(width, mut signedness) => {
|
||||
if cx.kernel_mode {
|
||||
signedness = false;
|
||||
@ -434,10 +431,6 @@ fn dig_scalar_pointee<'tcx>(
|
||||
let ptr_ty = cx.layout_of(cx.tcx.mk_mut_ptr(ty.ty.boxed_ty()));
|
||||
dig_scalar_pointee(cx, ptr_ty, index)
|
||||
}
|
||||
// TyKind::Tuple(substs) if substs.len() == 1 => {
|
||||
// let item = cx.layout_of(ty.ty.tuple_fields().next().unwrap());
|
||||
// trans_scalar_known_ty(cx, item, scalar, is_immediate)
|
||||
// }
|
||||
TyKind::Tuple(_) | TyKind::Adt(..) | TyKind::Closure(..) => {
|
||||
dig_scalar_pointee_adt(cx, ty, index)
|
||||
}
|
||||
@ -454,6 +447,9 @@ fn dig_scalar_pointee_adt<'tcx>(
|
||||
index: Option<usize>,
|
||||
) -> PointeeTy<'tcx> {
|
||||
match &ty.variants {
|
||||
// If it's a Variants::Multiple, then we want to emit the type of the dataful variant, not the type of the
|
||||
// discriminant. This is because the discriminant can e.g. have type *mut(), whereas we want the full underlying
|
||||
// type, only available in the dataful variant.
|
||||
Variants::Multiple {
|
||||
tag_encoding,
|
||||
tag_field,
|
||||
@ -513,8 +509,7 @@ fn trans_aggregate<'tcx>(cx: &CodegenCx<'tcx>, ty: TyAndLayout<'tcx>) -> Word {
|
||||
"FieldsShape::Primitive not supported yet in trans_type: {:?}",
|
||||
ty
|
||||
),
|
||||
// TODO: Is this the right thing to do?
|
||||
FieldsShape::Union(_field_count) => {
|
||||
FieldsShape::Union(_) => {
|
||||
assert_ne!(ty.size.bytes(), 0, "{:#?}", ty);
|
||||
assert!(!ty.is_unsized(), "{:#?}", ty);
|
||||
let byte = SpirvType::Integer(8, false).def(cx);
|
||||
@ -525,7 +520,7 @@ fn trans_aggregate<'tcx>(cx: &CodegenCx<'tcx>, ty: TyAndLayout<'tcx>) -> Word {
|
||||
}
|
||||
.def(cx)
|
||||
}
|
||||
FieldsShape::Array { stride: _, count } => {
|
||||
FieldsShape::Array { stride, count } => {
|
||||
let element_type = trans_type_impl(cx, ty.field(cx, 0), false);
|
||||
if ty.is_unsized() {
|
||||
// There's a potential for this array to be sized, but the element to be unsized, e.g. `[[u8]; 5]`.
|
||||
@ -547,8 +542,13 @@ fn trans_aggregate<'tcx>(cx: &CodegenCx<'tcx>, ty: TyAndLayout<'tcx>) -> Word {
|
||||
}
|
||||
.def(cx)
|
||||
} else {
|
||||
// TODO: Assert stride is same as spirv's stride?
|
||||
let count_const = cx.constant_u32(count as u32).def;
|
||||
let element_spv = cx.lookup_type(element_type);
|
||||
let stride_spv = element_spv
|
||||
.sizeof(cx)
|
||||
.expect("Unexpected unsized type in sized FieldsShape::Array")
|
||||
.align_to(element_spv.alignof(cx));
|
||||
assert_eq!(stride_spv, stride);
|
||||
SpirvType::Array {
|
||||
element: element_type,
|
||||
count: count_const,
|
||||
|
@ -178,7 +178,6 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
let (signed, construct_case) = match self.lookup_type(v.ty) {
|
||||
SpirvType::Integer(width, signed) => {
|
||||
let construct_case = match width {
|
||||
// TODO: How are negative values represented? sign-extended? if so, they'll be >MAX
|
||||
8 => |signed, v| {
|
||||
if v > u8::MAX as u128 {
|
||||
panic!("Switches to values above u8::MAX not supported: {:?}", v)
|
||||
@ -285,7 +284,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
match self.lookup_type(ty) {
|
||||
SpirvType::Integer(_, _) => self.emit().bitwise_and(ty, None, lhs.def, rhs.def),
|
||||
SpirvType::Bool => self.emit().logical_and(ty, None, lhs.def, rhs.def),
|
||||
o => panic!("TODO: and() not implemented for type {}", o.debug(ty, self)),
|
||||
o => panic!("and() not implemented for type {}", o.debug(ty, self)),
|
||||
}
|
||||
.unwrap()
|
||||
.with_type(ty)
|
||||
@ -296,7 +295,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
match self.lookup_type(ty) {
|
||||
SpirvType::Integer(_, _) => self.emit().bitwise_or(ty, None, lhs.def, rhs.def),
|
||||
SpirvType::Bool => self.emit().logical_or(ty, None, lhs.def, rhs.def),
|
||||
o => panic!("TODO: or() not implemented for type {}", o.debug(ty, self)),
|
||||
o => panic!("or() not implemented for type {}", o.debug(ty, self)),
|
||||
}
|
||||
.unwrap()
|
||||
.with_type(ty)
|
||||
@ -307,7 +306,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
match self.lookup_type(ty) {
|
||||
SpirvType::Integer(_, _) => self.emit().bitwise_xor(ty, None, lhs.def, rhs.def),
|
||||
SpirvType::Bool => self.emit().logical_not_equal(ty, None, lhs.def, rhs.def),
|
||||
o => panic!("TODO: xor() not implemented for type {}", o.debug(ty, self)),
|
||||
o => panic!("xor() not implemented for type {}", o.debug(ty, self)),
|
||||
}
|
||||
.unwrap()
|
||||
.with_type(ty)
|
||||
@ -316,10 +315,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
match self.lookup_type(val.ty) {
|
||||
SpirvType::Integer(_, _) => self.emit().not(val.ty, None, val.def),
|
||||
SpirvType::Bool => self.emit().logical_not(val.ty, None, val.def),
|
||||
o => panic!(
|
||||
"TODO: not() not implemented for type {}",
|
||||
o.debug(val.ty, self)
|
||||
),
|
||||
o => panic!("not() not implemented for type {}", o.debug(val.ty, self)),
|
||||
}
|
||||
.unwrap()
|
||||
.with_type(val.ty)
|
||||
@ -834,8 +830,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
}
|
||||
|
||||
fn icmp(&mut self, op: IntPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value {
|
||||
// TODO: Do we want to assert signedness matches the opcode? Is it possible to have one that doesn't match? Does
|
||||
// spir-v allow nonmatching instructions?
|
||||
// Note: the signedness of the opcode doesn't have to match the signedness of the operands.
|
||||
use IntPredicate::*;
|
||||
assert_ty_eq!(self, lhs.ty, rhs.ty);
|
||||
let b = SpirvType::Bool.def(self);
|
||||
@ -1112,7 +1107,6 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
}
|
||||
.def(self);
|
||||
if self.builder.lookup_const(elt.def).is_ok() {
|
||||
// TODO: Cache this?
|
||||
self.emit()
|
||||
.constant_composite(result_type, std::iter::repeat(elt.def).take(num_elts))
|
||||
} else {
|
||||
@ -1362,10 +1356,11 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
||||
}
|
||||
|
||||
unsafe fn delete_basic_block(&mut self, _bb: Self::BasicBlock) {
|
||||
todo!()
|
||||
// Ignore: If we were to delete the block, then other builder's selected_block index would become invalid, due
|
||||
// to shifting blocks.
|
||||
}
|
||||
|
||||
fn do_not_inline(&mut self, _llret: Self::Value) {
|
||||
todo!()
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
@ -649,8 +649,11 @@ impl<'a, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'tcx> {
|
||||
|
||||
fn abort(&mut self) {
|
||||
// codegen_llvm uses call(llvm.trap) here, so it is not a block terminator
|
||||
self.emit().kill().unwrap();
|
||||
*self = self.build_sibling_block("abort_continue");
|
||||
if !self.kernel_mode {
|
||||
self.emit().kill().unwrap();
|
||||
*self = self.build_sibling_block("abort_continue");
|
||||
}
|
||||
// TODO: Figure out an appropriate instruction for kernel mode.
|
||||
}
|
||||
|
||||
fn assume(&mut self, _val: Self::Value) {
|
||||
|
@ -160,6 +160,20 @@ impl BuilderSpirv {
|
||||
Err("Definition not found")
|
||||
}
|
||||
|
||||
pub fn lookup_const_bool(&self, def: Word) -> Result<bool, &'static str> {
|
||||
let builder = self.builder.borrow();
|
||||
for inst in &builder.module_ref().types_global_values {
|
||||
if inst.result_id == Some(def) {
|
||||
return match inst.class.opcode {
|
||||
Op::ConstantFalse => Ok(true),
|
||||
Op::ConstantTrue => Ok(true),
|
||||
_ => Err("Instruction not OpConstantTrue/False"),
|
||||
};
|
||||
}
|
||||
}
|
||||
Err("Definition not found")
|
||||
}
|
||||
|
||||
pub fn lookup_global_constant_variable(&self, def: Word) -> Result<Word, &'static str> {
|
||||
// TODO: Maybe assert that this indeed a constant?
|
||||
let builder = self.builder.borrow();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::abi::ConvSpirvType;
|
||||
use crate::builder::ExtInst;
|
||||
use crate::builder_spirv::{BuilderCursor, BuilderSpirv, SpirvValue, SpirvValueExt};
|
||||
use crate::poison_pass::poison_pass;
|
||||
use crate::finalizing_passes::{block_ordering_pass, poison_pass};
|
||||
use crate::spirv_type::{SpirvType, SpirvTypePrinter, TypeCache};
|
||||
use rspirv::dr::{Module, Operand};
|
||||
use rspirv::spirv::{Decoration, FunctionControl, LinkageType, StorageClass, Word};
|
||||
@ -144,6 +144,9 @@ impl<'tcx> CodegenCx<'tcx> {
|
||||
poison_pass(&mut result, &mut self.poisoned_values.borrow_mut());
|
||||
// defs go before fns
|
||||
result.functions.sort_by_key(|f| !f.blocks.is_empty());
|
||||
for function in &mut result.functions {
|
||||
block_ordering_pass(function);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@ -383,9 +386,6 @@ impl<'tcx> LayoutTypeMethods<'tcx> for CodegenCx<'tcx> {
|
||||
}
|
||||
|
||||
impl<'tcx> BaseTypeMethods<'tcx> for CodegenCx<'tcx> {
|
||||
// TODO: llvm types are signless, as in neither signed nor unsigned (I think?), so these are expected to be
|
||||
// signless. Do we want a SpirvType::Integer(_, Signless) to indicate the sign is unknown, and to do conversions at
|
||||
// appropriate places?
|
||||
fn type_i1(&self) -> Self::Type {
|
||||
SpirvType::Bool.def(self)
|
||||
}
|
||||
@ -462,10 +462,7 @@ impl<'tcx> BaseTypeMethods<'tcx> for CodegenCx<'tcx> {
|
||||
}
|
||||
.def(self)
|
||||
}
|
||||
fn type_ptr_to_ext(&self, ty: Self::Type, address_space: AddressSpace) -> Self::Type {
|
||||
if address_space != AddressSpace::DATA {
|
||||
panic!("TODO: Unimplemented AddressSpace {:?}", address_space)
|
||||
}
|
||||
fn type_ptr_to_ext(&self, ty: Self::Type, _address_space: AddressSpace) -> Self::Type {
|
||||
SpirvType::Pointer {
|
||||
storage_class: StorageClass::Function,
|
||||
pointee: ty,
|
||||
@ -526,7 +523,7 @@ impl<'tcx> StaticMethods for CodegenCx<'tcx> {
|
||||
.emit_global()
|
||||
.variable(ty, None, StorageClass::Function, Some(cv.def))
|
||||
.with_type(ty);
|
||||
// TODO: These should be StorageClass::Private, so just poison for now.
|
||||
// TODO: These should be StorageClass::UniformConstant, so just poison for now.
|
||||
self.poison(result.def);
|
||||
result
|
||||
}
|
||||
@ -544,23 +541,16 @@ impl<'tcx> StaticMethods for CodegenCx<'tcx> {
|
||||
SpirvType::Pointer { pointee, .. } => pointee,
|
||||
other => panic!("global had non-pointer type {}", other.debug(g.ty, self)),
|
||||
};
|
||||
let v = create_const_alloc(self, alloc, value_ty);
|
||||
let mut v = create_const_alloc(self, alloc, value_ty);
|
||||
|
||||
if self.lookup_type(v.ty) == SpirvType::Bool {
|
||||
// convert bool -> i8
|
||||
todo!();
|
||||
let val = self.builder.lookup_const_bool(v.def).unwrap();
|
||||
let val_int = if val { 1 } else { 0 };
|
||||
v = self.constant_u8(val_int);
|
||||
}
|
||||
|
||||
// let instance = Instance::mono(self.tcx, def_id);
|
||||
// let ty = instance.ty(self.tcx, ParamEnv::reveal_all());
|
||||
// let llty = self.layout_of(ty).llvm_type(self);
|
||||
|
||||
assert_ty_eq!(self, value_ty, v.ty);
|
||||
self.builder.set_global_initializer(g.def, v.def);
|
||||
|
||||
// if attrs.flags.contains(CodegenFnAttrFlags::USED) {
|
||||
// self.add_used_global(g);
|
||||
// }
|
||||
}
|
||||
|
||||
/// Mark the given global value as "used", to prevent a backend from potentially removing a
|
||||
@ -569,7 +559,7 @@ impl<'tcx> StaticMethods for CodegenCx<'tcx> {
|
||||
/// Static variables in Rust can be annotated with the `#[used]` attribute to direct the `rustc`
|
||||
/// compiler to mark the variable as a "used global".
|
||||
fn add_used_global(&self, _global: Self::Value) {
|
||||
todo!()
|
||||
// TODO: Ignore for now.
|
||||
}
|
||||
}
|
||||
|
||||
@ -992,7 +982,7 @@ impl<'tcx> ConstMethods<'tcx> for CodegenCx<'tcx> {
|
||||
res
|
||||
}
|
||||
Primitive::Pointer => {
|
||||
panic!("TODO: scalar_to_backend Primitive::Ptr not implemented yet")
|
||||
panic!("scalar_to_backend Primitive::Ptr is an invalid state")
|
||||
}
|
||||
},
|
||||
Scalar::Ptr(ptr) => {
|
||||
@ -1042,7 +1032,7 @@ impl<'tcx> ConstMethods<'tcx> for CodegenCx<'tcx> {
|
||||
},
|
||||
) => {
|
||||
if a_space != b_space {
|
||||
// TODO: make sure the type here is right.
|
||||
// TODO: Emit the correct type that is passed into this function.
|
||||
self.poison(value.def);
|
||||
}
|
||||
assert_ty_eq!(self, a, b);
|
||||
@ -1068,8 +1058,14 @@ impl<'tcx> ConstMethods<'tcx> for CodegenCx<'tcx> {
|
||||
}
|
||||
|
||||
fn const_ptrcast(&self, val: Self::Value, ty: Self::Type) -> Self::Value {
|
||||
// TODO: hack to get things working, this *will* fail spirv-val
|
||||
val.def.with_type(ty)
|
||||
if val.ty == ty {
|
||||
val
|
||||
} else {
|
||||
// constant ptrcast is not supported in spir-v
|
||||
let result = val.def.with_type(ty);
|
||||
self.poison(result.def);
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
use rspirv::dr::{Instruction, Module, Operand};
|
||||
use rspirv::dr::{Block, Function, Instruction, Module, Operand};
|
||||
use rspirv::spirv::{Op, Word};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::once;
|
||||
use std::mem::replace;
|
||||
|
||||
fn contains_poison(inst: &Instruction, poison: &HashSet<Word>) -> bool {
|
||||
inst.result_type.map_or(false, |w| poison.contains(&w))
|
||||
@ -21,7 +23,7 @@ fn is_poison(inst: &Instruction, poison: &HashSet<Word>) -> bool {
|
||||
}
|
||||
|
||||
pub fn poison_pass(module: &mut Module, poison: &mut HashSet<Word>) {
|
||||
// TODO: This is O(n^2), can we speed it up?
|
||||
// Note: This is O(n^2).
|
||||
while spread_poison(module, poison) {}
|
||||
|
||||
if option_env!("PRINT_POISON").is_some() {
|
||||
@ -118,3 +120,97 @@ fn spread_poison(module: &mut Module, poison: &mut HashSet<Word>) -> bool {
|
||||
}
|
||||
any
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
|
||||
pub fn block_ordering_pass(func: &mut Function) {
|
||||
if func.blocks.len() < 2 {
|
||||
return;
|
||||
}
|
||||
let mut graph = func
|
||||
.blocks
|
||||
.iter()
|
||||
.map(|block| {
|
||||
(
|
||||
block.label.as_ref().unwrap().result_id.unwrap(),
|
||||
outgoing_edges(block),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let entry_label = func.blocks[0].label.as_ref().unwrap().result_id.unwrap();
|
||||
delete_backedges(&mut graph, entry_label);
|
||||
|
||||
let mut sorter = topological_sort::TopologicalSort::<Word>::new();
|
||||
for (key, values) in graph {
|
||||
for value in values {
|
||||
sorter.add_dependency(key, value);
|
||||
}
|
||||
}
|
||||
let mut old_blocks = replace(&mut func.blocks, Vec::new());
|
||||
while let Some(item) = sorter.pop() {
|
||||
let index = old_blocks
|
||||
.iter()
|
||||
.position(|b| b.label.as_ref().unwrap().result_id.unwrap() == item)
|
||||
.unwrap();
|
||||
func.blocks.push(old_blocks.remove(index));
|
||||
}
|
||||
assert!(sorter.is_empty());
|
||||
assert!(old_blocks.is_empty());
|
||||
assert_eq!(
|
||||
func.blocks[0].label.as_ref().unwrap().result_id.unwrap(),
|
||||
entry_label,
|
||||
"Topo sorter did something weird (unreachable blocks?)"
|
||||
);
|
||||
}
|
||||
|
||||
fn outgoing_edges(block: &Block) -> Vec<Word> {
|
||||
fn unwrap_id_ref(operand: &Operand) -> Word {
|
||||
match *operand {
|
||||
Operand::IdRef(word) => word,
|
||||
_ => panic!("Expected Operand::IdRef: {}", operand),
|
||||
}
|
||||
}
|
||||
let terminator = block.instructions.last().unwrap();
|
||||
// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Termination
|
||||
match terminator.class.opcode {
|
||||
Op::Branch => vec![unwrap_id_ref(&terminator.operands[0])],
|
||||
Op::BranchConditional => vec![
|
||||
unwrap_id_ref(&terminator.operands[1]),
|
||||
unwrap_id_ref(&terminator.operands[2]),
|
||||
],
|
||||
Op::Switch => once(unwrap_id_ref(&terminator.operands[1]))
|
||||
.chain(
|
||||
terminator.operands[3..]
|
||||
.iter()
|
||||
.step_by(2)
|
||||
.map(unwrap_id_ref),
|
||||
)
|
||||
.collect(),
|
||||
Op::Return | Op::ReturnValue | Op::Kill | Op::Unreachable => Vec::new(),
|
||||
_ => panic!("Invalid block terminator: {:?}", terminator),
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_backedges(graph: &mut HashMap<Word, Vec<Word>>, entry: Word) {
|
||||
// TODO: This has extremely bad runtime
|
||||
let mut backedges = HashSet::new();
|
||||
fn re(
|
||||
graph: &HashMap<Word, Vec<Word>>,
|
||||
entry: Word,
|
||||
stack: &mut Vec<Word>,
|
||||
backedges: &mut HashSet<(Word, Word)>,
|
||||
) {
|
||||
stack.push(entry);
|
||||
for &item in &graph[&entry] {
|
||||
if stack.contains(&item) {
|
||||
backedges.insert((entry, item));
|
||||
} else if !backedges.contains(&(entry, item)) {
|
||||
re(graph, item, stack, backedges);
|
||||
}
|
||||
}
|
||||
assert_eq!(stack.pop(), Some(entry));
|
||||
}
|
||||
re(graph, entry, &mut Vec::new(), &mut backedges);
|
||||
for (from, to) in backedges {
|
||||
graph.get_mut(&from).unwrap().retain(|&o| o != to);
|
||||
}
|
||||
}
|
@ -19,8 +19,8 @@ mod abi;
|
||||
mod builder;
|
||||
mod builder_spirv;
|
||||
mod codegen_cx;
|
||||
mod finalizing_passes;
|
||||
mod link;
|
||||
mod poison_pass;
|
||||
mod spirv_type;
|
||||
mod things;
|
||||
|
||||
|
@ -286,8 +286,8 @@ impl SpirvType {
|
||||
|
||||
pub fn memset_const_pattern<'tcx>(&self, cx: &CodegenCx<'tcx>, fill_byte: u8) -> Word {
|
||||
match *self {
|
||||
SpirvType::Void => panic!("TODO: void memset not implemented yet"),
|
||||
SpirvType::Bool => panic!("TODO: bool memset not implemented yet"),
|
||||
SpirvType::Void => panic!("memset invalid on void pattern"),
|
||||
SpirvType::Bool => panic!("memset invalid on bool pattern"),
|
||||
SpirvType::Integer(width, _signedness) => match width {
|
||||
8 => cx.constant_u8(fill_byte).def,
|
||||
16 => cx.constant_u16(memset_fill_u16(fill_byte)).def,
|
||||
@ -333,8 +333,8 @@ impl SpirvType {
|
||||
fill_var: Word,
|
||||
) -> Word {
|
||||
match *self {
|
||||
SpirvType::Void => panic!("TODO: void memset not implemented yet"),
|
||||
SpirvType::Bool => panic!("TODO: bool memset not implemented yet"),
|
||||
SpirvType::Void => panic!("memset invalid on void pattern"),
|
||||
SpirvType::Bool => panic!("memset invalid on bool pattern"),
|
||||
SpirvType::Integer(width, _signedness) => match width {
|
||||
8 => fill_var,
|
||||
16 => memset_dynamic_scalar(builder, fill_var, 2, false),
|
||||
|
Loading…
Reference in New Issue
Block a user