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