extern "rust-call" とは何か

extern "rust-call" は(Rust 1.16.0時点では) Fn, FnMut, FnOnce の宣言に出現する。この意味について Rust issue 41058: Get rid of the “rust-call” hack の説明がわかりやすい。

Currently, we fake variadics by defining functions with the rust-call ABI. These functions receive a tuple as parameter (of arbitrary arity and types), but in their ABI the tuple is expanded.

つまり、 rust-call は、可変長な型多相性をタプルで代用するにあたって、ABIを展開後の引数リストに合わせるためのハック的な仕組みということになる。

rust-callが処理されるまで

extern "rust-call"AbiDatas により abi::Abi::RustCall に変換される。(なお 単に extern とすると Abi::C になり、何も指定しないと Abi::Rust になる。)

rust-call のつけられた関数の受け取り側では、construct_fn 内で spread_arg というマークがつけられる。 さらに arg_local_refs で引数の取り出し処理を生成する際に特殊な処理が行われる。これは、バラバラに渡された末尾引数をタプルに戻す処理を書いている。

一方、 rust-call 関数を呼び出す側では、trans_block の関数呼び出しに対する処理で、末尾のタプルを分解して渡すようになっている。

rust-callの生成

手動で生成するほかに、以下の位置で rust-call な関数が生成されている。

実験

#![feature(unboxed_closures)]

pub fn f(x: (u8, u8, u8, u8, u8, u8, u8, u8)) {
}
pub extern "rust-call" fn g(x: (u8, u8, u8, u8, u8, u8, u8, u8)) {
}

fn main() {
    f((10, 11, 12, 13, 14, 15, 16, 17));
    g((10, 11, 12, 13, 14, 15, 16, 17));
}

これをデバッグモードでLLVM IRまでコンパイルすると以下のようになる。

; ModuleID = 'rust_out.cgu-0.rs'
source_filename = "rust_out.cgu-0.rs"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@__rustc_debug_gdb_scripts_section__ = internal unnamed_addr constant [34 x i8] c"\01gdb_load_rust_pretty_printers.py\00", section ".debug_gdb_scripts", align 1

; Function Attrs: uwtable
define internal void @_ZN8rust_out1f17h77fb50884e4b9292E(i64) unnamed_addr #0 !dbg !5 {
entry-block:
  %x = alloca { i8, i8, i8, i8, i8, i8, i8, i8 }
  %_0 = alloca {}
  %abi_cast = alloca i64
  %arg0 = alloca { i8, i8, i8, i8, i8, i8, i8, i8 }
  store i64 %0, i64* %abi_cast
  %1 = bitcast { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0 to i8*
  %2 = bitcast i64* %abi_cast to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %1, i8* %2, i64 8, i32 1, i1 false)
  call void @llvm.dbg.declare(metadata { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, metadata !22, metadata !23), !dbg !24
  call void @llvm.dbg.declare(metadata { i8, i8, i8, i8, i8, i8, i8, i8 }* %x, metadata !25, metadata !23), !dbg !27
  br label %start, !dbg !27

start:                                            ; preds = %entry-block
  %3 = bitcast { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0 to i8*, !dbg !28
  %4 = bitcast { i8, i8, i8, i8, i8, i8, i8, i8 }* %x to i8*, !dbg !28
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %4, i8* %3, i64 8, i32 1, i1 false), !dbg !28
  ret void, !dbg !29
}

; Function Attrs: uwtable
define internal void @_ZN8rust_out1g17h976fe88719539209E(i8, i8, i8, i8, i8, i8, i8, i8) unnamed_addr #0 !dbg !30 {
entry-block:
  %x = alloca { i8, i8, i8, i8, i8, i8, i8, i8 }
  %_0 = alloca {}
  %arg0 = alloca { i8, i8, i8, i8, i8, i8, i8, i8 }
  %8 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 0
  store i8 %0, i8* %8
  %9 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 1
  store i8 %1, i8* %9
  %10 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 2
  store i8 %2, i8* %10
  %11 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 3
  store i8 %3, i8* %11
  %12 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 4
  store i8 %4, i8* %12
  %13 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 5
  store i8 %5, i8* %13
  %14 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 6
  store i8 %6, i8* %14
  %15 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, i32 0, i32 7
  store i8 %7, i8* %15
  call void @llvm.dbg.declare(metadata { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0, metadata !33, metadata !23), !dbg !34
  call void @llvm.dbg.declare(metadata { i8, i8, i8, i8, i8, i8, i8, i8 }* %x, metadata !35, metadata !23), !dbg !37
  br label %start, !dbg !37

start:                                            ; preds = %entry-block
  %16 = bitcast { i8, i8, i8, i8, i8, i8, i8, i8 }* %arg0 to i8*, !dbg !38
  %17 = bitcast { i8, i8, i8, i8, i8, i8, i8, i8 }* %x to i8*, !dbg !38
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %17, i8* %16, i64 8, i32 1, i1 false), !dbg !38
  ret void, !dbg !39
}

