struct AsyncCopyCall {
elem: u32,
dst: String,
dst_epr: String,
dst_dims: String,
src: String,
src_epr: String,
src_dims: String,
offset: String,
result: String,
}
pub(crate) fn lower_simdgroup_async_copy(san_ll: &str) -> String {
if !san_ll.contains("air.simdgroup_async_copy_2d") {
return san_ll.to_string();
}
let mut elem_sizes: std::collections::BTreeSet<u32> = std::collections::BTreeSet::new();
let mut out: Vec<String> = Vec::new();
let mut fresh = fresh_counter(san_ll);
for line in san_ll.lines() {
let trimmed = line.trim_start();
if trimmed.contains("air.simdgroup_async_copy_2d") && trimmed.contains("call") {
match parse_async_copy(trimmed) {
Some(call) => {
elem_sizes.insert(call.elem);
let indent = &line[..line.len() - trimmed.len()];
emit_copy_call(&mut out, indent, &call, &mut fresh);
continue;
}
None => {
out.push(line.to_string());
continue;
}
}
}
if trimmed.contains("air.is_null_simdgroup_event") && trimmed.contains("call") {
if let Some(res) = call_result_id(trimmed) {
let indent = &line[..line.len() - trimmed.len()];
out.push(format!("{indent}{res} = icmp ne i64 0, 0"));
continue;
}
}
if trimmed.contains("air.wait_simdgroup_events") && trimmed.contains("call") {
let indent = &line[..line.len() - trimmed.len()];
out.push(format!("{indent}call void @air.wg.barrier(i32 2, i32 1)"));
continue;
}
out.push(line.to_string());
}
let mut result = out.join("\n");
if san_ll.ends_with('\n') {
result.push('\n');
}
for &elem in &elem_sizes {
result.push_str(&helper_definition(elem));
}
if !san_ll.contains("declare void @air.wg.barrier") {
result.push_str("\ndeclare void @air.wg.barrier(i32, i32)\n");
}
result
}
fn emit_copy_call(out: &mut Vec<String>, indent: &str, call: &AsyncCopyCall, fresh: &mut u64) {
let mut id = || {
let v = format!("%__ac{}", *fresh);
*fresh += 1;
v
};
let (dw, dh, sw, sh, ox, oy) = (id(), id(), id(), id(), id(), id());
out.push(format!(
"{indent}{dw} = extractelement <2 x i64> {}, i64 0",
call.dst_dims
));
out.push(format!(
"{indent}{dh} = extractelement <2 x i64> {}, i64 1",
call.dst_dims
));
out.push(format!(
"{indent}{sw} = extractelement <2 x i64> {}, i64 0",
call.src_dims
));
out.push(format!(
"{indent}{sh} = extractelement <2 x i64> {}, i64 1",
call.src_dims
));
out.push(format!(
"{indent}{ox} = extractelement <2 x i64> {}, i64 0",
call.offset
));
out.push(format!(
"{indent}{oy} = extractelement <2 x i64> {}, i64 1",
call.offset
));
out.push(format!(
"{indent}call void @__metal2vulkan_sac2d_e{}(ptr addrspace(3) {}, i64 {}, i64 {dw}, i64 {dh}, ptr addrspace(1) {}, i64 {}, i64 {sw}, i64 {sh}, i64 {ox}, i64 {oy})",
call.elem, call.dst, call.dst_epr, call.src, call.src_epr
));
out.push(format!("{indent}{} = inttoptr i64 1 to ptr", call.result));
}
fn helper_definition(elem: u32) -> String {
let ty = match elem {
2 => "i16",
_ => "i32",
};
format!(
r#"
define internal void @__metal2vulkan_sac2d_e{elem}(ptr addrspace(3) %dst, i64 %depr, i64 %dw, i64 %dh, ptr addrspace(1) %src, i64 %sepr, i64 %sw, i64 %sh, i64 %ox, i64 %oy) {{
entry:
br label %rowhead
rowhead:
%r = phi i64 [ 0, %entry ], [ %rnext, %rowlatch ]
%rok = icmp ult i64 %r, %dh
br i1 %rok, label %colhead, label %done
colhead:
%c = phi i64 [ 0, %rowhead ], [ %cnext, %collatch ]
%cok = icmp ult i64 %c, %dw
br i1 %cok, label %body, label %rowlatch
body:
%sx = add i64 %ox, %c
%sy = add i64 %oy, %r
%xin = icmp ult i64 %sx, %sw
%yin = icmp ult i64 %sy, %sh
%in = and i1 %xin, %yin
%didx = mul i64 %r, %depr
%didx2 = add i64 %didx, %c
%dp = getelementptr {ty}, ptr addrspace(3) %dst, i64 %didx2
br i1 %in, label %copy, label %zero
copy:
%syr = mul i64 %sy, %sepr
%sidx = add i64 %syr, %sx
%sp = getelementptr {ty}, ptr addrspace(1) %src, i64 %sidx
%v = load {ty}, ptr addrspace(1) %sp
store {ty} %v, ptr addrspace(3) %dp
br label %collatch
zero:
store {ty} 0, ptr addrspace(3) %dp
br label %collatch
collatch:
%cnext = add i64 %c, 1
br label %colhead
rowlatch:
%rnext = add i64 %r, 1
br label %rowhead
done:
ret void
}}
"#
)
}
fn parse_async_copy(line: &str) -> Option<AsyncCopyCall> {
let result = line.split('=').next()?.trim().to_string();
if !result.starts_with('%') {
return None;
}
let open = line.find('(')?;
let args_str = &line[open + 1..line.rfind(')')?];
let args = split_args(args_str);
if args.len() != 12 {
return None;
}
let elem: u32 = operand_value(&args[0]).parse().ok()?;
Some(AsyncCopyCall {
elem,
dst: operand_value(&args[2]),
dst_epr: operand_value(&args[3]),
dst_dims: operand_value(&args[5]),
src: operand_value(&args[6]),
src_epr: operand_value(&args[7]),
src_dims: operand_value(&args[9]),
offset: operand_value(&args[10]),
result,
})
}
fn split_args(s: &str) -> Vec<String> {
let mut out = Vec::new();
let mut depth = 0i32;
let mut cur = String::new();
for ch in s.chars() {
match ch {
'<' | '(' | '[' | '{' => {
depth += 1;
cur.push(ch);
}
'>' | ')' | ']' | '}' => {
depth -= 1;
cur.push(ch);
}
',' if depth == 0 => {
out.push(cur.trim().to_string());
cur.clear();
}
_ => cur.push(ch),
}
}
if !cur.trim().is_empty() {
out.push(cur.trim().to_string());
}
out
}
fn operand_value(arg: &str) -> String {
let a = arg.trim();
if let Some(rest) = a.strip_prefix("<2 x i64>") {
return rest.trim().to_string();
}
a.rsplit(' ').next().unwrap_or("").to_string()
}
fn call_result_id(line: &str) -> Option<String> {
let head = line.split('=').next()?.trim();
head.starts_with('%').then(|| head.to_string())
}
fn fresh_counter(_san_ll: &str) -> u64 {
0
}