Rustのsize_of_valはどこで実装されているか
概要: std::mem::size_of_val
はRustでもCでも実装できない。どこで実装されているかを調べた。
size_of_val
が特殊な理由
std::mem::size_of_val
は std::mem::size_of
の親戚である。 size_of
はCのsizeofと似たようなものだと言ってよい。 RustはCとは異なり、値によってバイト数が異なるような型 ([T]
, str
, trait objects, dynamically sized structures) があり、これらの型に対して一般化されたsizeofが size_of_val
である。
Rustの標準ライブラリの関数の多くは、通常のRustのコードとして実装されている。そうでなくても、ほとんどはunsafe Rustや、 extern "C"
を用いた外部関数として与えられている。
size_of_val
はRustのプリミティブであるからRust/unsafe Rustでは書けないし、多相だからCの外部関数としては書けない。
size_of_val
のライブラリ定義
std::mem
は core::mem
の別名である。 (std
375行目)
#[stable(feature = "rust1", since = "1.0.0")] pub use core::mem;
core::mem::size_of_val
は core::intrinsics::size_of_val
をsafeに言い換える薄いラッパーである。 (core::mem
217行目)
#[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn size_of_val<T: ?Sized>(val: &T) -> usize { unsafe { intrinsics::size_of_val(val) } }
そして、 core:intrinsics::size_of_val
は extern "rust-intrinsic"
で定義されている。 (core::intrinsics
622行目)
extern "rust-intrinsic" { ... pub fn size_of_val<T: ?Sized>(_: &T) -> usize; ... }
extern "rust-intrinsic"
はどこで処理されているのか
"rust-intrinsic"
という文字列は syntax::abi
90行目 で解釈され、 syntax::abi::Abi::RustIntrinsic
になる。
#[allow(non_upper_case_globals)] const AbiDatas: &'static [AbiData] = &[ ... AbiData {abi: Abi::RustIntrinsic, name: "rust-intrinsic", generic: true }, ... ];
HIRからMIRへの変換で関数呼び出し構文を処理する際に、 rustc_trans::callee::CalleeData::Intrinsic
というマークがつけられる。 rustc_trans::callee
102行目
pub fn def<'a>(ccx: &CrateContext<'a, 'tcx>, def_id: DefId, substs: &'tcx Substs<'tcx>) -> Callee<'tcx> { ... if let ty::TyFnDef(.., f) = fn_ty.sty { if f.abi == Abi::RustIntrinsic || f.abi == Abi::PlatformIntrinsic { return Callee { data: Intrinsic, ty: fn_ty }; } } ... }
Intrinsic
のついた関数呼び出しは rustc_trans::mir::block
の MirContext::trans_block
という関数内で2つに分けて処理されている。
move_val_init
と transmute
は rustc_trans::mir::block
432行目 で処理されている。
残りのintrinsicsは rustc_trans::mir::block
528行目 で処理され、コード生成は rustc_trans::intrinsic::trans_intrinsic_call
に移譲されている。
ここに様々なintrinsic関数のコード生成処理が直書きされているが、今回の目当ての size_of_val
は rustc_trans::intrinsic
161行目 にある。
(_, "size_of_val") => { let tp_ty = substs.type_at(0); if !type_is_sized(tcx, tp_ty) { let (llsize, _) = glue::size_and_align_of_dst(&bcx.build(), tp_ty, llargs[1]); llsize } else { let lltp_ty = type_of::type_of(ccx, tp_ty); C_uint(ccx, machine::llsize_of_alloc(ccx, lltp_ty)) } }
型に Sized
がついている場合は size_of
と同じ処理をしている。つまり、sizeofをコンパイル時に計算し、定数を入れるだけのLLVMコードを生成する。
Sized
でない場合は、 rustc_trans::glue::size_and_align_of_dst
を呼ぶ。この関数はなぜかDrop glue関連コードと一緒になっている。 (Drop glueからも利用されるからだと思われる)
pub fn size_and_align_of_dst<'blk, 'tcx>(bcx: &BlockAndBuilder<'blk, 'tcx>, t: Ty<'tcx>, info: ValueRef) -> (ValueRef, ValueRef) { ... match t.sty { ty::TyAdt(def, substs) => { ... let (unsized_size, unsized_align) = size_and_align_of_dst(bcx, field_ty, info); ... (size, align) } ty::TyDynamic(..) => { ... (bcx.load(size_ptr), bcx.load(align_ptr)) } ty::TySlice(_) | ty::TyStr => { ... (bcx.mul(info, C_uint(bcx.ccx(), unit_size)), C_uint(bcx.ccx(), unit_align)) } _ => bug!("Unexpected unsized type, found {}", t) } }
確かに、Sizedでない型の成り立ちに応じて場合分けをしていることがわかる。
まとめ
size_of_val
など、Rustの型システムに依存する一部のプリミティブはcompiler intrinsicsとして扱われ、その実装はコンパイラ自身が動的に生成している。