From 40e05413096bbd5f35e566e796f525742076600f Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Thu, 26 Jun 2014 13:46:54 +0200 Subject: [PATCH] debuginfo: Make names of types in debuginfo reliable and omit source locations from debug info type descriptions. So far, type names generated for debuginfo where a bit sketchy. It was not clearly defined when a name should be fully qualified and when not, if region parameters should be shown or not, and other things like that. This commit makes the debuginfo module responsible for creating type names instead of using ppaux::ty_to_str() and brings type names, as they show up in the DWARF information, in line with GCC and Clang: * The name of the type being described is unqualified. It's path is defined by its position in the namespace hierarchy. * Type arguments are always fully qualified, no matter if they would actually be in scope at the type definition location. Care is also taken to reliably make type names consistent across crate boundaries. That is, the code now tries make the type name the same, regardless if the type is in the local crate or reconstructed from metadata. Otherwise LLVM will complain about violating the one-definition-rule when using link-time-optimization. This commit also removes all source location information from type descriptions because these cannot be reconstructed for types instantiated from metadata. Again, with LTO enabled, this can lead to two versions of the debuginfo type description, one with and one without source location information, which then triggers the LLVM ODR assertion. Fortunately, source location information about types is rarely used, so this has little impact. Once source location information is preserved in metadata (#1972) it can also be reenabled for type descriptions. --- src/librustc/middle/trans/debuginfo.rs | 595 +++++++++++++++++-------- src/test/debuginfo/type-names.rs | 333 ++++++++++++++ 2 files changed, 744 insertions(+), 184 deletions(-) create mode 100644 src/test/debuginfo/type-names.rs diff --git a/src/librustc/middle/trans/debuginfo.rs b/src/librustc/middle/trans/debuginfo.rs index 400babb39f8..f4019898003 100644 --- a/src/librustc/middle/trans/debuginfo.rs +++ b/src/librustc/middle/trans/debuginfo.rs @@ -222,6 +222,13 @@ static DW_ATE_signed: c_uint = 0x05; static DW_ATE_unsigned: c_uint = 0x07; static DW_ATE_unsigned_char: c_uint = 0x08; +static UNKNOWN_LINE_NUMBER: c_uint = 0; +static UNKNOWN_COLUMN_NUMBER: c_uint = 0; + +// ptr::null() doesn't work :( +static UNKNOWN_FILE_METADATA: DIFile = (0 as DIFile); +static UNKNOWN_SCOPE_METADATA: DIScope = (0 as DIScope); + //=----------------------------------------------------------------------------- // Public Interface of debuginfo module //=----------------------------------------------------------------------------- @@ -330,15 +337,15 @@ impl TypeMap { unique_type_id.push_char('{'); match ty::get(type_).sty { - ty::ty_nil | - ty::ty_bot | - ty::ty_bool | - ty::ty_char | - ty::ty_str | - ty::ty_int(_) | - ty::ty_uint(_) | + ty::ty_nil | + ty::ty_bot | + ty::ty_bool | + ty::ty_char | + ty::ty_str | + ty::ty_int(_) | + ty::ty_uint(_) | ty::ty_float(_) => { - unique_type_id.push_str(ppaux::ty_to_str(cx.tcx(), type_).as_slice()); + push_debuginfo_type_name(cx, type_, false, &mut unique_type_id); }, ty::ty_enum(def_id, ref substs) => { unique_type_id.push_str("enum "); @@ -587,7 +594,7 @@ impl TypeMap { element_type: ty::t) -> UniqueTypeId { let element_type_id = self.get_unique_type_id_of_type(cx, element_type); - let heap_vec_box_type_id = format!("$$HEAP_VEC_BOX<{}>$$", + let heap_vec_box_type_id = format!("{{HEAP_VEC_BOX<{}>}}", self.get_unique_type_id_as_string(element_type_id) .as_slice()); let interner_key = self.unique_id_interner.intern(Rc::new(heap_vec_box_type_id)); @@ -599,7 +606,7 @@ impl TypeMap { element_type: ty::t) -> UniqueTypeId { let element_type_id = self.get_unique_type_id_of_type(cx, element_type); - let gc_box_type_id = format!("$$GC_BOX<{}>$$", + let gc_box_type_id = format!("{{GC_BOX<{}>}}", self.get_unique_type_id_as_string(element_type_id) .as_slice()); let interner_key = self.unique_id_interner.intern(Rc::new(gc_box_type_id)); @@ -607,6 +614,19 @@ impl TypeMap { } } +// Returns from the enclosing function if the type metadata with the given +// unique id can be found in the type map +macro_rules! return_if_metadata_created_in_meantime( + ($cx: expr, $unique_type_id: expr) => ( + match debug_context($cx).type_map + .borrow() + .find_metadata_for_unique_id($unique_type_id) { + Some(metadata) => return MetadataCreationResult::new(metadata, true), + None => { /* proceed normally */ } + }; + ) +) + /// A context object for maintaining all state needed by the debuginfo module. pub struct CrateDebugContext { @@ -1304,9 +1324,12 @@ pub fn create_function_debug_context(cx: &CrateContext, if has_self_type { let actual_self_type = self_type.unwrap(); // Add self type name to <...> clause of function name - let actual_self_type_name = ppaux::ty_to_str(cx.tcx(), actual_self_type); - name_to_append_suffix_to.push_str( - actual_self_type_name.as_slice()); + let actual_self_type_name = compute_debuginfo_type_name( + cx, + actual_self_type, + true); + + name_to_append_suffix_to.push_str(actual_self_type_name.as_slice()); if generics.is_type_parameterized() { name_to_append_suffix_to.push_str(","); @@ -1343,7 +1366,9 @@ pub fn create_function_debug_context(cx: &CrateContext, for (index, &ast::TyParam{ ident: ident, .. }) in generics.ty_params.iter().enumerate() { let actual_type = *actual_types.get(index); // Add actual type name to <...> clause of function name - let actual_type_name = ppaux::ty_to_str(cx.tcx(), actual_type); + let actual_type_name = compute_debuginfo_type_name(cx, + actual_type, + true); name_to_append_suffix_to.push_str(actual_type_name.as_slice()); if index != generics.ty_params.len() - 1 { @@ -1646,7 +1671,7 @@ fn pointer_type_metadata(cx: &CrateContext, -> DIType { let pointer_llvm_type = type_of::type_of(cx, pointer_type); let (pointer_size, pointer_align) = size_and_align_of(cx, pointer_llvm_type); - let name = ppaux::ty_to_str(cx.tcx(), pointer_type); + let name = compute_debuginfo_type_name(cx, pointer_type, false); let ptr_metadata = name.as_slice().with_c_str(|name| { unsafe { llvm::LLVMDIBuilderCreatePointerType( @@ -1719,7 +1744,6 @@ enum RecursiveTypeDescription { unique_type_id: UniqueTypeId, metadata_stub: DICompositeType, llvm_type: Type, - file_metadata: DIFile, member_description_factory: MemberDescriptionFactory, }, FinalMetadata(DICompositeType) @@ -1731,7 +1755,6 @@ fn create_and_register_recursive_type_forward_declaration( unique_type_id: UniqueTypeId, metadata_stub: DICompositeType, llvm_type: Type, - file_metadata: DIFile, member_description_factory: MemberDescriptionFactory) -> RecursiveTypeDescription { @@ -1745,7 +1768,6 @@ fn create_and_register_recursive_type_forward_declaration( unique_type_id: unique_type_id, metadata_stub: metadata_stub, llvm_type: llvm_type, - file_metadata: file_metadata, member_description_factory: member_description_factory, } } @@ -1761,8 +1783,8 @@ impl RecursiveTypeDescription { unique_type_id, metadata_stub, llvm_type, - file_metadata, - ref member_description_factory + ref member_description_factory, + .. } => { // Make sure that we have a forward declaration of the type in // the TypeMap so that recursive references are possible. This @@ -1788,9 +1810,7 @@ impl RecursiveTypeDescription { set_members_of_composite_type(cx, metadata_stub, llvm_type, - member_descriptions.as_slice(), - file_metadata, - codemap::DUMMY_SP); + member_descriptions.as_slice()); return MetadataCreationResult::new(metadata_stub, true); } } @@ -1845,6 +1865,7 @@ impl StructMemberDescriptionFactory { } } + fn prepare_struct_metadata(cx: &CrateContext, struct_type: ty::t, def_id: ast::DefId, @@ -1852,21 +1873,16 @@ fn prepare_struct_metadata(cx: &CrateContext, unique_type_id: UniqueTypeId, span: Span) -> RecursiveTypeDescription { - let struct_name = ppaux::ty_to_str(cx.tcx(), struct_type); + let struct_name = compute_debuginfo_type_name(cx, struct_type, false); let struct_llvm_type = type_of::type_of(cx, struct_type); - let (containing_scope, definition_span) = get_namespace_and_span_for_item(cx, def_id); - - let file_name = span_start(cx, definition_span).file.name.clone(); - let file_metadata = file_metadata(cx, file_name.as_slice()); + let (containing_scope, _) = get_namespace_and_span_for_item(cx, def_id); let struct_metadata_stub = create_struct_stub(cx, struct_llvm_type, struct_name.as_slice(), unique_type_id, - containing_scope, - file_metadata, - definition_span); + containing_scope); let fields = ty::struct_fields(cx.tcx(), def_id, substs); @@ -1876,7 +1892,6 @@ fn prepare_struct_metadata(cx: &CrateContext, unique_type_id, struct_metadata_stub, struct_llvm_type, - file_metadata, StructMDF(StructMemberDescriptionFactory { fields: fields, is_simd: ty::type_is_simd(cx.tcx(), struct_type), @@ -1916,12 +1931,9 @@ fn prepare_tuple_metadata(cx: &CrateContext, unique_type_id: UniqueTypeId, span: Span) -> RecursiveTypeDescription { - let tuple_name = ppaux::ty_to_str(cx.tcx(), tuple_type); + let tuple_name = compute_debuginfo_type_name(cx, tuple_type, false); let tuple_llvm_type = type_of::type_of(cx, tuple_type); - let loc = span_start(cx, span); - let file_metadata = file_metadata(cx, loc.file.name.as_slice()); - create_and_register_recursive_type_forward_declaration( cx, tuple_type, @@ -1930,11 +1942,8 @@ fn prepare_tuple_metadata(cx: &CrateContext, tuple_llvm_type, tuple_name.as_slice(), unique_type_id, - file_metadata, - file_metadata, - span), + UNKNOWN_SCOPE_METADATA), tuple_llvm_type, - file_metadata, TupleMDF(TupleMemberDescriptionFactory { component_types: Vec::from_slice(component_types), span: span, @@ -1982,7 +1991,6 @@ impl EnumMemberDescriptionFactory { &**self.variants.get(i), discriminant_info, self.containing_scope, - self.file_metadata, self.span); let member_descriptions = member_desc_factory @@ -1991,9 +1999,7 @@ impl EnumMemberDescriptionFactory { set_members_of_composite_type(cx, variant_type_metadata, variant_llvm_type, - member_descriptions.as_slice(), - self.file_metadata, - codemap::DUMMY_SP); + member_descriptions.as_slice()); MemberDescription { name: "".to_string(), llvm_type: variant_llvm_type, @@ -2017,7 +2023,6 @@ impl EnumMemberDescriptionFactory { &**self.variants.get(0), NoDiscriminant, self.containing_scope, - self.file_metadata, self.span); let member_descriptions = @@ -2026,9 +2031,7 @@ impl EnumMemberDescriptionFactory { set_members_of_composite_type(cx, variant_type_metadata, variant_llvm_type, - member_descriptions.as_slice(), - self.file_metadata, - codemap::DUMMY_SP); + member_descriptions.as_slice()); vec![ MemberDescription { name: "".to_string(), @@ -2119,7 +2122,6 @@ impl EnumMemberDescriptionFactory { &**self.variants.get(nndiscr as uint), OptimizedDiscriminant(ptrfield), self.containing_scope, - self.file_metadata, self.span); let variant_member_descriptions = @@ -2128,9 +2130,7 @@ impl EnumMemberDescriptionFactory { set_members_of_composite_type(cx, variant_type_metadata, variant_llvm_type, - variant_member_descriptions.as_slice(), - self.file_metadata, - codemap::DUMMY_SP); + variant_member_descriptions.as_slice()); // Encode the information about the null variant in the union // member's name. @@ -2195,7 +2195,6 @@ fn describe_enum_variant(cx: &CrateContext, variant_info: &ty::VariantInfo, discriminant_info: EnumDiscriminantInfo, containing_scope: DIScope, - file_metadata: DIFile, span: Span) -> (DICompositeType, Type, MemberDescriptionFactory) { let variant_llvm_type = @@ -2207,14 +2206,6 @@ fn describe_enum_variant(cx: &CrateContext, struct_def.packed); // Could do some consistency checks here: size, align, field count, discr type - // Find the source code location of the variant's definition - let variant_definition_span = if variant_info.id.krate == ast::LOCAL_CRATE { - cx.tcx.map.span(variant_info.id.node) - } else { - // For definitions from other crates we have no location information available. - codemap::DUMMY_SP - }; - let variant_name = token::get_ident(variant_info.name); let variant_name = variant_name.get(); let unique_type_id = debug_context(cx).type_map @@ -2228,9 +2219,7 @@ fn describe_enum_variant(cx: &CrateContext, variant_llvm_type, variant_name, unique_type_id, - containing_scope, - file_metadata, - variant_definition_span); + containing_scope); // Get the argument names from the enum variant info let mut arg_names: Vec<_> = match variant_info.arg_names { @@ -2276,7 +2265,7 @@ fn prepare_enum_metadata(cx: &CrateContext, unique_type_id: UniqueTypeId, span: Span) -> RecursiveTypeDescription { - let enum_name = ppaux::ty_to_str(cx.tcx(), enum_type); + let enum_name = compute_debuginfo_type_name(cx, enum_type, false); let (containing_scope, definition_span) = get_namespace_and_span_for_item(cx, enum_def_id); let loc = span_start(cx, definition_span); @@ -2323,8 +2312,8 @@ fn prepare_enum_metadata(cx: &CrateContext, DIB(cx), containing_scope, name, - file_metadata, - loc.line as c_uint, + UNKNOWN_FILE_METADATA, + UNKNOWN_LINE_NUMBER, bytes_to_bits(discriminant_size), bytes_to_bits(discriminant_align), create_DIArray(DIB(cx), enumerators_metadata.as_slice()), @@ -2368,8 +2357,8 @@ fn prepare_enum_metadata(cx: &CrateContext, DIB(cx), containing_scope, enum_name, - file_metadata, - loc.line as c_uint, + UNKNOWN_FILE_METADATA, + UNKNOWN_LINE_NUMBER, bytes_to_bits(enum_type_size), bytes_to_bits(enum_type_align), 0, // Flags @@ -2386,7 +2375,6 @@ fn prepare_enum_metadata(cx: &CrateContext, unique_type_id, enum_metadata, enum_llvm_type, - file_metadata, EnumMDF(EnumMemberDescriptionFactory { enum_type: enum_type, type_rep: type_rep.clone(), @@ -2421,24 +2409,23 @@ fn composite_type_metadata(cx: &CrateContext, composite_type_unique_id: UniqueTypeId, member_descriptions: &[MemberDescription], containing_scope: DIScope, - file_metadata: DIFile, - definition_span: Span) + + // Ignore source location information as long as it + // can't be reconstructed for non-local crates. + _file_metadata: DIFile, + _definition_span: Span) -> DICompositeType { // Create the (empty) struct metadata node ... let composite_type_metadata = create_struct_stub(cx, composite_llvm_type, composite_type_name, composite_type_unique_id, - containing_scope, - file_metadata, - definition_span); + containing_scope); // ... and immediately create and add the member descriptions. set_members_of_composite_type(cx, composite_type_metadata, composite_llvm_type, - member_descriptions, - file_metadata, - definition_span); + member_descriptions); return composite_type_metadata; } @@ -2446,9 +2433,7 @@ fn composite_type_metadata(cx: &CrateContext, fn set_members_of_composite_type(cx: &CrateContext, composite_type_metadata: DICompositeType, composite_llvm_type: Type, - member_descriptions: &[MemberDescription], - file_metadata: DIFile, - definition_span: Span) { + member_descriptions: &[MemberDescription]) { // In some rare cases LLVM metadata uniquing would lead to an existing type // description being used instead of a new one created in create_struct_stub. // This would cause a hard to trace assertion in DICompositeType::SetTypeArray(). @@ -2483,8 +2468,6 @@ fn set_members_of_composite_type(cx: &CrateContext, } } - let loc = span_start(cx, definition_span); - let member_metadata: Vec = member_descriptions .iter() .enumerate() @@ -2501,8 +2484,8 @@ fn set_members_of_composite_type(cx: &CrateContext, DIB(cx), composite_type_metadata, member_name, - file_metadata, - loc.line as c_uint, + UNKNOWN_FILE_METADATA, + UNKNOWN_LINE_NUMBER, bytes_to_bits(member_size), bytes_to_bits(member_align), bytes_to_bits(member_offset), @@ -2526,11 +2509,8 @@ fn create_struct_stub(cx: &CrateContext, struct_llvm_type: Type, struct_type_name: &str, unique_type_id: UniqueTypeId, - containing_scope: DIScope, - file_metadata: DIFile, - definition_span: Span) + containing_scope: DIScope) -> DICompositeType { - let loc = span_start(cx, definition_span); let (struct_size, struct_align) = size_and_align_of(cx, struct_llvm_type); let unique_type_id_str = debug_context(cx).type_map @@ -2548,8 +2528,8 @@ fn create_struct_stub(cx: &CrateContext, DIB(cx), containing_scope, name, - file_metadata, - loc.line as c_uint, + UNKNOWN_FILE_METADATA, + UNKNOWN_LINE_NUMBER, bytes_to_bits(struct_size), bytes_to_bits(struct_align), 0, @@ -2572,12 +2552,9 @@ fn at_box_metadata(cx: &CrateContext, -> MetadataCreationResult { let content_type_metadata = type_metadata(cx, content_type, codemap::DUMMY_SP); - match debug_context(cx).type_map.borrow().find_metadata_for_unique_id(unique_type_id) { - Some(metadata) => return MetadataCreationResult::new(metadata, true), - None => { /* proceed */ } - }; + return_if_metadata_created_in_meantime!(cx, unique_type_id); - let content_type_name = ppaux::ty_to_str(cx.tcx(), content_type); + let content_type_name = compute_debuginfo_type_name(cx, content_type, true); let content_type_name = content_type_name.as_slice(); let content_llvm_type = type_of::type_of(cx, content_type); @@ -2593,7 +2570,6 @@ fn at_box_metadata(cx: &CrateContext, let nil_pointer_type_metadata = type_metadata(cx, nil_pointer_type, codemap::DUMMY_SP); - let member_descriptions = [ MemberDescription { name: "refcnt".to_string(), @@ -2627,9 +2603,6 @@ fn at_box_metadata(cx: &CrateContext, } ]; - let loc = span_start(cx, codemap::DUMMY_SP); - let file_metadata = file_metadata(cx, loc.file.name.as_slice()); - let gc_box_unique_id = debug_context(cx).type_map .borrow_mut() .get_unique_type_id_of_gc_box(cx, content_type); @@ -2640,8 +2613,8 @@ fn at_box_metadata(cx: &CrateContext, box_type_name.as_slice(), gc_box_unique_id, member_descriptions, - file_metadata, - file_metadata, + UNKNOWN_SCOPE_METADATA, + UNKNOWN_FILE_METADATA, codemap::DUMMY_SP); let gc_pointer_metadata = pointer_type_metadata(cx, @@ -2674,10 +2647,7 @@ fn fixed_vec_metadata(cx: &CrateContext, -> MetadataCreationResult { let element_type_metadata = type_metadata(cx, element_type, span); - match debug_context(cx).type_map.borrow().find_metadata_for_unique_id(unique_type_id) { - Some(metadata) => return MetadataCreationResult::new(metadata, true), - None => { /* proceed */ } - }; + return_if_metadata_created_in_meantime!(cx, unique_type_id); let element_llvm_type = type_of::type_of(cx, element_type); let (element_type_size, element_type_align) = size_and_align_of(cx, element_llvm_type); @@ -2712,13 +2682,12 @@ fn heap_vec_metadata(cx: &CrateContext, let element_llvm_type = type_of::type_of(cx, element_type); let (element_size, element_align) = size_and_align_of(cx, element_llvm_type); - match debug_context(cx).type_map.borrow().find_metadata_for_unique_id(unique_type_id) { - Some(metadata) => return MetadataCreationResult::new(metadata, true), - None => { /* proceed */ } - }; + return_if_metadata_created_in_meantime!(cx, unique_type_id); let vecbox_llvm_type = Type::vec(cx, &element_llvm_type); - let vec_pointer_type_name = ppaux::ty_to_str(cx.tcx(), vec_pointer_type); + let vec_pointer_type_name = compute_debuginfo_type_name(cx, + vec_pointer_type, + true); let vec_pointer_type_name = vec_pointer_type_name.as_slice(); let member_llvm_types = vecbox_llvm_type.field_types(); @@ -2769,7 +2738,7 @@ fn heap_vec_metadata(cx: &CrateContext, vec_pointer_type_name, vec_box_unique_id, member_descriptions, - file_metadata, + UNKNOWN_SCOPE_METADATA, file_metadata, span); @@ -2791,13 +2760,10 @@ fn vec_slice_metadata(cx: &CrateContext, let element_type_metadata = type_metadata(cx, data_ptr_type, span); - match debug_context(cx).type_map.borrow().find_metadata_for_unique_id(unique_type_id) { - Some(metadata) => return MetadataCreationResult::new(metadata, true), - None => { /* proceed */ } - }; + return_if_metadata_created_in_meantime!(cx, unique_type_id); let slice_llvm_type = type_of::type_of(cx, vec_type); - let slice_type_name = ppaux::ty_to_str(cx.tcx(), vec_type); + let slice_type_name = compute_debuginfo_type_name(cx, vec_type, true); let member_llvm_types = slice_llvm_type.field_types(); assert!(slice_layout_is_correct(cx, @@ -2828,7 +2794,7 @@ fn vec_slice_metadata(cx: &CrateContext, slice_type_name.as_slice(), unique_type_id, member_descriptions, - file_metadata, + UNKNOWN_SCOPE_METADATA, file_metadata, span); return MetadataCreationResult::new(metadata, false); @@ -2848,8 +2814,6 @@ fn subroutine_type_metadata(cx: &CrateContext, signature: &ty::FnSig, span: Span) -> MetadataCreationResult { - let loc = span_start(cx, span); - let file_metadata = file_metadata(cx, loc.file.name.as_slice()); let mut signature_metadata: Vec = Vec::with_capacity(signature.inputs.len() + 1); // return type @@ -2863,57 +2827,64 @@ fn subroutine_type_metadata(cx: &CrateContext, signature_metadata.push(type_metadata(cx, argument_type, span)); } - match debug_context(cx).type_map.borrow().find_metadata_for_unique_id(unique_type_id) { - Some(metadata) => return MetadataCreationResult::new(metadata, true), - None => { /* proceed */ } - }; + return_if_metadata_created_in_meantime!(cx, unique_type_id); return MetadataCreationResult::new( unsafe { llvm::LLVMDIBuilderCreateSubroutineType( DIB(cx), - file_metadata, + UNKNOWN_FILE_METADATA, create_DIArray(DIB(cx), signature_metadata.as_slice())) }, false); } -fn trait_metadata(cx: &CrateContext, - def_id: ast::DefId, - trait_type: ty::t, - substs: &subst::Substs, - trait_store: ty::TraitStore, - _: &ty::BuiltinBounds, - unique_type_id: UniqueTypeId) - -> DIType { +fn trait_pointer_metadata(cx: &CrateContext, + // trait_pointer_type must be the type of the fat + // pointer to the concrete trait object + trait_pointer_type: ty::t, + unique_type_id: UniqueTypeId) + -> DIType { // The implementation provided here is a stub. It makes sure that the trait // type is assigned the correct name, size, namespace, and source location. // But it does not describe the trait's methods. - let last = ty::with_path(cx.tcx(), def_id, |mut path| path.last().unwrap()); - let ident_string = token::get_name(last.name()); - let mut name = ppaux::trait_store_to_str(cx.tcx(), trait_store); - name.push_str(ident_string.get()); - // Add type and region parameters - let trait_def = ty::lookup_trait_def(cx.tcx(), def_id); - let name = ppaux::parameterized(cx.tcx(), name.as_slice(), - substs, &trait_def.generics); + let trait_object_type = match ty::get(trait_pointer_type).sty { + ty::ty_uniq(pointee_type) => pointee_type, + ty::ty_rptr(_, ty::mt { ty, .. }) => ty, + _ => { + let pp_type_name = ppaux::ty_to_str(cx.tcx(), trait_pointer_type); + cx.sess().bug(format!("debuginfo: Unexpected trait-pointer type in \ + trait_pointer_metadata(): {}", + pp_type_name.as_slice()).as_slice()); + } + }; - let (containing_scope, definition_span) = get_namespace_and_span_for_item(cx, def_id); + let def_id = match ty::get(trait_object_type).sty { + ty::ty_trait(box ty::TyTrait { def_id, .. }) => def_id, + _ => { + let pp_type_name = ppaux::ty_to_str(cx.tcx(), trait_object_type); + cx.sess().bug(format!("debuginfo: Unexpected trait-object type in \ + trait_pointer_metadata(): {}", + pp_type_name.as_slice()).as_slice()); + } + }; - let file_name = span_start(cx, definition_span).file.name.clone(); - let file_metadata = file_metadata(cx, file_name.as_slice()); + let trait_pointer_type_name = + compute_debuginfo_type_name(cx, trait_pointer_type, false); - let trait_llvm_type = type_of::type_of(cx, trait_type); + let (containing_scope, _) = get_namespace_and_span_for_item(cx, def_id); + + let trait_pointer_llvm_type = type_of::type_of(cx, trait_pointer_type); composite_type_metadata(cx, - trait_llvm_type, - name.as_slice(), + trait_pointer_llvm_type, + trait_pointer_type_name.as_slice(), unique_type_id, [], containing_scope, - file_metadata, - definition_span) + UNKNOWN_FILE_METADATA, + codemap::DUMMY_SP) } fn type_metadata(cx: &CrateContext, @@ -2955,15 +2926,6 @@ fn type_metadata(cx: &CrateContext, debug!("type_metadata: {:?}", ty::get(t)); - macro_rules! return_if_created_in_meantime( - () => ( - match debug_context(cx).type_map.borrow().find_metadata_for_unique_id(unique_type_id) { - Some(metadata) => return metadata, - None => { /* proceed normally */ } - }; - ) - ) - let sty = &ty::get(t).sty; let MetadataCreationResult { metadata, already_stored_in_typemap } = match *sty { ty::ty_nil | @@ -2993,19 +2955,22 @@ fn type_metadata(cx: &CrateContext, let i8_t = ty::mk_i8(); heap_vec_metadata(cx, pointee_type, i8_t, unique_type_id, usage_site_span) } - ty::ty_trait(box ty::TyTrait { - def_id, - ref substs, - ref bounds - }) => { + ty::ty_trait(..) => { MetadataCreationResult::new( - trait_metadata(cx, def_id, t, substs, ty::UniqTraitStore, - bounds, unique_type_id), - false) + trait_pointer_metadata(cx, t, unique_type_id), + false) } _ => { - let pointee_metadata = type_metadata(cx, pointee_type, usage_site_span); - return_if_created_in_meantime!(); + let pointee_metadata = type_metadata(cx, + pointee_type, + usage_site_span); + match debug_context(cx).type_map + .borrow() + .find_metadata_for_unique_id(unique_type_id) { + Some(metadata) => return metadata, + None => { /* proceed normally */ } + }; + MetadataCreationResult::new(pointer_type_metadata(cx, t, pointee_metadata), false) } @@ -3017,22 +2982,23 @@ fn type_metadata(cx: &CrateContext, vec_slice_metadata(cx, t, mt.ty, unique_type_id, usage_site_span) } ty::ty_str => { - vec_slice_metadata(cx, t, ty::mk_i8(), unique_type_id, usage_site_span) + vec_slice_metadata(cx, t, ty::mk_u8(), unique_type_id, usage_site_span) } - ty::ty_trait(box ty::TyTrait { - def_id, - ref substs, - ref bounds - }) => { + ty::ty_trait(..) => { MetadataCreationResult::new( - trait_metadata(cx, def_id, t, substs, - ty::RegionTraitStore(ty::ReStatic, mt.mutbl), - bounds, unique_type_id), - false) + trait_pointer_metadata(cx, t, unique_type_id), + false) } _ => { let pointee = type_metadata(cx, mt.ty, usage_site_span); - return_if_created_in_meantime!(); + + match debug_context(cx).type_map + .borrow() + .find_metadata_for_unique_id(unique_type_id) { + Some(metadata) => return metadata, + None => { /* proceed normally */ } + }; + MetadataCreationResult::new(pointer_type_metadata(cx, t, pointee), false) } } @@ -3150,7 +3116,8 @@ fn set_debug_location(cx: &CrateContext, debug_location: DebugLocation) { match debug_location { KnownLocation { scope, line, .. } => { - let col = 0u; // Always set the column to zero like Clang and GCC + // Always set the column to zero like Clang and GCC + let col = UNKNOWN_COLUMN_NUMBER; debug!("setting debug location to {} {}", line, col); let elements = [C_i32(cx, line as i32), C_i32(cx, col as i32), scope, ptr::mut_null()]; @@ -3700,6 +3667,265 @@ fn populate_scope_map(cx: &CrateContext, } +//=----------------------------------------------------------------------------- +// Type Names for Debug Info +//=----------------------------------------------------------------------------- + +// Compute the name of the type as it should be stored in debuginfo. Does not do +// any caching, i.e. calling the function twice with the same type will also do +// the work twice. The `qualified` parameter only affects the first level of the +// type name, further levels (i.e. type parameters) are always fully qualified. +fn compute_debuginfo_type_name(cx: &CrateContext, + t: ty::t, + qualified: bool) + -> String { + let mut result = String::with_capacity(64); + push_debuginfo_type_name(cx, t, qualified, &mut result); + result +} + +// Pushes the name of the type as it should be stored in debuginfo on the +// `output` String. See also compute_debuginfo_type_name(). +fn push_debuginfo_type_name(cx: &CrateContext, + t: ty::t, + qualified: bool, + output:&mut String) { + match ty::get(t).sty { + ty::ty_nil => output.push_str("()"), + ty::ty_bot => output.push_str("!"), + ty::ty_bool => output.push_str("bool"), + ty::ty_char => output.push_str("char"), + ty::ty_str => output.push_str("str"), + ty::ty_int(ast::TyI) => output.push_str("int"), + ty::ty_int(ast::TyI8) => output.push_str("i8"), + ty::ty_int(ast::TyI16) => output.push_str("i16"), + ty::ty_int(ast::TyI32) => output.push_str("i32"), + ty::ty_int(ast::TyI64) => output.push_str("i64"), + ty::ty_uint(ast::TyU) => output.push_str("uint"), + ty::ty_uint(ast::TyU8) => output.push_str("u8"), + ty::ty_uint(ast::TyU16) => output.push_str("u16"), + ty::ty_uint(ast::TyU32) => output.push_str("u32"), + ty::ty_uint(ast::TyU64) => output.push_str("u64"), + ty::ty_float(ast::TyF32) => output.push_str("f32"), + ty::ty_float(ast::TyF64) => output.push_str("f64"), + ty::ty_struct(def_id, ref substs) | + ty::ty_enum(def_id, ref substs) => { + push_item_name(cx, def_id, qualified, output); + push_type_params(cx, substs, output); + }, + ty::ty_tup(ref component_types) => { + output.push_char('('); + for &component_type in component_types.iter() { + push_debuginfo_type_name(cx, component_type, true, output); + output.push_str(", "); + } + output.pop_char(); + output.pop_char(); + output.push_char(')'); + }, + ty::ty_uniq(inner_type) => { + output.push_str("Box<"); + push_debuginfo_type_name(cx, inner_type, true, output); + output.push_char('>'); + }, + ty::ty_box(inner_type) => { + output.push_char('@'); + push_debuginfo_type_name(cx, inner_type, true, output); + }, + ty::ty_ptr(ty::mt { ty: inner_type, mutbl } ) => { + output.push_char('*'); + match mutbl { + ast::MutImmutable => output.push_str("const "), + ast::MutMutable => output.push_str("mut "), + } + + push_debuginfo_type_name(cx, inner_type, true, output); + }, + ty::ty_rptr(_, ty::mt { ty: inner_type, mutbl }) => { + output.push_char('&'); + if mutbl == ast::MutMutable { + output.push_str("mut "); + } + + push_debuginfo_type_name(cx, inner_type, true, output); + }, + ty::ty_vec(ty::mt { ty: inner_type, .. }, optional_length) => { + output.push_char('['); + push_debuginfo_type_name(cx, inner_type, true, output); + + match optional_length { + Some(len) => { + output.push_str(format!(", ..{}", len).as_slice()); + } + None => { /* nothing to do */ } + }; + + output.push_char(']'); + }, + ty::ty_trait(ref trait_data) => { + push_item_name(cx, trait_data.def_id, false, output); + push_type_params(cx, &trait_data.substs, output); + }, + ty::ty_bare_fn(ty::BareFnTy{ fn_style, abi, ref sig } ) => { + if fn_style == ast::UnsafeFn { + output.push_str("unsafe "); + } + + if abi != ::syntax::abi::Rust { + output.push_str("extern \""); + output.push_str(abi.name()); + output.push_str("\" "); + } + + output.push_str("fn("); + + if sig.inputs.len() > 0 { + for ¶meter_type in sig.inputs.iter() { + push_debuginfo_type_name(cx, parameter_type, true, output); + output.push_str(", "); + } + output.pop_char(); + output.pop_char(); + } + + if sig.variadic { + if sig.inputs.len() > 0 { + output.push_str(", ..."); + } else { + output.push_str("..."); + } + } + + output.push_char(')'); + + if !ty::type_is_nil(sig.output) { + output.push_str(" -> "); + push_debuginfo_type_name(cx, sig.output, true, output); + } + }, + ty::ty_closure(box ty::ClosureTy { fn_style, + onceness, + store, + ref sig, + .. // omitting bounds ... + }) => { + if fn_style == ast::UnsafeFn { + output.push_str("unsafe "); + } + + if onceness == ast::Once { + output.push_str("once "); + } + + let param_list_closing_char; + match store { + ty::UniqTraitStore => { + output.push_str("proc("); + param_list_closing_char = ')'; + } + ty::RegionTraitStore(_, ast::MutMutable) => { + output.push_str("&mut|"); + param_list_closing_char = '|'; + } + ty::RegionTraitStore(_, ast::MutImmutable) => { + output.push_str("&|"); + param_list_closing_char = '|'; + } + }; + + if sig.inputs.len() > 0 { + for ¶meter_type in sig.inputs.iter() { + push_debuginfo_type_name(cx, parameter_type, true, output); + output.push_str(", "); + } + output.pop_char(); + output.pop_char(); + } + + if sig.variadic { + if sig.inputs.len() > 0 { + output.push_str(", ..."); + } else { + output.push_str("..."); + } + } + + output.push_char(param_list_closing_char); + + if !ty::type_is_nil(sig.output) { + output.push_str(" -> "); + push_debuginfo_type_name(cx, sig.output, true, output); + } + }, + ty::ty_err | + ty::ty_infer(_) | + ty::ty_param(_) => { + cx.sess().bug(format!("debuginfo: Trying to create type name for \ + unexpected type: {}", ppaux::ty_to_str(cx.tcx(), t)).as_slice()); + } + } + + fn push_item_name(cx: &CrateContext, + def_id: ast::DefId, + qualified: bool, + output: &mut String) { + ty::with_path(cx.tcx(), def_id, |mut path| { + if qualified { + if def_id.krate == ast::LOCAL_CRATE { + output.push_str(crate_root_namespace(cx)); + output.push_str("::"); + } + + let mut path_element_count = 0u; + for path_element in path { + let name = token::get_name(path_element.name()); + output.push_str(name.get()); + output.push_str("::"); + path_element_count += 1; + } + + if path_element_count == 0 { + cx.sess().bug("debuginfo: Encountered empty item path!"); + } + + output.pop_char(); + output.pop_char(); + } else { + let name = token::get_name(path.last() + .expect("debuginfo: Empty item path?") + .name()); + output.push_str(name.get()); + } + }); + } + + // Pushes the type parameters in the given `Substs` to the output string. + // This ignores region parameters, since they can't reliably be + // reconstructed for items from non-local crates. For local crates, this + // would be possible but with inlining and LTO we have to use the least + // common denominator - otherwise we would run into conflicts. + fn push_type_params(cx: &CrateContext, + substs: &subst::Substs, + output: &mut String) { + if substs.types.is_empty() { + return; + } + + output.push_char('<'); + + for &type_parameter in substs.types.iter() { + push_debuginfo_type_name(cx, type_parameter, true, output); + output.push_str(", "); + } + + output.pop_char(); + output.pop_char(); + + output.push_char('>'); + } +} + + //=----------------------------------------------------------------------------- // Namespace Handling //=----------------------------------------------------------------------------- @@ -3731,14 +3957,15 @@ impl NamespaceTreeNode { } } +fn crate_root_namespace<'a>(cx: &'a CrateContext) -> &'a str { + cx.link_meta.crateid.name.as_slice() +} + fn namespace_for_item(cx: &CrateContext, def_id: ast::DefId) -> Rc { ty::with_path(cx.tcx(), def_id, |path| { // prepend crate name if not already present let krate = if def_id.krate == ast::LOCAL_CRATE { - let crate_namespace_ident = token::str_to_ident(cx.link_meta - .crateid - .name - .as_slice()); + let crate_namespace_ident = token::str_to_ident(crate_root_namespace(cx)); Some(ast_map::PathMod(crate_namespace_ident.name)) } else { None diff --git a/src/test/debuginfo/type-names.rs b/src/test/debuginfo/type-names.rs new file mode 100644 index 00000000000..ac5fe830275 --- /dev/null +++ b/src/test/debuginfo/type-names.rs @@ -0,0 +1,333 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-tidy-linelength +// ignore-lldb +// ignore-android: FIXME(#10381) + +// compile-flags:-g +// gdb-command:rbreak zzz +// gdb-command:run +// gdb-command:finish + + +// STRUCTS +// gdb-command:whatis simple_struct +// gdb-check:type = struct Struct1 + +// gdb-command:whatis generic_struct1 +// gdb-check:type = struct GenericStruct + +// gdb-command:whatis generic_struct2 +// gdb-check:type = struct GenericStruct uint> + +// gdb-command:whatis mod_struct +// gdb-check:type = struct Struct2 + + +// ENUMS +// gdb-command:whatis simple_enum_1 +// gdb-check:type = union Enum1 + +// gdb-command:whatis simple_enum_2 +// gdb-check:type = union Enum1 + +// gdb-command:whatis simple_enum_3 +// gdb-check:type = union Enum2 + +// gdb-command:whatis generic_enum_1 +// gdb-check:type = union Enum3 + +// gdb-command:whatis generic_enum_2 +// gdb-check:type = union Enum3 + + +// TUPLES +// gdb-command:whatis tuple1 +// gdb-check:type = struct (u32, type-names::Struct1, type-names::Mod1::Mod2::Enum3) + +// gdb-command:whatis tuple2 +// gdb-check:type = struct ((type-names::Struct1, type-names::Mod1::Mod2::Struct3), type-names::Mod1::Enum2, char) + + +// BOX +// gdb-command:whatis box1 +// gdb-check:type = struct (Box, i32) + +// gdb-command:whatis box2 +// gdb-check:type = struct (Box>, i32) + + +// REFERENCES +// gdb-command:whatis ref1 +// gdb-check:type = struct (&type-names::Struct1, i32) + +// gdb-command:whatis ref2 +// gdb-check:type = struct (&type-names::GenericStruct, i32) + +// gdb-command:whatis mut_ref1 +// gdb-check:type = struct (&mut type-names::Struct1, i32) + +// gdb-command:whatis mut_ref2 +// gdb-check:type = struct (&mut type-names::GenericStruct, i32) + + +// RAW POINTERS +// gdb-command:whatis mut_ptr1 +// gdb-check:type = struct (*mut type-names::Struct1, int) + +// gdb-command:whatis mut_ptr2 +// gdb-check:type = struct (*mut int, int) + +// gdb-command:whatis mut_ptr3 +// gdb-check:type = struct (*mut type-names::Mod1::Mod2::Enum3, int) + +// gdb-command:whatis const_ptr1 +// gdb-check:type = struct (*const type-names::Struct1, int) + +// gdb-command:whatis const_ptr2 +// gdb-check:type = struct (*const int, int) + +// gdb-command:whatis const_ptr3 +// gdb-check:type = struct (*const type-names::Mod1::Mod2::Enum3, int) + + +// VECTORS +// gdb-command:whatis fixed_size_vec1 +// gdb-check:type = struct ([type-names::Struct1, ..3], i16) + +// gdb-command:whatis fixed_size_vec2 +// gdb-check:type = struct ([uint, ..3], i16) + +// gdb-command:whatis slice1 +// gdb-check:type = struct &[uint] + +// gdb-command:whatis slice2 +// gdb-check:type = struct &[type-names::Mod1::Enum2] + + +// TRAITS +// gdb-command:whatis box_trait +// gdb-check:type = struct Box + +// gdb-command:whatis ref_trait +// gdb-check:type = struct &Trait1 + +// gdb-command:whatis mut_ref_trait +// gdb-check:type = struct &mut Trait1 + +// gdb-command:whatis generic_box_trait +// gdb-check:type = struct Box> + +// gdb-command:whatis generic_ref_trait +// gdb-check:type = struct &Trait2 + +// gdb-command:whatis generic_mut_ref_trait +// gdb-check:type = struct &mut Trait2> + + +// BARE FUNCTIONS +// gdb-command:whatis rust_fn +// gdb-check:type = struct (fn(core::option::Option, core::option::Option<&type-names::Mod1::Struct2>), uint) + +// gdb-command:whatis extern_c_fn +// gdb-check:type = struct (extern "C" fn(int), uint) + +// gdb-command:whatis unsafe_fn +// gdb-check:type = struct (unsafe fn(core::result::Result), uint) + +// gdb-command:whatis extern_stdcall_fn +// gdb-check:type = struct (extern "stdcall" fn(), uint) + +// gdb-command:whatis rust_fn_with_return_value +// gdb-check:type = struct (fn(f64) -> uint, uint) + +// gdb-command:whatis extern_c_fn_with_return_value +// gdb-check:type = struct (extern "C" fn() -> type-names::Struct1, uint) + +// gdb-command:whatis unsafe_fn_with_return_value +// gdb-check:type = struct (unsafe fn(type-names::GenericStruct) -> type-names::Mod1::Struct2, uint) + +// gdb-command:whatis extern_stdcall_fn_with_return_value +// gdb-check:type = struct (extern "stdcall" fn(Box) -> uint, uint) + +// gdb-command:whatis generic_function_int +// gdb-check:type = struct (fn(int) -> int, uint) + +// gdb-command:whatis generic_function_struct3 +// gdb-check:type = struct (fn(type-names::Mod1::Mod2::Struct3) -> type-names::Mod1::Mod2::Struct3, uint) + +// gdb-command:whatis variadic_function +// gdb-check:type = struct (unsafe extern "C" fn(*const u8, ...) -> int, uint) + + +// CLOSURES +// gdb-command:whatis some_proc +// gdb-check:type = struct (once proc(int, u8) -> (int, u8), uint) + +// gdb-command:whatis stack_closure1 +// gdb-check:type = struct (&mut|int|, uint) + +// gdb-command:whatis stack_closure2 +// gdb-check:type = struct (&mut|i8, f32| -> f32, uint) + +use std::ptr; + +struct Struct1; +struct GenericStruct; + +enum Enum1 { + Variant1_1, + Variant1_2(int) +} + +mod Mod1 { + pub struct Struct2; + + pub enum Enum2 { + Variant2_1, + Variant2_2(super::Struct1) + } + + pub mod Mod2 { + pub struct Struct3; + + pub enum Enum3 { + Variant3_1, + Variant3_2(T), + } + } +} + +trait Trait1 { } +trait Trait2 { } + +impl Trait1 for int {} +impl Trait2 for int {} + +fn rust_fn(_: Option, _: Option<&Mod1::Struct2>) {} +extern "C" fn extern_c_fn(_: int) {} +unsafe fn unsafe_fn(_: Result) {} +extern "stdcall" fn extern_stdcall_fn() {} + +fn rust_fn_with_return_value(_: f64) -> uint { 4 } +extern "C" fn extern_c_fn_with_return_value() -> Struct1 { Struct1 } +unsafe fn unsafe_fn_with_return_value(_: GenericStruct) -> Mod1::Struct2 { Mod1::Struct2 } +extern "stdcall" fn extern_stdcall_fn_with_return_value(_: Box) -> uint { 0 } + +fn generic_function(x: T) -> T { x } + +extern { + fn printf(_:*const u8, ...) -> int; +} + +// In many of the cases below, the type that is actually under test is wrapped +// in a tuple, e.g. Box, references, raw pointers, fixed-size vectors, ... +// This is because GDB will not print the type name from DWARF debuginfo for +// some kinds of types (pointers, arrays, functions, ...) +// Since tuples are structs as far as GDB is concerned, their name will be +// printed correctly, so the tests below just construct a tuple type that will +// then *contain* the type name that we want to see. +fn main() { + + // Structs + let simple_struct = Struct1; + let generic_struct1: GenericStruct = GenericStruct; + let generic_struct2: GenericStruct uint> = GenericStruct; + let mod_struct = Mod1::Struct2; + + // Enums + let simple_enum_1 = Variant1_1; + let simple_enum_2 = Variant1_2(0); + let simple_enum_3 = Mod1::Variant2_2(Struct1); + + let generic_enum_1: Mod1::Mod2::Enum3 = Mod1::Mod2::Variant3_1; + let generic_enum_2 = Mod1::Mod2::Variant3_2(Struct1); + + // Tuples + let tuple1 = (8u32, Struct1, Mod1::Mod2::Variant3_2(Mod1::Struct2)); + let tuple2 = ((Struct1, Mod1::Mod2::Struct3), Mod1::Variant2_1, 'x'); + + // Box + let box1 = (box 1f32, 0i32); + let box2 = (box Mod1::Mod2::Variant3_2(1f32), 0i32); + + // References + let ref1 = (&Struct1, 0i32); + let ref2 = (&GenericStruct::, 0i32); + + let mut mut_struct1 = Struct1; + let mut mut_generic_struct = GenericStruct::; + let mut_ref1 = (&mut mut_struct1, 0i32); + let mut_ref2 = (&mut mut_generic_struct, 0i32); + + // Raw Pointers + let mut_ptr1: (*mut Struct1, int) = (ptr::mut_null(), 0); + let mut_ptr2: (*mut int, int) = (ptr::mut_null(), 0); + let mut_ptr3: (*mut Mod1::Mod2::Enum3, int) = (ptr::mut_null(), 0); + + let const_ptr1: (*const Struct1, int) = (ptr::null(), 0); + let const_ptr2: (*const int, int) = (ptr::null(), 0); + let const_ptr3: (*const Mod1::Mod2::Enum3, int) = (ptr::null(), 0); + + // Vectors + let fixed_size_vec1 = ([Struct1, Struct1, Struct1], 0i16); + let fixed_size_vec2 = ([0u, 1u, 2u], 0i16); + + let vec1 = vec![0u, 2u, 3u]; + let slice1 = vec1.as_slice(); + let vec2 = vec![Mod1::Variant2_2(Struct1)]; + let slice2 = vec2.as_slice(); + + // Trait Objects + let box_trait = (box 0i) as Box; + let ref_trait = &0i as &Trait1; + let mut mut_int1 = 0i; + let mut_ref_trait = (&mut mut_int1) as &mut Trait1; + + let generic_box_trait = (box 0i) as Box>; + let generic_ref_trait = (&0i) as &Trait2; + + let mut generic_mut_ref_trait_impl = 0i; + let generic_mut_ref_trait = (&mut generic_mut_ref_trait_impl) as + &mut Trait2>; + + // Bare Functions + let rust_fn = (rust_fn, 0u); + let extern_c_fn = (extern_c_fn, 0u); + let unsafe_fn = (unsafe_fn, 0u); + let extern_stdcall_fn = (extern_stdcall_fn, 0u); + + let rust_fn_with_return_value = (rust_fn_with_return_value, 0u); + let extern_c_fn_with_return_value = (extern_c_fn_with_return_value, 0u); + let unsafe_fn_with_return_value = (unsafe_fn_with_return_value, 0u); + let extern_stdcall_fn_with_return_value = (extern_stdcall_fn_with_return_value, 0u); + + let generic_function_int = (generic_function::, 0u); + let generic_function_struct3 = (generic_function::, 0u); + + let variadic_function = (printf, 0u); + + // Closures + // I (mw) am a bit unclear about the current state of closures, their + // various forms (boxed, unboxed, proc, capture-by-ref, by-val, once) and + // how that maps to rustc's internal representation of these forms. + // Once closures have reached their 1.0 form, the tests below should + // probably be expanded. + let some_proc = (proc(a:int, b:u8) (a, b), 0u); + + let stack_closure1 = (|x:int| {}, 0u); + let stack_closure2 = (|x:i8, y: f32| { (x as f32) + y }, 0u); + + zzz(); +} + +#[inline(never)] +fn zzz() { () }