Rust에서 인라인 asm 템플릿 만들기

29531 단어
Kostya는 about it을 썼고 그가 놓친 것이 이미 어떤 식으로든 사용할 수 없는지 알아내도록 요청했습니다.

시작하기 전에



멀티미디어, 러스트 또는 어셈블리에 익숙하지 않은 사용자를 위해
  • 멀티미디어는 데이터를 처리하고 적시에 사용자에게 제공/렌더링하는 것입니다.
  • 대기 시간을 잘 제어하고 최소한의 CPU를 사용해야 합니다.
  • 이로 인해 x86_64 AVX2 또는 ARM NEON 와 같은 아키텍처별 확장을 사용하게 됩니다.
  • C(또는 Rust)와 같은 고급 언어는 내장 함수를 통해 이러한 확장에 대한 액세스를 제공하지만 꽤 번거롭기 때문에 어셈블리를 있는 그대로 작성하는 것이 더 즐겁습니다.
  • 예를 보려면 dav1drav1e을 참조하십시오.

  • 녹 및 조립



    Rust는 어셈블리를 지원하는 데 상당히 약점이 있었습니다.
  • rustc.s 또는 .Sgccclang처럼 컴파일하지 않으므로 cc-rs 또는 nasm-rs에 의존하게 됩니다.
  • rustc는 최근까지 인라인 어셈블리에 대한 안정적인 지원이 없었으며 지금은 유용한 부분이 여전히 nightly에 있습니다.

  • 우리는 얼마나 멀리 있습니까?



    Kostya는 현재 안정 버전을 시도했고 h264 디코더용 어셈블리를 작성하여 20%의 이득을 얻었지만 3가지 문제가 있었습니다.
  • 그는 하위 레지스터를 관리하는 방법을 알 수 없었고 컴파일러 경고는 사용자가 format! 용어를 알고 있고 asm! 관련 용어를 알고 있다고 가정하기 때문에 그에게 그다지 도움이 되지 않았습니다. this is being addressed

  • 안정의 asm! 피연산자는 symconst 을 포함하지 않습니다.
  • Kostya는 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_4avg_8 사이에는 movdmovq가 있으며 이를 위해 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이지만 operandstt에서 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을 지원하는 첫 번째 버전을 출시할 것이고 아마 그것에 대해 조금 쓸 것입니다.

    좋은 웹페이지 즐겨찾기