Skip to content
470 changes: 257 additions & 213 deletions src/cf/structurize.rs

Large diffs are not rendered by default.

297 changes: 226 additions & 71 deletions src/cf/unstructured.rs

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ pub enum TypeKind {
// and kept separately in `VarDecl`, might be a better approach?
QPtr,

// TODO(eddyb) reconsider name? add signature? etc.
Thunk,

SpvInst {
spv_inst: spv::Inst,
// FIXME(eddyb) find a better name.
Expand Down Expand Up @@ -754,9 +757,8 @@ pub struct FuncDefBody {
/// [`Node`], or function (the latter being a "structured return")
/// * "divergent": execution gets stuck in the region (an infinite loop),
/// or is aborted (e.g. `OpTerminateInvocation` from SPIR-V)
/// * "unstructured": [`Region`]s which connect to other [`Region`]s
/// using [`cfg::ControlInst`](crate::cfg::ControlInst)s (as described by a
/// [`cfg::ControlFlowGraph`](crate::cfg::ControlFlowGraph))
/// * "unstructured": [`Region`]s which connect to other [`Region`]s using "`thunk`s"
/// (as described by [`cfg::ControlFlowGraph`](crate::cfg::ControlFlowGraph))
///
/// When a function's entire body can be described by a single [`Region`],
/// that function is said to have (entirely) "structured control-flow".
Expand Down Expand Up @@ -802,8 +804,7 @@ pub struct FuncDefBody {
/// instead of in the merge (where phi nodes require special-casing, as
/// their "uses" of all the "source" values would normally be illegal)
/// * in unstructured control-flow, region `inputs` are additionally used for
/// representing phi nodes, as [`cfg::ControlInst`](crate::cfg::ControlInst)s
/// passing values to their target regions
/// representing phi nodes, as `thunk`s passing values to their target regions
/// * all value uses across unstructured control-flow edges (i.e. not in the
/// same region containing the value definition) *require* explicit passing,
/// as unstructured control-flow [`Region`](crate::Region)s
Expand Down Expand Up @@ -907,6 +908,9 @@ pub enum NodeKind {
#[from]
QPtr(qptr::QPtrOp),

// TODO(eddyb) document (maybe move into e.g. `cf::ThunkOp`?).
ThunkBind(cf::unstructured::ControlTarget),

// FIXME(eddyb) should this have `#[from]`?
SpvInst(spv::Inst),
SpvExtInst {
Expand Down
16 changes: 16 additions & 0 deletions src/mem/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ impl<'a> GatherAccesses<'a> {
DataInstKind::FuncCall(_)
| DataInstKind::Mem(_)
| DataInstKind::QPtr(_)
| DataInstKind::ThunkBind(_)
| DataInstKind::SpvInst(_)
| DataInstKind::SpvExtInst { .. } => {}
}
Expand Down Expand Up @@ -1216,6 +1217,21 @@ impl<'a> GatherAccesses<'a> {
);
}

DataInstKind::ThunkBind(_) => {
if data_inst_def
.inputs
.iter()
.any(|&v| is_qptr(func_def_body.at(v).type_of(&cx)))
{
accesses_or_err_attrs_to_attach.push((
AttrTarget::Node(node),
Err(AnalysisError(Diag::bug([
"unsupported `thunk.bind` with pointer inputs".into(),
]))),
));
}
}

DataInstKind::SpvInst(_) | DataInstKind::SpvExtInst { .. } => {
let mut has_from_spv_ptr_output_attr = false;
for attr in &cx[data_inst_def.attrs].attrs {
Expand Down
3 changes: 3 additions & 0 deletions src/mem/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ impl<'a> LayoutCache<'a> {
["`layout_of(qptr)` (already lowered?)".into()],
)));
}
TypeKind::Thunk => {
return Err(LayoutError(Diag::bug(["`layout_of(thunk)`".into()])));
}
TypeKind::SpvInst { spv_inst, type_and_const_inputs } => {
(spv_inst, type_and_const_inputs)
}
Expand Down
138 changes: 43 additions & 95 deletions src/print/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,20 +613,16 @@ impl<'a> Visitor<'a> for Plan<'a> {
}
}

fn visit_func_decl(&mut self, func_decl: &'a FuncDecl) {
if let DeclDef::Present(func_def_body) = &func_decl.def
&& let Some(cfg) = &func_def_body.unstructured_cfg
{
for region in cfg.rev_post_order(func_def_body) {
if let Some(control_inst) = cfg.control_inst_on_exit_from.get(region) {
for &target in &control_inst.targets {
*self.use_counts.entry(Use::RegionLabel(target)).or_default() += 1;
}
fn visit_node_def(&mut self, func_at_node: FuncAt<'a, Node>) {
if let DataInstKind::ThunkBind(target) = func_at_node.def().kind {
match target {
cf::unstructured::ControlTarget::Region(target) => {
*self.use_counts.entry(Use::RegionLabel(target)).or_default() += 1;
}
cf::unstructured::ControlTarget::Return => {}
}
}

func_decl.inner_visit_with(self);
func_at_node.inner_visit_with(self);
}

fn visit_value_use(&mut self, v: &'a Value) {
Expand Down Expand Up @@ -791,13 +787,8 @@ impl DbgScopeDefPlacer<'_> {
None => self.scopes_used_in_region(func_def_body.at_body()),
Some(cfg) => DbgScopeDefMap::merge_unordered(
DbgScopeDefPlace::top_of(func_def_body.at_body()),
cfg.rev_post_order(func_def_body).map(|region| {
let mut scopes = self.scopes_used_in_region(func_def_body.at(region));
if let Some(control_inst) = cfg.control_inst_on_exit_from.get(region) {
scopes.prepend_attrs(self.cx, control_inst.attrs);
}
scopes
}),
cfg.rev_post_order(func_def_body)
.map(|region| self.scopes_used_in_region(func_def_body.at(region))),
),
}
}
Expand Down Expand Up @@ -1110,9 +1101,9 @@ impl<'a> Printer<'a> {
|| type_and_const_inputs.is_empty()
}

TypeKind::QPtr | TypeKind::SpvStringLiteralForExtInst => {
true
}
TypeKind::QPtr
| TypeKind::Thunk
| TypeKind::SpvStringLiteralForExtInst => true,
};

ty_def.attrs == AttrSet::default()
Expand Down Expand Up @@ -3127,6 +3118,8 @@ impl Print for TypeDef {
// FIXME(eddyb) should this be shortened to `qtr`?
TypeKind::QPtr => printer.declarative_keyword_style().apply("qptr").into(),

TypeKind::Thunk => printer.imperative_keyword_style().apply("thunk").into(),

TypeKind::SpvInst { spv_inst, type_and_const_inputs } => printer
.pretty_spv_inst(
printer.spv_op_style(),
Expand Down Expand Up @@ -3550,24 +3543,23 @@ impl Print for FuncDecl {
pretty::Fragment::default()
};

// FIXME(eddyb) `:` as used here for C-like "label syntax"
// interferes (in theory) with `e: T` "type ascription syntax".
pretty::Fragment::new([
pretty::Node::ForceLineSeparation.into(),
label.print_as_def(printer),
label_inputs,
":".into(),
pretty::Node::ForceLineSeparation.into(),
])
} else if region == def.body {
printer.imperative_keyword_style().apply("entry").into()
} else {
pretty::Fragment::default()
printer.error_style().apply("/* undefined label */_").into()
};

pretty::Fragment::new([
label_header,
" {".into(),
pretty::Node::IndentedBlock(vec![def.at(region).print(printer)])
.into(),
cfg.control_inst_on_exit_from[region].print(printer),
"}".into(),
])
})
.intersperse({
Expand Down Expand Up @@ -3845,6 +3837,7 @@ impl Print for FuncAt<'_, Node> {
DataInstKind::FuncCall(_)
| DataInstKind::Mem(_)
| DataInstKind::QPtr(_)
| DataInstKind::ThunkBind(_)
| DataInstKind::SpvInst(_)
| DataInstKind::SpvExtInst { .. } => {
// FIXME(eddyb) `outputs_header` is wastefully built even in
Expand Down Expand Up @@ -4030,6 +4023,29 @@ impl FuncAt<'_, DataInst> {
])
}

&DataInstKind::ThunkBind(target) => pretty::Fragment::new([
printer
.demote_style_for_namespace_prefix(printer.declarative_keyword_style())
.apply("thunk.")
.into(),
printer.imperative_keyword_style().apply("bind").into(),
pretty::join_comma_sep(
"(",
[
match target {
cf::unstructured::ControlTarget::Region(target) => {
Use::RegionLabel(target).print(printer)
}
cf::unstructured::ControlTarget::Return => {
printer.imperative_keyword_style().apply("return").into()
}
},
pretty::join_comma_sep("(", inputs.iter().map(|v| v.print(printer)), ")"),
],
")",
),
]),

DataInstKind::SpvInst(inst) => printer.pretty_spv_inst(
printer.spv_op_style(),
inst.opcode,
Expand Down Expand Up @@ -4253,74 +4269,6 @@ impl FuncAt<'_, DataInst> {
}
}

impl Print for cf::unstructured::ControlInst {
type Output = pretty::Fragment;
fn print(&self, printer: &Printer<'_>) -> pretty::Fragment {
let Self { attrs, kind, inputs, targets, target_inputs } = self;

let attrs = attrs.print(printer);

let kw_style = printer.imperative_keyword_style();
let kw = |kw| kw_style.apply(kw).into();

let mut targets = targets.iter().map(|&target_region| {
let mut target = pretty::Fragment::new([
kw("branch"),
" ".into(),
Use::RegionLabel(target_region).print(printer),
]);
if let Some(inputs) = target_inputs.get(&target_region) {
target = pretty::Fragment::new([
target,
pretty::join_comma_sep("(", inputs.iter().map(|v| v.print(printer)), ")"),
]);
}
target
});

let def = match kind {
cf::unstructured::ControlInstKind::Unreachable => {
// FIXME(eddyb) use `targets.is_empty()` when that is stabilized.
assert!(targets.len() == 0 && inputs.is_empty());
kw("unreachable")
}
cf::unstructured::ControlInstKind::Return => {
// FIXME(eddyb) use `targets.is_empty()` when that is stabilized.
assert!(targets.len() == 0);
match inputs[..] {
[] => kw("return"),
[v] => pretty::Fragment::new([kw("return"), " ".into(), v.print(printer)]),
_ => unreachable!(),
}
}
cf::unstructured::ControlInstKind::ExitInvocation(cf::ExitInvocationKind::SpvInst(
spv::Inst { opcode, imms },
)) => {
// FIXME(eddyb) use `targets.is_empty()` when that is stabilized.
assert!(targets.len() == 0);
printer.pretty_spv_inst(
kw_style,
*opcode,
imms,
inputs.iter().map(|v| v.print(printer)),
)
}

cf::unstructured::ControlInstKind::Branch => {
assert_eq!((targets.len(), inputs.len()), (1, 0));
targets.next().unwrap()
}

cf::unstructured::ControlInstKind::SelectBranch(kind) => {
assert_eq!(inputs.len(), 1);
kind.print_with_scrutinee_and_cases(printer, kw_style, inputs[0], targets)
}
};

pretty::Fragment::new([attrs, def])
}
}

impl SelectionKind {
fn print_with_scrutinee_and_cases(
&self,
Expand Down
11 changes: 11 additions & 0 deletions src/qptr/lift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,17 @@ impl LiftToSpvPtrInstsInFunc<'_> {
new_data_inst_def
}

&DataInstKind::ThunkBind(_) => {
for &v in &data_inst_def.inputs {
if self.lifter.as_spv_ptr_type(type_of_val(v)).is_some() {
return Err(LiftError(Diag::bug([
"unsupported `thunk.bind` with pointer inputs".into(),
])));
}
}
return Ok(Transformed::Unchanged);
}

DataInstKind::SpvInst(_) | DataInstKind::SpvExtInst { .. } => {
let mut to_spv_ptr_input_adjustments = vec![];
let mut from_spv_ptr_output = None;
Expand Down
3 changes: 2 additions & 1 deletion src/qptr/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,8 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
| NodeKind::ExitInvocation(_)
| DataInstKind::FuncCall(_)
| DataInstKind::Mem(_)
| DataInstKind::QPtr(_) => return,
| DataInstKind::QPtr(_)
| DataInstKind::ThunkBind(_) => return,

DataInstKind::SpvInst(_) | DataInstKind::SpvExtInst { .. } => {}
}
Expand Down
Loading
Loading