Rust에서 인라인 asm 템플릿 만들기
시작하기 전에
멀티미디어, 러스트 또는 어셈블리에 익숙하지 않은 사용자를 위해
x86_64 AVX2
또는 ARM NEON
와 같은 아키텍처별 확장을 사용하게 됩니다. 녹 및 조립
Rust는 어셈블리를 지원하는 데 상당히 약점이 있었습니다.
rustc
는 .s
또는 .S
를 gcc
및 clang
처럼 컴파일하지 않으므로 cc-rs 또는 nasm-rs에 의존하게 됩니다.rustc
는 최근까지 인라인 어셈블리에 대한 안정적인 지원이 없었으며 지금은 유용한 부분이 여전히 nightly에 있습니다. 우리는 얼마나 멀리 있습니까?
Kostya는 현재 안정 버전을 시도했고 h264 디코더용 어셈블리를 작성하여 20%의 이득을 얻었지만 3가지 문제가 있었습니다.
format!
용어를 알고 있고 asm!
관련 용어를 알고 있다고 가정하기 때문에 그에게 그다지 도움이 되지 않았습니다. this is being addressed안정의
asm!
피연산자는 sym 및 const 을 포함하지 않습니다. macro_rules!()
에 익숙했던 것처럼 gcc
를 사용하여 어셈블리 템플릿을 처리하는 방법을 알 수 없었습니다. 나는 zulip보다 더 나은 방법을 생각할 수 없었기 때문에 munching tokens에 요청했으며 일반적으로 이것은 훨씬 간단하고 분명한 것을 놓치고 있음을 의미합니다.
운 좋게도 Amanieu가 우리를 도왔고 이 블로그 게시물은 메모 유지에 관한 것입니다.
인라인 ASM 템플릿 사용 사례
멀티미디어 소프트웨어에서는
4x4
, 8x8
, 16x16
등의 픽셀 블록에서 작동하는 작은 커널을 작성하는 경우가 많으며 일반적으로 동일한 내부 논리가 공유되며 이상적으로는 자신을 반복하지 않기를 원합니다. x86이 가진 많은 확장에서 동일한 논리를 공유할 수 있다면 말입니다.Kostya는 이것을 예로 사용했습니다.
fn avg_4(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bh: usize) {
unsafe {
asm!(
"2:",
"movd xmm1, [{src}]",
"movd xmm3, [{src} + {sstride}]",
"movd xmm0, [{dst}]",
"movd xmm2, [{dst} + {dstride}]",
"lea {src}, [{src} + {sstride} * 2]",
"pavgb xmm0, xmm1",
"pavgb xmm2, xmm3",
"movd [{dst}], xmm0",
"movd [{dst} + {dstride}], xmm2",
"lea {dst}, [{dst} + {dstride} * 2]",
"sub {h}, 2",
"jnz 2b",
src = inout(reg) src.as_ptr() => _,
sstride = in(reg) sstride,
dst = inout(reg) dst.as_mut_ptr() => _,
dstride = in(reg) dstride,
h = inout(reg) bh => _,
out("xmm0") _,
out("xmm1") _,
out("xmm2") _,
out("xmm3") _,
);
}
}
fn avg_8(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bh: usize) {
unsafe {
asm!(
"2:",
"movq xmm0, [{src}]",
"movq xmm1, [{src} + {sstride}]",
"movq xmm2, [{dst}]",
"movq xmm3, [{dst} + {dstride}]",
"lea {src}, [{src} + {sstride} * 2]",
"pavgb xmm0, xmm2",
"pavgb xmm1, xmm3",
"movq [{dst}], xmm0",
"movq [{dst} + {dstride}], xmm1",
"lea {dst}, [{dst} + {dstride} * 2]",
"sub {h}, 2",
"jnz 2b",
src = inout(reg) src.as_ptr() => _,
sstride = in(reg) sstride,
dst = inout(reg) dst.as_mut_ptr() => _,
dstride = in(reg) dstride,
h = inout(reg) bh => _,
out("xmm0") _,
out("xmm1") _,
out("xmm2") _,
out("xmm3") _,
);
}
}
fn avg_16(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bh: usize) {
unsafe {
asm!(
"2:",
"movaps xmm0, [{src}]",
"movaps xmm1, [{src} + {sstride}]",
"pavgb xmm0, [{dst}]",
"pavgb xmm1, [{dst} + {dstride}]",
"lea {src}, [{src} + {sstride} * 2]",
"movq [{dst}], xmm0",
"movq [{dst} + {dstride}], xmm1",
"lea {dst}, [{dst} + {dstride} * 2]",
"sub {h}, 2",
"jnz 2b",
src = inout(reg) src.as_ptr() => _,
sstride = in(reg) sstride,
dst = inout(reg) dst.as_mut_ptr() => _,
dstride = in(reg) dstride,
h = inout(reg) bh => _,
out("xmm0") _,
out("xmm1") _,
);
}
}
avg_4
와 avg_8
사이에는 movd
대 movq
가 있으며 이를 위해 Amalieu는 corosensei에서 사용하는 concat!
패턴을 사용할 것을 제안했습니다.avg_4 및 avg_8은 결국
macro_rules! avg {
($name: ident, $mov:literal) => {
fn $name(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bh: usize) {
unsafe {
asm!(
"2:",
concat!($mov, " xmm1, [{src}]"),
concat!($mov, " xmm3, [{src} + {sstride}]"),
concat!($mov, " xmm0, [{dst}]"),
concat!($mov, " xmm2, [{dst} + {dstride}]"),
"lea {src}, [{src} + {sstride} * 2]",
"pavgb xmm0, xmm1",
"pavgb xmm2, xmm3",
concat!($mov, " [{dst}], xmm0"),
concat!($mov, " [{dst} + {dstride}], xmm2"),
"lea {dst}, [{dst} + {dstride} * 2]",
"sub {h}, 2",
"jnz 2b",
src = inout(reg) src.as_ptr() => _,
sstride = in(reg) sstride,
dst = inout(reg) dst.as_mut_ptr() => _,
dstride = in(reg) dstride,
h = inout(reg) bh => _,
out("xmm0") _,
out("xmm1") _,
out("xmm2") _,
out("xmm3") _,
);
}
}
}
}
avg!{avg_4};
avg!{avg_8};
avg_8 및 avg_16을 분해하려면
operands
를 처리하는 방법이 필요하며 asm 문은 literals
이지만 operands
는 tt
에서 macro_rules!
로만 표현할 수 있습니다.이 사용 사례를 처리하는 일반적인 방법은 전처리기
#if
지시문에 의존하는 것입니다. macro_rules를 사용하면 좀 더 창의적이어야 합니다.Amalieu는 나에게 another example을 주었고 결국 다음과 같이 만들었습니다.
macro_rules! avg_common {
($name:ident { $($load:literal),* } { $($store:literal),* } $($out:tt)*) => {
fn $name(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, bh: usize) {
unsafe {
asm!(
"2:",
$($load),*,
"lea {src}, [{src} + {sstride} * 2]",
"pavgb xmm0, xmm2",
"pavgb xmm1, xmm3",
$($store),*,
"lea {dst}, [{dst} + {dstride} * 2]",
"sub {h}, 2",
"jnz 2b",
src = inout(reg) src.as_ptr() => _,
sstride = in(reg) sstride,
dst = inout(reg) dst.as_mut_ptr() => _,
dstride = in(reg) dstride,
h = inout(reg) bh => _,
$($out)*
)
}
}
}
}
macro_rules! avg {
(avg_8) => {
avg_common!{avg_8 {
"movq xmm0, [{src}]",
"movq xmm1, [{src} + {sstride}]",
"movq xmm2, [{dst}]",
"movq xmm3, [{dst} + {dstride}]"
}
{
"movq [{dst}], xmm0",
"movq [{dst} + {dstride}], xmm1"
}
out("xmm0") _,
out("xmm1") _,
out("xmm2") _,
out("xmm3") _,
}
};
(avg_16) => {
avg_common!{avg_16 {
"movaps xmm0, [{src}]",
"movaps xmm1, [{src} + {sstride}]",
"pavgb xmm0, [{dst}]",
"pavgb xmm1, [{dst} + {dstride}]"
}
{
"movq [{dst}], xmm0",
"movq [{dst} + {dstride}], xmm1"
}
out("xmm0") _,
out("xmm1") _,
}
};
}
피연산자의 여러 블록을 지원하더라도 추가 주의가 필요한 경우에도 Kostya 문제를 어느 정도 해결합니다.
다음에 온다
나는 특히 Rust에서 SIFIS-Home의 새로운 구현을 작성하는 WebOfThings 프로젝트로 꽤 바빴습니다. 곧 우리는 wot-1.1을 지원하는 첫 번째 버전을 출시할 것이고 아마 그것에 대해 조금 쓸 것입니다.
Reference
이 문제에 관하여(Rust에서 인라인 asm 템플릿 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/luzero/templating-inline-asm-in-rust-37ee텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)