; Function Attrs: uwtable
define internal void @_ZN8rust_out4main17h6d608a6572dc79f7E() unnamed_addr #0 !dbg !40 {
entry-block:
  %_4 = alloca { i8, i8, i8, i8, i8, i8, i8, i8 }
  %_2 = alloca { i8, i8, i8, i8, i8, i8, i8, i8 }
  %_0 = alloca {}
  br label %start

start:                                            ; preds = %entry-block
  %0 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 0, !dbg !43
  store i8 10, i8* %0, !dbg !43
  %1 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 1, !dbg !43
  store i8 11, i8* %1, !dbg !43
  %2 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 2, !dbg !43
  store i8 12, i8* %2, !dbg !43
  %3 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 3, !dbg !43
  store i8 13, i8* %3, !dbg !43
  %4 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 4, !dbg !43
  store i8 14, i8* %4, !dbg !43
  %5 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 5, !dbg !43
  store i8 15, i8* %5, !dbg !43
  %6 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 6, !dbg !43
  store i8 16, i8* %6, !dbg !43
  %7 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2, i32 0, i32 7, !dbg !43
  store i8 17, i8* %7, !dbg !43
  %8 = bitcast { i8, i8, i8, i8, i8, i8, i8, i8 }* %_2 to i64*, !dbg !43
  %9 = load i64, i64* %8, align 1, !dbg !43
  call void @_ZN8rust_out1f17h77fb50884e4b9292E(i64 %9), !dbg !43
  br label %bb1, !dbg !43

bb1:                                              ; preds = %start
  %10 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 0, !dbg !44
  store i8 10, i8* %10, !dbg !44
  %11 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 1, !dbg !44
  store i8 11, i8* %11, !dbg !44
  %12 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 2, !dbg !44
  store i8 12, i8* %12, !dbg !44
  %13 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 3, !dbg !44
  store i8 13, i8* %13, !dbg !44
  %14 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 4, !dbg !44
  store i8 14, i8* %14, !dbg !44
  %15 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 5, !dbg !44
  store i8 15, i8* %15, !dbg !44
  %16 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 6, !dbg !44
  store i8 16, i8* %16, !dbg !44
  %17 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 7, !dbg !44
  store i8 17, i8* %17, !dbg !44
  %18 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 0, !dbg !44
  %19 = load i8, i8* %18, !dbg !44
  %20 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 1, !dbg !44
  %21 = load i8, i8* %20, !dbg !44
  %22 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 2, !dbg !44
  %23 = load i8, i8* %22, !dbg !44
  %24 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 3, !dbg !44
  %25 = load i8, i8* %24, !dbg !44
  %26 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 4, !dbg !44
  %27 = load i8, i8* %26, !dbg !44
  %28 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 5, !dbg !44
  %29 = load i8, i8* %28, !dbg !44
  %30 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 6, !dbg !44
  %31 = load i8, i8* %30, !dbg !44
  %32 = getelementptr inbounds { i8, i8, i8, i8, i8, i8, i8, i8 }, { i8, i8, i8, i8, i8, i8, i8, i8 }* %_4, i32 0, i32 7, !dbg !44
  %33 = load i8, i8* %32, !dbg !44
  call void @_ZN8rust_out1g17h976fe88719539209E(i8 %19, i8 %21, i8 %23, i8 %25, i8 %27, i8 %29, i8 %31, i8 %33), !dbg !44
  br label %bb2, !dbg !44

bb2:                                              ; preds = %bb1
  ret void, !dbg !45
}

; Function Attrs: argmemonly nounwind
declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1

; Function Attrs: nounwind readnone
declare void @llvm.dbg.declare(metadata, metadata, metadata) #2

define i64 @main(i64, i8**) unnamed_addr #3 {
top:
  %2 = load volatile i8, i8* getelementptr inbounds ([34 x i8], [34 x i8]* @__rustc_debug_gdb_scripts_section__, i32 0, i32 0), align 1
  %3 = call i64 @_ZN3std2rt10lang_start17ha5350a26f8f175abE(i8* bitcast (void ()* @_ZN8rust_out4main17h6d608a6572dc79f7E to i8*), i64 %0, i8** %1)
  ret i64 %3
}

declare i64 @_ZN3std2rt10lang_start17ha5350a26f8f175abE(i8*, i64, i8**) unnamed_addr #3

attributes #0 = { uwtable "no-frame-pointer-elim"="true" }
attributes #1 = { argmemonly nounwind }
attributes #2 = { nounwind readnone }
attributes #3 = { "no-frame-pointer-elim"="true" }

!llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2}

!0 = !{i32 1, !"PIE Level", i32 2}
!1 = !{i32 2, !"Debug Info Version", i32 3}
!2 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !3, producer: "rustc version 1.18.0-nightly (ad36c2f55 2017-04-09)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !4)
!3 = !DIFile(filename: "rust_out", directory: "/tmp")
!4 = !{}
!5 = distinct !DISubprogram(name: "f", linkageName: "_ZN8rust_out1fE", scope: !7, file: !6, line: 3, type: !8, isLocal: true, isDefinition: true, scopeLine: 3, flags: DIFlagPrototyped, isOptimized: false, unit: !2, templateParams: !4, variables: !4)
!6 = !DIFile(filename: "<anon>", directory: "/tmp")
!7 = !DINamespace(name: "rust_out", scope: null, file: !6, line: 1)
!8 = !DISubroutineType(types: !9)
!9 = !{null, !10}
!10 = !DICompositeType(tag: DW_TAG_structure_type, name: "(u8, u8, u8, u8, u8, u8, u8, u8)", file: !11, size: 64, align: 8, elements: !12, identifier: "489a633ed2a79c313b5e1834133e88ec27884aa2")
!11 = !DIFile(filename: "<unknown>", directory: "")
!12 = !{!13, !15, !16, !17, !18, !19, !20, !21}
!13 = !DIDerivedType(tag: DW_TAG_member, name: "__0", scope: !10, file: !11, baseType: !14, size: 8, align: 8)
!14 = !DIBasicType(name: "u8", size: 8, align: 8, encoding: DW_ATE_unsigned)
!15 = !DIDerivedType(tag: DW_TAG_member, name: "__1", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 8)
!16 = !DIDerivedType(tag: DW_TAG_member, name: "__2", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 16)
!17 = !DIDerivedType(tag: DW_TAG_member, name: "__3", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 24)
!18 = !DIDerivedType(tag: DW_TAG_member, name: "__4", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 32)
!19 = !DIDerivedType(tag: DW_TAG_member, name: "__5", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 40)
!20 = !DIDerivedType(tag: DW_TAG_member, name: "__6", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 48)
!21 = !DIDerivedType(tag: DW_TAG_member, name: "__7", scope: !10, file: !11, baseType: !14, size: 8, align: 8, offset: 56)
!22 = !DILocalVariable(name: "x", arg: 1, scope: !5, file: !6, line: 1, type: !10)
!23 = !DIExpression()
!24 = !DILocation(line: 1, scope: !5)
!25 = !DILocalVariable(name: "x", scope: !26, file: !6, line: 3, type: !10)
!26 = distinct !DILexicalBlock(scope: !5, file: !6, line: 3, column: 46)
!27 = !DILocation(line: 3, scope: !26)
!28 = !DILocation(line: 3, scope: !5)
!29 = !DILocation(line: 4, scope: !26)
!30 = distinct !DISubprogram(name: "g", linkageName: "_ZN8rust_out1gE", scope: !7, file: !6, line: 5, type: !31, isLocal: false, isDefinition: true, scopeLine: 5, flags: DIFlagPrototyped, isOptimized: false, unit: !2, templateParams: !4, variables: !4)
!31 = !DISubroutineType(types: !32)
!32 = !{null, !14, !14, !14, !14, !14, !14, !14, !14}
!33 = !DILocalVariable(name: "x", arg: 1, scope: !30, file: !6, line: 1, type: !10)
!34 = !DILocation(line: 1, scope: !30)
!35 = !DILocalVariable(name: "x", scope: !36, file: !6, line: 5, type: !10)
!36 = distinct !DILexicalBlock(scope: !30, file: !6, line: 5, column: 65)
!37 = !DILocation(line: 5, scope: !36)
!38 = !DILocation(line: 5, scope: !30)
!39 = !DILocation(line: 6, scope: !36)
!40 = distinct !DISubprogram(name: "main", linkageName: "_ZN8rust_out4mainE", scope: !7, file: !6, line: 8, type: !41, isLocal: true, isDefinition: true, scopeLine: 8, flags: DIFlagPrototyped | DIFlagMainSubprogram, isOptimized: false, unit: !2, templateParams: !4, variables: !4)
!41 = !DISubroutineType(types: !42)
!42 = !{null}
!43 = !DILocation(line: 9, scope: !40)
!44 = !DILocation(line: 10, scope: !40)
!45 = !DILocation(line: 11, scope: !46)
!46 = !DILexicalBlockFile(scope: !40, file: !6, discriminator: 0)

例えば g の呼び出しでは、まずタプルを生成したあとに、そのタプルから再び値を取り出す処理が入っていることがわかる。(これらの無駄は最適化で除去されることが期待される。)