Introducing rspiv::Builder into the structurizer. (#253)

* Replaced most manual spirv manipulation with rspirv builder

* Replaced most manual spirv manipulation with rspirv builder

* removed braces

* prefer slices over vec
This commit is contained in:
Viktor Zoutman 2020-11-18 14:43:38 +01:00 committed by GitHub
parent bedbc4dc0f
commit 6353505e9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 207 additions and 289 deletions

View File

@ -134,10 +134,12 @@ pub fn link(sess: &Session, mut inputs: Vec<Module>, opts: &Options) -> Result<M
dce::dce(&mut output); dce::dce(&mut output);
} }
if opts.structurize { let mut output = if opts.structurize {
let _timer = sess.timer("link_structurize"); let _timer = sess.timer("link_structurize");
structurizer::structurize(sess, &mut output); structurizer::structurize(sess, output)
} } else {
output
};
{ {
let _timer = sess.timer("link_block_ordering_pass_and_mem2reg"); let _timer = sess.timer("link_block_ordering_pass_and_mem2reg");

View File

@ -1,8 +1,7 @@
// This pass inserts merge instructions for structured control flow with the assumption the spir-v is reducible. // This pass inserts merge instructions for structured control flow with the assumption the spir-v is reducible.
use super::id;
use super::simple_passes::outgoing_edges; use super::simple_passes::outgoing_edges;
use rspirv::dr::{Block, Instruction, Module, ModuleHeader, Operand}; use rspirv::dr::{Block, Builder, InsertPoint, Instruction, Module, Operand};
use rspirv::spirv::{Op, SelectionControl, Word}; use rspirv::spirv::{Op, SelectionControl, Word};
use rustc_session::Session; use rustc_session::Session;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
@ -106,69 +105,51 @@ impl ControlFlowInfo {
} }
} }
fn get_debug_names(&self) -> Vec<(Word, String)> { fn set_names(&self, builder: &mut Builder) {
let mut retval = Vec::new();
for loop_info in &self.loops { for loop_info in &self.loops {
retval.push((loop_info.header_id, "loop_header".to_string())); builder.name(loop_info.header_id, "loop_header".to_string());
retval.push((loop_info.merge_id, "loop_merge".to_string())); builder.name(loop_info.merge_id, "loop_merge".to_string());
retval.push((loop_info.continue_id, "loop_continue".to_string())); builder.name(loop_info.continue_id, "loop_continue".to_string());
} }
for id in &self.if_merge_ids { for id in &self.if_merge_ids {
retval.push((*id, "if_merge".to_string())); builder.name(*id, "if_merge".to_string());
} }
for id in &self.switch_merge_ids { for id in &self.switch_merge_ids {
retval.push((*id, "switch_merge".to_string())); builder.name(*id, "switch_merge".to_string());
} }
retval
} }
} }
pub fn structurize(sess: &Session, module: &mut Module) { pub fn structurize(sess: &Session, module: Module) -> Module {
let mut debug_names = Vec::new(); let mut builder = Builder::new_from_module(module);
for func in &mut module.functions { for func_idx in 0..builder.module_ref().functions.len() {
let mut cf_info = ControlFlowInfo::new(); let mut cf_info = ControlFlowInfo::new();
insert_loop_merge_on_conditional_branch( builder.select_function(Some(func_idx)).unwrap();
&mut module.header.as_mut().unwrap(),
&mut func.blocks,
&mut cf_info,
);
retarget_loop_children_if_needed(&mut func.blocks, &cf_info); insert_loop_merge_on_conditional_branch(&mut builder, &mut cf_info);
retarget_loop_children_if_needed(&mut builder, &cf_info);
insert_selection_merge_on_conditional_branch( insert_selection_merge_on_conditional_branch(sess, &mut builder, &mut cf_info);
sess, defer_loop_internals(&mut builder, &cf_info);
&mut module.header.as_mut().unwrap(), cf_info.set_names(&mut builder);
&mut func.blocks,
&mut cf_info,
);
defer_loop_internals(
&mut module.header.as_mut().unwrap(),
&mut func.blocks,
&cf_info,
&mut debug_names,
&mut module.types_global_values,
);
debug_names.extend(cf_info.get_debug_names());
} }
for (id, name) in debug_names { builder.module()
module.debugs.push(Instruction::new(
Op::Name,
None,
None,
vec![Operand::IdRef(id), Operand::LiteralString(name)],
))
}
} }
fn find_block_index_from_id(blocks: &[Block], id: &Word) -> usize { fn get_blocks_mut(builder: &mut Builder) -> &mut Vec<Block> {
for (i, block) in blocks.iter().enumerate() { let function = builder.selected_function().unwrap();
&mut builder.module_mut().functions[function].blocks
}
fn get_blocks_ref(builder: &Builder) -> &[Block] {
let function = builder.selected_function().unwrap();
&builder.module_ref().functions[function].blocks
}
fn find_block_index_from_id(builder: &Builder, id: &Word) -> usize {
for (i, block) in get_blocks_ref(builder).iter().enumerate() {
if block.label_id() == Some(*id) { if block.label_id() == Some(*id) {
return i; return i;
} }
@ -177,8 +158,24 @@ fn find_block_index_from_id(blocks: &[Block], id: &Word) -> usize {
panic!("Failed to find block from id {}", id); panic!("Failed to find block from id {}", id);
} }
macro_rules! get_block_mut {
($builder:expr, $idx:expr) => {
&mut get_blocks_mut($builder)[$idx]
};
}
macro_rules! get_block_ref {
($builder:expr, $idx:expr) => {
&get_blocks_ref($builder)[$idx]
};
}
fn idx_to_id(builder: &mut Builder, idx: usize) -> Word {
get_blocks_ref(builder)[idx].label_id().unwrap()
}
// some times break will yeet themselfs out of a parent loop by skipping the merge block. This prevents that. // some times break will yeet themselfs out of a parent loop by skipping the merge block. This prevents that.
fn retarget_loop_children_if_needed(blocks: &mut [Block], cf_info: &ControlFlowInfo) { fn retarget_loop_children_if_needed(builder: &mut Builder, cf_info: &ControlFlowInfo) {
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
let LoopInfo { let LoopInfo {
header_id: header, header_id: header,
@ -190,8 +187,8 @@ fn retarget_loop_children_if_needed(blocks: &mut [Block], cf_info: &ControlFlowI
next.push_back(*header); next.push_back(*header);
while let Some(front) = next.pop_front() { while let Some(front) = next.pop_front() {
let block_idx = find_block_index_from_id(blocks, &front); let block_idx = find_block_index_from_id(builder, &front);
let mut new_edges = outgoing_edges(&blocks[block_idx]); let mut new_edges = outgoing_edges(get_block_ref!(builder, block_idx));
// Make sure we are not looping or going into child loops. // Make sure we are not looping or going into child loops.
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
@ -211,9 +208,9 @@ fn retarget_loop_children_if_needed(blocks: &mut [Block], cf_info: &ControlFlowI
if new_edges.len() == 1 { if new_edges.len() == 1 {
// if front branches to a block that is the child of a merge, retarget it. // if front branches to a block that is the child of a merge, retarget it.
if block_is_parent_of(*merge, new_edges[0], blocks) { if block_is_parent_of(builder, *merge, new_edges[0]) {
// retarget front to branch to merge. // retarget front to branch to merge.
let front_block = &mut blocks[block_idx]; let front_block = get_block_mut!(builder, block_idx);
(*front_block (*front_block
.instructions .instructions
.last_mut() .last_mut()
@ -229,9 +226,9 @@ fn retarget_loop_children_if_needed(blocks: &mut [Block], cf_info: &ControlFlowI
} }
} }
fn incoming_edges(id: Word, blocks: &[Block]) -> Vec<Word> { fn incoming_edges(id: Word, builder: &mut Builder) -> Vec<Word> {
let mut incoming_edges = Vec::new(); let mut incoming_edges = Vec::new();
for block in blocks { for block in get_blocks_ref(builder) {
let out = outgoing_edges(block); let out = outgoing_edges(block);
if out.contains(&id) { if out.contains(&id) {
incoming_edges.push(block.label_id().unwrap()); incoming_edges.push(block.label_id().unwrap());
@ -241,168 +238,94 @@ fn incoming_edges(id: Word, blocks: &[Block]) -> Vec<Word> {
incoming_edges incoming_edges
} }
fn num_incoming_edges(id: Word, blocks: &[Block]) -> usize { fn num_incoming_edges(id: Word, builder: &mut Builder) -> usize {
incoming_edges(id, blocks).len() incoming_edges(id, builder).len()
} }
// Turn a block into a conditional branch that either goes to yes or goes to merge. // Turn a block into a conditional branch that either goes to yes or goes to merge.
fn change_block_to_switch( fn change_block_to_switch(
blocks: &mut [Block], builder: &mut Builder,
block_id: Word, block_id: Word,
cases: &[Word], cases: &[Word],
merge: Word, merge: Word,
condition: Word, condition: Word,
) { ) {
let cb_idx = find_block_index_from_id(blocks, &block_id); let cb_idx = find_block_index_from_id(builder, &block_id);
let cb_block = &mut blocks[cb_idx]; builder.select_block(Some(cb_idx)).unwrap();
// selection merge let target: Vec<(Operand, Word)> = cases
let merge_operands = vec![ .iter()
Operand::IdRef(merge), .enumerate()
Operand::SelectionControl(SelectionControl::NONE), .map(|(i, id)| (Operand::LiteralInt32(i as u32 + 1), *id))
]; .collect();
*cb_block.instructions.last_mut().unwrap() =
Instruction::new(Op::SelectionMerge, None, None, merge_operands);
// conditional branch builder.pop_instruction().unwrap();
let mut switch_operands = vec![Operand::IdRef(condition), Operand::IdRef(merge)]; builder
.selection_merge(merge, SelectionControl::NONE)
for (i, label_id) in cases.iter().enumerate() { .unwrap();
let literal = i as u32 + 1; builder.switch(condition, merge, target).unwrap();
switch_operands.push(Operand::LiteralInt32(literal));
switch_operands.push(Operand::IdRef(*label_id));
}
cb_block
.instructions
.push(Instruction::new(Op::Switch, None, None, switch_operands));
}
fn find_or_create_int_constant(
opcode: Op,
constants: &mut Vec<Instruction>,
header: &mut ModuleHeader,
type_result_id: Word,
value: u32,
) -> Word {
// create
let result_id = id(header);
let new_constant = Instruction::new(
opcode,
Some(type_result_id),
Some(result_id),
vec![Operand::LiteralInt32(value)],
);
constants.push(new_constant);
result_id
}
fn find_or_create_type_constant(
opcode: Op,
constants: &mut Vec<Instruction>,
header: &mut ModuleHeader,
) -> Word {
// find
for constant in constants.iter() {
if constant.class.opcode == opcode {
return constant.result_id.unwrap();
}
}
// create
let result_id = id(header);
let new_constant = Instruction::new(opcode, None, Some(result_id), vec![]);
constants.push(new_constant);
result_id
} }
// detect the intermediate break block by checking whether a block that branches to a merge block has 2 parents. // detect the intermediate break block by checking whether a block that branches to a merge block has 2 parents.
fn defer_loop_internals( fn defer_loop_internals(builder: &mut Builder, cf_info: &ControlFlowInfo) {
header: &mut ModuleHeader,
blocks: &mut Vec<Block>,
cf_info: &ControlFlowInfo,
debug_names: &mut Vec<(Word, String)>,
constants: &mut Vec<Instruction>,
) {
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
// find all blocks that branch to a merge block. // find all blocks that branch to a merge block.
let mut possible_intermediate_block_idexes = Vec::new(); let mut possible_intermediate_block_idexes = Vec::new();
for (i, block) in blocks.iter().enumerate() { for (i, block) in get_blocks_ref(builder).iter().enumerate() {
let out = outgoing_edges(block); let out = outgoing_edges(block);
if out.len() == 1 && out[0] == loop_info.merge_id { if out.len() == 1 && out[0] == loop_info.merge_id {
possible_intermediate_block_idexes.push(i) possible_intermediate_block_idexes.push(i)
} }
} }
// check how many incoming edges the branch has and use that to collect a list of intermediate blocks. // check how many incoming edges the branch has and use that to collect a list of intermediate blocks.
let mut intermediate_blocks = Vec::new(); let mut intermediate_block_ids = Vec::new();
for i in possible_intermediate_block_idexes { for i in possible_intermediate_block_idexes {
let intermediate_block = &blocks[i]; let intermediate_block_id = idx_to_id(builder, i);
let num_incoming_edges = let num_incoming_edges = num_incoming_edges(intermediate_block_id, builder);
num_incoming_edges(intermediate_block.label_id().unwrap(), blocks);
if num_incoming_edges > 1 { if num_incoming_edges > 1 {
intermediate_blocks.push(i); intermediate_block_ids.push(intermediate_block_id);
} }
} }
if !intermediate_blocks.is_empty() { if !intermediate_block_ids.is_empty() {
let intermediate_block_ids: Vec<Word> = intermediate_blocks
.iter()
.map(|idx| blocks[*idx].label_id().unwrap())
.collect();
// Create a new empty block. // Create a new empty block.
let old_merge_block_id = split_block(header, blocks, loop_info.merge_id, false); let old_merge_block_id = split_block(builder, loop_info.merge_id, false);
// Create Phi // Create Phi
let phi_result_id = id(header); let phi_result_id = builder.id();
let int_type_id = find_or_create_type_constant(Op::TypeInt, constants, header); let int_type_id = builder.type_int(32, 1);
let const_0_id = let const_0_id = builder.constant_u32(int_type_id, 0);
find_or_create_int_constant(Op::Constant, constants, header, int_type_id, 0);
let mut phi_operands = vec![]; let mut phi_operands = vec![];
for (intermediate_i, intermediate_block_id) in intermediate_block_ids.iter().enumerate() for (intermediate_i, intermediate_block_id) in intermediate_block_ids.iter().enumerate()
{ {
let intermediate_i = intermediate_i as u32 + 1; let intermediate_i = intermediate_i as u32 + 1;
let intermediate_block_idx = let const_x_id = builder.constant_u32(int_type_id, intermediate_i);
find_block_index_from_id(blocks, intermediate_block_id); let t = incoming_edges(*intermediate_block_id, builder);
let intermediate_block_id = blocks[intermediate_block_idx].label_id().unwrap();
let const_x_id = find_or_create_int_constant(
Op::Constant,
constants,
header,
int_type_id,
intermediate_i,
);
let t = incoming_edges(intermediate_block_id, blocks);
for blocks_that_go_to_intermediate in t { for blocks_that_go_to_intermediate in t {
phi_operands.push(Operand::IdRef(const_x_id)); phi_operands.push((const_x_id, blocks_that_go_to_intermediate));
phi_operands.push(Operand::IdRef(blocks_that_go_to_intermediate));
} }
debug_names.push((intermediate_block_id, "deferred".to_string())); builder.name(*intermediate_block_id, "deferred".to_string());
} }
phi_operands.push(Operand::IdRef(const_0_id)); phi_operands.push((const_0_id, loop_info.header_id));
phi_operands.push(Operand::IdRef(loop_info.header_id));
let phi_inst = Instruction::new(
Op::Phi,
Some(int_type_id),
Some(phi_result_id),
phi_operands,
);
// add phi to the empty merge block. builder
{ .select_block(Some(find_block_index_from_id(builder, &loop_info.merge_id)))
let merge_block_idx = find_block_index_from_id(blocks, &loop_info.merge_id); .unwrap();
let merge_block = &mut blocks[merge_block_idx]; builder
merge_block.instructions.insert(0, phi_inst); .insert_phi(
} InsertPoint::Begin,
int_type_id,
Some(phi_result_id),
phi_operands,
)
.unwrap();
// point all intermediate blocks to the new empty merge block. // point all intermediate blocks to the new empty merge block.
for intermediate_block_id in intermediate_block_ids.iter() { for intermediate_block_id in intermediate_block_ids.iter() {
for incoming_id in incoming_edges(*intermediate_block_id, blocks) { for incoming_id in incoming_edges(*intermediate_block_id, builder) {
let incoming_idx = find_block_index_from_id(blocks, &incoming_id); let incoming_idx = find_block_index_from_id(builder, &incoming_id);
let incoming_block = &mut blocks[incoming_idx]; let incoming_block = get_block_mut!(builder, incoming_idx);
for operand in &mut incoming_block.instructions.last_mut().unwrap().operands { for operand in &mut incoming_block.instructions.last_mut().unwrap().operands {
if *operand == Operand::IdRef(*intermediate_block_id) { if *operand == Operand::IdRef(*intermediate_block_id) {
@ -414,7 +337,7 @@ fn defer_loop_internals(
// Create a switch statement of all intermediate blocks. // Create a switch statement of all intermediate blocks.
change_block_to_switch( change_block_to_switch(
blocks, builder,
loop_info.merge_id, loop_info.merge_id,
&intermediate_block_ids, &intermediate_block_ids,
old_merge_block_id, old_merge_block_id,
@ -424,8 +347,8 @@ fn defer_loop_internals(
// point intermediate blocks to the old merge block. // point intermediate blocks to the old merge block.
for intermediate_block_id in intermediate_block_ids.iter() { for intermediate_block_id in intermediate_block_ids.iter() {
let intermediate_block_idx = let intermediate_block_idx =
find_block_index_from_id(blocks, intermediate_block_id); find_block_index_from_id(builder, intermediate_block_id);
for operand in &mut blocks[intermediate_block_idx] for operand in &mut get_block_mut!(builder, intermediate_block_idx)
.instructions .instructions
.last_mut() .last_mut()
.unwrap() .unwrap()
@ -441,14 +364,14 @@ fn defer_loop_internals(
} }
// "Combines" all continue blocks into 1 and returns the ID of the continue block. // "Combines" all continue blocks into 1 and returns the ID of the continue block.
fn eliminate_multiple_continue_blocks(blocks: &mut Vec<Block>, header: Word) -> Word { fn eliminate_multiple_continue_blocks(builder: &mut Builder, header: Word) -> Word {
// Find all possible continue blocks. // Find all possible continue blocks.
let mut continue_blocks = Vec::new(); let mut continue_blocks = Vec::new();
for block in blocks.iter() { for block in get_blocks_ref(builder) {
let block_id = block.label_id().unwrap(); let block_id = block.label_id().unwrap();
if ends_in_branch(block) { if ends_in_branch(block) {
let edge = outgoing_edges(block)[0]; let edge = outgoing_edges(block)[0];
if edge == header && block_is_parent_of(header, block_id, blocks) { if edge == header && block_is_parent_of(builder, header, block_id) {
continue_blocks.push(block_id); continue_blocks.push(block_id);
} }
} }
@ -457,8 +380,8 @@ fn eliminate_multiple_continue_blocks(blocks: &mut Vec<Block>, header: Word) ->
if continue_blocks.len() > 1 { if continue_blocks.len() > 1 {
let continue_block_id = continue_blocks.last().unwrap(); let continue_block_id = continue_blocks.last().unwrap();
for block_id in continue_blocks.iter().take(continue_blocks.len() - 1) { for block_id in continue_blocks.iter().take(continue_blocks.len() - 1) {
let idx = find_block_index_from_id(blocks, block_id); let idx = find_block_index_from_id(builder, block_id);
let block = &mut blocks[idx]; let block = get_block_mut!(builder, idx);
for op in &mut block.instructions.last_mut().unwrap().operands { for op in &mut block.instructions.last_mut().unwrap().operands {
if *op == Operand::IdRef(header) { if *op == Operand::IdRef(header) {
*op = Operand::IdRef(*continue_block_id); *op = Operand::IdRef(*continue_block_id);
@ -472,13 +395,13 @@ fn eliminate_multiple_continue_blocks(blocks: &mut Vec<Block>, header: Word) ->
} }
} }
fn block_leads_into_break(blocks: &[Block], cf_info: &ControlFlowInfo, start: Word) -> bool { fn block_leads_into_break(builder: &Builder, cf_info: &ControlFlowInfo, start: Word) -> bool {
let mut next: VecDeque<Word> = VecDeque::new(); let mut next: VecDeque<Word> = VecDeque::new();
next.push_back(start); next.push_back(start);
while let Some(front) = next.pop_front() { while let Some(front) = next.pop_front() {
let block_idx = find_block_index_from_id(blocks, &front); let block_idx = find_block_index_from_id(builder, &front);
let mut new_edges = outgoing_edges(&blocks[block_idx]); let mut new_edges = outgoing_edges(get_block_ref!(builder, block_idx));
// Make sure we are not looping. // Make sure we are not looping.
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
@ -492,13 +415,14 @@ fn block_leads_into_break(blocks: &[Block], cf_info: &ControlFlowInfo, start: Wo
} }
// Skip inner branches. TODO: is this correct? // Skip inner branches. TODO: is this correct?
if ends_in_branch_conditional(&blocks[find_block_index_from_id(blocks, &front)]) { if ends_in_branch_conditional(get_block_ref!(builder, block_idx)) {
new_edges.clear(); new_edges.clear();
} }
// if front is a merge block return true // if front is a merge block return true
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
if front == loop_info.merge_id && block_is_parent_of(loop_info.header_id, start, blocks) if front == loop_info.merge_id
&& block_is_parent_of(builder, loop_info.header_id, start)
{ {
return true; return true;
} }
@ -510,9 +434,9 @@ fn block_leads_into_break(blocks: &[Block], cf_info: &ControlFlowInfo, start: Wo
false false
} }
fn block_leads_into_continue(blocks: &[Block], cf_info: &ControlFlowInfo, start: Word) -> bool { fn block_leads_into_continue(builder: &Builder, cf_info: &ControlFlowInfo, start: Word) -> bool {
let start_idx = find_block_index_from_id(blocks, &start); let start_idx = find_block_index_from_id(builder, &start);
let new_edges = outgoing_edges(&blocks[start_idx]); let new_edges = outgoing_edges(get_block_ref!(builder, start_idx));
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
if new_edges.len() == 1 && loop_info.continue_id == new_edges[0] { if new_edges.len() == 1 && loop_info.continue_id == new_edges[0] {
return true; return true;
@ -523,7 +447,12 @@ fn block_leads_into_continue(blocks: &[Block], cf_info: &ControlFlowInfo, start:
} }
// every branch from a reaches b. // every branch from a reaches b.
fn block_is_reverse_idom_of(blocks: &[Block], cf_info: &ControlFlowInfo, a: Word, b: Word) -> bool { fn block_is_reverse_idom_of(
builder: &Builder,
cf_info: &ControlFlowInfo,
a: Word,
b: Word,
) -> bool {
let mut next: VecDeque<Word> = VecDeque::new(); let mut next: VecDeque<Word> = VecDeque::new();
next.push_back(a); next.push_back(a);
@ -531,13 +460,13 @@ fn block_is_reverse_idom_of(blocks: &[Block], cf_info: &ControlFlowInfo, a: Word
processed.push(a); // ensures we are not looping. processed.push(a); // ensures we are not looping.
while let Some(front) = next.pop_front() { while let Some(front) = next.pop_front() {
let block_idx = find_block_index_from_id(blocks, &front); let block_idx = find_block_index_from_id(builder, &front);
if front == b { if front == b {
continue; continue;
} }
let mut new_edges = outgoing_edges(&blocks[block_idx]); let mut new_edges = outgoing_edges(get_block_ref!(builder, block_idx));
// Skip loop bodies by jumping to the merge block is we hit a header block. // Skip loop bodies by jumping to the merge block is we hit a header block.
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
@ -549,7 +478,7 @@ fn block_is_reverse_idom_of(blocks: &[Block], cf_info: &ControlFlowInfo, a: Word
for loop_info in &cf_info.loops { for loop_info in &cf_info.loops {
// Make sure we are not looping. // Make sure we are not looping.
if block_is_parent_of(loop_info.header_id, a, blocks) if block_is_parent_of(builder, loop_info.header_id, a)
&& new_edges.contains(&loop_info.header_id) && new_edges.contains(&loop_info.header_id)
{ {
let index = new_edges let index = new_edges
@ -560,7 +489,7 @@ fn block_is_reverse_idom_of(blocks: &[Block], cf_info: &ControlFlowInfo, a: Word
} }
// Make sure we are not continuing after a merge. // Make sure we are not continuing after a merge.
if block_is_parent_of(loop_info.header_id, a, blocks) && front == loop_info.merge_id { if block_is_parent_of(builder, loop_info.header_id, a) && front == loop_info.merge_id {
new_edges.clear(); new_edges.clear();
} }
} }
@ -582,13 +511,13 @@ fn block_is_reverse_idom_of(blocks: &[Block], cf_info: &ControlFlowInfo, a: Word
true true
} }
fn get_possible_merge_positions( fn get_possible_merge_positions(
blocks: &[Block], builder: &Builder,
cf_info: &ControlFlowInfo, cf_info: &ControlFlowInfo,
start: Word, start: Word,
) -> Vec<usize> { ) -> Vec<usize> {
let mut retval = Vec::new(); let mut retval = Vec::new();
for (idx, block) in blocks.iter().enumerate() { for (idx, block) in get_blocks_ref(builder).iter().enumerate() {
if block_is_reverse_idom_of(blocks, cf_info, start, block.label_id().unwrap()) { if block_is_reverse_idom_of(builder, cf_info, start, block.label_id().unwrap()) {
retval.push(idx); retval.push(idx);
} }
} }
@ -596,7 +525,7 @@ fn get_possible_merge_positions(
retval retval
} }
fn block_is_parent_of(parent: Word, child: Word, blocks: &[Block]) -> bool { fn block_is_parent_of(builder: &Builder, parent: Word, child: Word) -> bool {
let mut next: VecDeque<Word> = VecDeque::new(); let mut next: VecDeque<Word> = VecDeque::new();
next.push_back(parent); next.push_back(parent);
@ -604,8 +533,8 @@ fn block_is_parent_of(parent: Word, child: Word, blocks: &[Block]) -> bool {
processed.push(parent); // ensures we are not looping. processed.push(parent); // ensures we are not looping.
while let Some(front) = next.pop_front() { while let Some(front) = next.pop_front() {
let block_idx = find_block_index_from_id(blocks, &front); let block_idx = find_block_index_from_id(builder, &front);
let mut new_edges = outgoing_edges(&blocks[block_idx]); let mut new_edges = outgoing_edges(get_block_ref!(builder, block_idx));
for id in &processed { for id in &processed {
if let Some(i) = new_edges.iter().position(|x| x == id) { if let Some(i) = new_edges.iter().position(|x| x == id) {
@ -626,7 +555,7 @@ fn block_is_parent_of(parent: Word, child: Word, blocks: &[Block]) -> bool {
// Returns the idx of the branch that loops. // Returns the idx of the branch that loops.
fn get_looping_branch_from_block( fn get_looping_branch_from_block(
blocks: &[Block], builder: &Builder,
cf_info: &ControlFlowInfo, cf_info: &ControlFlowInfo,
start: Word, start: Word,
) -> Option<usize> { ) -> Option<usize> {
@ -641,17 +570,18 @@ fn get_looping_branch_from_block(
continue; continue;
} }
let block_idx = find_block_index_from_id(blocks, &front); let block_idx = find_block_index_from_id(builder, &front);
let mut new_edges = outgoing_edges(&blocks[block_idx]); let mut new_edges = outgoing_edges(get_block_ref!(builder, block_idx));
let edge_it = new_edges.iter().find(|&x| x == &start); // Check if the new_edges contain the start let edge_it = new_edges.iter().find(|&x| x == &start); // Check if the new_edges contain the start
if new_edges.len() == 1 { if new_edges.len() == 1 {
if let Some(edge_it) = edge_it { if let Some(edge_it) = edge_it {
// loop over the orginal edges to find which branch is looping // loop over the orginal edges to find which branch is looping
let start_edges = outgoing_edges(&blocks[find_block_index_from_id(blocks, &start)]); let start_idx = find_block_index_from_id(builder, &front);
let start_edges = outgoing_edges(get_block_ref!(builder, start_idx));
for (i, start_edge) in start_edges.iter().enumerate() { for (i, start_edge) in start_edges.iter().enumerate() {
if start_edge == edge_it || block_is_parent_of(*start_edge, *edge_it, blocks) { if start_edge == edge_it || block_is_parent_of(builder, *start_edge, *edge_it) {
return Some(i); return Some(i);
} }
} }
@ -687,36 +617,23 @@ fn ends_in_return(block: &Block) -> bool {
} }
// Returns the new id assigned to the original block. // Returns the new id assigned to the original block.
fn split_block( fn split_block(builder: &mut Builder, block_to_split: Word, retarget: bool) -> Word {
header: &mut ModuleHeader,
blocks: &mut Vec<Block>,
block_to_split: Word,
retarget: bool,
) -> Word {
// create new block with old id.
let block_to_split_index = find_block_index_from_id(blocks, &block_to_split);
let orignial_block = &mut blocks[block_to_split_index];
let original_id = orignial_block.label_id().unwrap();
let mut new_block = Block::new();
new_block.label = orignial_block.label.clone();
// assign old block new id. // assign old block new id.
let new_original_block_id = id(header); let new_original_block_id = builder.id();
let block_to_split_index = find_block_index_from_id(builder, &block_to_split);
let orignial_block = get_block_mut!(builder, block_to_split_index);
orignial_block.label.as_mut().unwrap().result_id = Some(new_original_block_id); orignial_block.label.as_mut().unwrap().result_id = Some(new_original_block_id);
// create new block with old id.
builder.begin_block(Some(block_to_split)).unwrap();
// new block branches to old block. // new block branches to old block.
let branch_inst = Instruction::new( builder.branch(new_original_block_id).unwrap();
Op::Branch,
None,
None,
vec![Operand::IdRef(new_original_block_id)],
);
new_block.instructions.push(branch_inst);
if retarget { if retarget {
// update all merge ops to point the the old block with its new id. // update all merge ops to point the the old block with its new id.
for block in blocks.iter_mut() { for block in get_blocks_mut(builder) {
for inst in &mut block.instructions { for inst in &mut block.instructions {
if inst.class.opcode == Op::LoopMerge || inst.class.opcode == Op::SelectionMerge { if inst.class.opcode == Op::LoopMerge || inst.class.opcode == Op::SelectionMerge {
for operand in &mut inst.operands { for operand in &mut inst.operands {
if *operand == Operand::IdRef(original_id) { if *operand == Operand::IdRef(block_to_split) {
*operand = Operand::IdRef(new_original_block_id); *operand = Operand::IdRef(new_original_block_id);
} }
} }
@ -725,35 +642,25 @@ fn split_block(
} }
} }
// insert new block before the old block.
blocks.insert(block_to_split_index, new_block);
new_original_block_id new_original_block_id
} }
fn make_unreachable_block(header: &mut ModuleHeader, blocks: &mut Vec<Block>) -> Word { fn make_unreachable_block(builder: &mut Builder) -> Word {
let id = id(header); let id = builder.id();
let mut new_block = Block::new(); builder.begin_block(Some(id)).unwrap();
new_block.label = Some(Instruction::new(Op::Label, None, Some(id), vec![])); builder.unreachable().unwrap();
// new block is unreachable
new_block
.instructions
.push(Instruction::new(Op::Unreachable, None, None, vec![]));
// insert new block at the end
blocks.push(new_block);
id id
} }
pub fn insert_selection_merge_on_conditional_branch( pub fn insert_selection_merge_on_conditional_branch(
sess: &Session, sess: &Session,
header: &mut ModuleHeader, builder: &mut Builder,
blocks: &mut Vec<Block>,
cf_info: &mut ControlFlowInfo, cf_info: &mut ControlFlowInfo,
) { ) {
let mut branch_conditional_ops = Vec::new(); let mut branch_conditional_ops = Vec::new();
// Find conditional branches that are not loops // Find conditional branches that are not loops
for block in blocks.iter() { for block in get_blocks_ref(builder) {
if ends_in_branch_conditional(block) if ends_in_branch_conditional(block)
&& !cf_info.id_is_loops_header(block.label_id().unwrap()) && !cf_info.id_is_loops_header(block.label_id().unwrap())
{ {
@ -770,11 +677,11 @@ pub fn insert_selection_merge_on_conditional_branch(
None => id, None => id,
}; };
let bi = find_block_index_from_id(blocks, id); let bi = find_block_index_from_id(builder, id);
let out = outgoing_edges(&blocks[bi]); let out = outgoing_edges(&get_blocks_ref(builder)[bi]);
let id = &blocks[bi].label_id().unwrap(); let id = idx_to_id(builder, bi);
let a_nexts = get_possible_merge_positions(blocks, cf_info, out[0]); let a_nexts = get_possible_merge_positions(builder, cf_info, out[0]);
let b_nexts = get_possible_merge_positions(blocks, cf_info, out[1]); let b_nexts = get_possible_merge_positions(builder, cf_info, out[1]);
// Check for a matching possible merge position. // Check for a matching possible merge position.
let mut first_merge = None; let mut first_merge = None;
@ -789,45 +696,37 @@ pub fn insert_selection_merge_on_conditional_branch(
let merge_block_id = if let Some(idx) = first_merge { let merge_block_id = if let Some(idx) = first_merge {
// We found a existing block that we can use as a merge block! // We found a existing block that we can use as a merge block!
blocks[idx].label_id().unwrap() idx_to_id(builder, idx)
} else { } else {
let a_first_idx = find_block_index_from_id(blocks, &out[0]); let a_first_id = out[0];
let b_first_idx = find_block_index_from_id(blocks, &out[1]); let b_first_id = out[1];
let a_first_id = blocks[a_first_idx].label_id().unwrap();
let b_first_id = blocks[b_first_idx].label_id().unwrap();
let a_last_idx = match a_nexts.last() { let a_last_idx = match a_nexts.last() {
Some(last) => *last, Some(last) => *last,
None => find_block_index_from_id(blocks, &out[0]), None => find_block_index_from_id(builder, &out[0]),
}; };
let b_last_idx = match b_nexts.last() { let b_last_idx = match b_nexts.last() {
Some(last) => *last, Some(last) => *last,
None => find_block_index_from_id(blocks, &out[1]), None => find_block_index_from_id(builder, &out[1]),
}; };
let branch_a_breaks = block_leads_into_break(blocks, cf_info, a_first_id); let branch_a_breaks = block_leads_into_break(builder, cf_info, a_first_id);
let branch_b_breaks = block_leads_into_break(blocks, cf_info, b_first_id); let branch_b_breaks = block_leads_into_break(builder, cf_info, b_first_id);
let branch_a_continues = block_leads_into_continue(blocks, cf_info, a_first_id); let branch_a_continues = block_leads_into_continue(builder, cf_info, a_first_id);
let branch_b_continues = block_leads_into_continue(blocks, cf_info, b_first_id); let branch_b_continues = block_leads_into_continue(builder, cf_info, b_first_id);
let branch_a_returns = ends_in_return(&blocks[a_last_idx]); let branch_a_returns = ends_in_return(get_block_ref!(builder, a_last_idx));
let branch_b_returns = ends_in_return(&blocks[b_last_idx]); let branch_b_returns = ends_in_return(get_block_ref!(builder, b_last_idx));
if ((branch_a_breaks || branch_a_continues) && (branch_b_breaks || branch_b_continues)) if ((branch_a_breaks || branch_a_continues) && (branch_b_breaks || branch_b_continues))
|| branch_a_returns && branch_b_returns || branch_a_returns && branch_b_returns
{ {
// (fully unreachable) insert a rando block and mark as merge. // (fully unreachable) insert a rando block and mark as merge.
make_unreachable_block(header, blocks) make_unreachable_block(builder)
} else if branch_a_breaks || branch_a_continues || branch_a_returns { } else if branch_a_breaks || branch_a_continues || branch_a_returns {
// (partially unreachable) merge block becomes branch b immediatly // (partially unreachable) merge block becomes branch b immediatly
blocks[b_first_idx].label_id().unwrap() b_first_id
} else if branch_b_breaks || branch_b_continues || branch_b_returns { } else if branch_b_breaks || branch_b_continues || branch_b_returns {
// (partially unreachable) merge block becomes branch a immediatly // (partially unreachable) merge block becomes branch a immediatly
blocks[a_first_idx].label_id().unwrap() a_first_id
} else if branch_a_returns {
// (partially unreachable) merge block becomes end/start of b.
sess.fatal("UNIMPLEMENTED, A partially unreachable case was detected on a.");
} else if branch_b_returns {
// (partially unreachable) merge block becomes end/start of a.
sess.fatal("UNIMPLEMENTED, A partially unreachable case was detected on b.");
} else { } else {
// In theory this should never happen. // In theory this should never happen.
sess.fatal("UNEXPECTED, Unknown exit detected."); sess.fatal("UNEXPECTED, Unknown exit detected.");
@ -835,7 +734,7 @@ pub fn insert_selection_merge_on_conditional_branch(
}; };
if cf_info.used(merge_block_id) { if cf_info.used(merge_block_id) {
let new_id = split_block(header, blocks, merge_block_id, true); let new_id = split_block(builder, merge_block_id, true);
cf_info.retarget(merge_block_id, new_id); cf_info.retarget(merge_block_id, new_id);
if branch_conditional_ops.contains(&merge_block_id) { if branch_conditional_ops.contains(&merge_block_id) {
@ -851,8 +750,8 @@ pub fn insert_selection_merge_on_conditional_branch(
cf_info.if_merge_ids.push(merge_block_id); cf_info.if_merge_ids.push(merge_block_id);
// Insert the merge instruction // Insert the merge instruction
let bi = find_block_index_from_id(blocks, id); // after this we don't insert or remove blocks let bi = find_block_index_from_id(builder, &id); // after this we don't insert or remove blocks
let block = &mut blocks[bi]; let block = get_block_mut!(builder, bi);
let merge_inst = Instruction::new(Op::SelectionMerge, None, None, merge_operands); let merge_inst = Instruction::new(Op::SelectionMerge, None, None, merge_operands);
block block
.instructions .instructions
@ -861,18 +760,17 @@ pub fn insert_selection_merge_on_conditional_branch(
} }
pub fn insert_loop_merge_on_conditional_branch( pub fn insert_loop_merge_on_conditional_branch(
header: &mut ModuleHeader, builder: &mut Builder,
blocks: &mut Vec<Block>,
cf_info: &mut ControlFlowInfo, cf_info: &mut ControlFlowInfo,
) { ) {
let mut branch_conditional_ops = Vec::new(); let mut branch_conditional_ops = Vec::new();
// Find conditional branches that are loops, and find which branch is the one that loops. // Find conditional branches that are loops, and find which branch is the one that loops.
for block in blocks.iter() { for block in get_blocks_ref(builder) {
if ends_in_branch_conditional(block) { if ends_in_branch_conditional(block) {
let block_id = block.label_id().unwrap(); let block_id = block.label_id().unwrap();
if let Some(looping_branch_idx) = if let Some(looping_branch_idx) =
get_looping_branch_from_block(blocks, cf_info, block_id) get_looping_branch_from_block(builder, cf_info, block_id)
{ {
branch_conditional_ops.push((block_id, looping_branch_idx)); branch_conditional_ops.push((block_id, looping_branch_idx));
cf_info.loops.push(LoopInfo { cf_info.loops.push(LoopInfo {
@ -883,7 +781,6 @@ pub fn insert_loop_merge_on_conditional_branch(
} }
} }
} }
let mut modified_ids = HashMap::new(); let mut modified_ids = HashMap::new();
// Figure out which branch loops and which branch should merge, also find any potential break ops. // Figure out which branch loops and which branch should merge, also find any potential break ops.
@ -894,14 +791,14 @@ pub fn insert_loop_merge_on_conditional_branch(
}; };
let merge_branch_idx = (looping_branch_idx + 1) % 2; let merge_branch_idx = (looping_branch_idx + 1) % 2;
let bi = find_block_index_from_id(blocks, &id); let bi = find_block_index_from_id(builder, &id);
let out = outgoing_edges(&blocks[bi]); let out = outgoing_edges(&get_blocks_ref(builder)[bi]);
let continue_block_id = eliminate_multiple_continue_blocks(blocks, id); let continue_block_id = eliminate_multiple_continue_blocks(builder, id);
let merge_block_id = out[merge_branch_idx]; let merge_block_id = out[merge_branch_idx];
if cf_info.used(continue_block_id) { if cf_info.used(continue_block_id) {
let new_id = split_block(header, blocks, continue_block_id, true); let new_id = split_block(builder, continue_block_id, true);
cf_info.retarget(continue_block_id, new_id); cf_info.retarget(continue_block_id, new_id);
if branch_conditional_ops.contains(&(continue_block_id, *looping_branch_idx)) { if branch_conditional_ops.contains(&(continue_block_id, *looping_branch_idx)) {
@ -909,7 +806,7 @@ pub fn insert_loop_merge_on_conditional_branch(
} }
} }
if cf_info.used(merge_block_id) { if cf_info.used(merge_block_id) {
let new_id = split_block(header, blocks, merge_block_id, true); let new_id = split_block(builder, merge_block_id, true);
cf_info.retarget(merge_block_id, new_id); cf_info.retarget(merge_block_id, new_id);
if branch_conditional_ops.contains(&(merge_block_id, *looping_branch_idx)) { if branch_conditional_ops.contains(&(merge_block_id, *looping_branch_idx)) {
@ -917,8 +814,8 @@ pub fn insert_loop_merge_on_conditional_branch(
} }
} }
let bi = find_block_index_from_id(blocks, &id); // after this we don't insert or remove blocks let bi = find_block_index_from_id(builder, &id); // after this we don't insert or remove blocks
let check_block = &mut blocks[bi]; let check_block = &mut get_blocks_mut(builder)[bi];
let merge_operands = vec![ let merge_operands = vec![
Operand::IdRef(merge_block_id), Operand::IdRef(merge_block_id),

View File

@ -328,3 +328,22 @@ pub fn main(i: Input<i32>) {
} }
"#); "#);
} }
#[test]
fn cf_defer() {
val(r#"
#[allow(unused_attributes)]
#[spirv(fragment)]
pub fn main(i: Input<i32>) {
while i.load() < 32 {
let current_position = 0;
if i.load() < current_position {
break;
}
if i.load() < current_position {
break;
}
}
}
"#);
}