Skip to content

Avoid placing immutable lvalues in allocas and zeroing them to cancel cleanup. #11445

Closed
@eddyb

Description

@eddyb

This is the current LLVM IR generated at --opt-level=0 for a seemingly noop transmute:

fn foo(x: ~[u8]) -> ~str {unsafe{transmute(x)}}
define internal { i64, i64, [0 x i8] }* @foo::h5f52f1de17121161af::v0.0({ i64, %tydesc*, i8*, i8*, i8 }*, { i64, i64, [0 x i8] }* noalias) unnamed_addr #4 {
"function top level":
  %__arg = alloca { i64, i64, [0 x i8] }*
  %__self = alloca { i64, i64, [0 x i8] }*
  %2 = alloca { i8*, i32 }
  store { i64, i64, [0 x i8] }* %1, { i64, i64, [0 x i8] }** %__arg
  %3 = load { i64, i64, [0 x i8] }** %__arg
  store { i64, i64, [0 x i8] }* %3, { i64, i64, [0 x i8] }** %__self
  %4 = bitcast { i64, i64, [0 x i8] }** %__arg to i8*
  call void @llvm.memset.p0i8.i64(i8* %4, i8 0, i64 8, i32 8, i1 false)
  %5 = load { i64, i64, [0 x i8] }** %__self
  br label %"normal return"

"normal return":                                  ; preds = %"function top level"
  %6 = bitcast { i64, i64, [0 x i8] }** %__arg to i32**
  call void @"_$UP$u32::glue_drop::hb6a3c7b062d25f8far"({}* null, i32** %6)
  ret { i64, i64, [0 x i8] }* %5

unwind:                                           ; No predecessors!
  %7 = landingpad { i8*, i32 } personality i32 (i32, i32, i64, %"struct.std::rt::unwind::libunwind::_Unwind_Exception[#1]"*, %"enum.std::rt::unwind::libunwind::_Unwind_Context[#1]"*)* @rust_eh_personality
          cleanup
  store { i8*, i32 } %7, { i8*, i32 }* %2
  br label %cleanup

cleanup:                                          ; preds = %unwind
  %8 = bitcast { i64, i64, [0 x i8] }** %__arg to i32**
  call void @"_$UP$u32::glue_drop::hb6a3c7b062d25f8far"({}* null, i32** %8)
  %9 = load { i8*, i32 }* %2
  resume { i8*, i32 } %9
}

Wherever x is moved out of, a memset call is used to zero the pointer, to "cancel the cleanup" (drop glue ignores NULL pointers and objects with a drop flag of 0).

The generated IR could look like this, without affecting semantics:

define internal { i64, i64, [0 x i8] }* @foo::h5f52f1de17121161af::v0.0({ i64, %tydesc*, i8*, i8*, i8 }*, { i64, i64, [0 x i8] }* noalias) unnamed_addr #4 {
"function top level":
  %__self = alloca { i64, i64, [0 x i8] }*
  store { i64, i64, [0 x i8] }* %1 { i64, i64, [0 x i8] }** %__self
  %2 = load { i64, i64, [0 x i8] }** %__self
  br label %"normal return"

"normal return":                                  ; preds = %"function top level"
  ret { i64, i64, [0 x i8] }* %2

(it might be even possible to remove the alloca used for the inlined transmute call)

The reason I wasn't able to implement this behavior in #11252 is the differing scope between the cleanup creation and the cleanup cancellation. It becomes obvious when we have multiple branches:

fn foo<T: Clone>(x: T) {
    if rand() % 1 == 0 {
        x // moves out of x (zeroes x to cancel cleanup)
    } else {
        x.clone() // doesn't move out of x
    }
}

@huonw recalled someone mentioning requiring to move out of x in both branches, but I don't know if that's a viable solution.

cc @nikomatsakis @pcwalton @thestinger

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-codegenArea: Code generation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions