Change:
Running the following through Godbolt, using rustc 1.71.0-nightly ( 2023-05-10) with : I expected rustc to create an 8 KB stack allocation and a memcpy from it to the heap allocation, instead it creates 2 identical stack allocations and 2 memcpys to go along with them: Trying to narrow the problem down further, when eliminating the stack allocations entirely: The code duplication still persists, and is limited to the call:
label +A-codegen +I-slow
Godbolt:
It seems this is not unique to , happens using as well. It may be useful to note that without the binding, rustc emits a single :
This applies to a lot of things that takes an array as input. For example, Box and Vec I have a potential fix in TLDR, marking a tail call makes Alias Analysis smarter, a memcpy can be optimized, and a dead alloca can be removed. I'll elaborate below. , explaining why there are two allocas. rustc generated code basically memcpy's from alloca1 to alloca2, and then another memcpy from alloca2 to the Arc/Vec/Box. fails to mark rustalloc as a tail call. The tail mark means that the callee will not access the callers local memory. The call cannot be marked because the function iterates over GEPs, and the loop latch condition uses the GEP in a icmp, escaping the alloca. The patch I submitted makes sure icmps are not escaping. tries to optimize memcpys, but . BasicAA can determine a callee . rustalloc does not have the tail mark from the previous step, and BasicAA deduces that rustalloc may read/write caller allocas. Interesting is that if you take the optimized code that posted and run it through , the extra alloca is removed In the -O3 pipeline, LoopVectorize changes the loop from iterating over GEPS to indicies, removing the icmp against a GEP. TailCallElim is called after, marking rustalloc as tail, but MemCpyOptimizer is not run again.
The duplicate alloca could also have been removed if rustalloc was set to only access inaccessible memory. It is set for libc malloc . If its correct, maybe rustc can set it for rustalloc (Big emphasis on if its correct. I don't know about the internals).
I also want to note that that emits code for the arrays. It intentionally iterates from the start to end pointer. It would probably not be hard to change the code to iterate over array indices instead, which would help LLVM optimizations, but I'm not sure if that is a desirable change.
Huh, I was under the impression that iterating over GEPs () and breaking on pointer equality is the preferred way to do these kinds of loops. We're doing that in several places.
I am not 100% which is better, maybe there are drawbacks to iterating over indices that I am not aware of. Could you share some examples of where else there might be GEP iterating? I have a change in which should make LLVM smarter about comparisons against GEP inductions, but I'm questioning whether we should merge it. Examples could help next steps.
It's used for and .
This lead me to experiment with various loop shapes for slice::Iter::fold in and an index-based do-while loop + a zero-len precheck did yield the largest wins out of the approaches I tried.

Discussion:
) { let zero = self.const_usize(0); let count = self.const_usize(count); <del> let start = dest.project_index(self, zero).llval; let end = dest.project_index(self, count).llval; </del> let header_bb = self.append_sibling_block("repeat_loop_header"); let body_bb = self.append_sibling_block("repeat_loop_body");

self.br(header_bb); let mut header_bx = Self::build(self.cx, header_bb); <del> let current = header_bx.phi(self.val_ty(start), &[start], &[self.llbb()]); </del> <ins> let i = header_bx.phi(self.val_ty(zero), &[zero], &[self.llbb()]); </ins> <del> let keep_going = header_bx.icmp(IntPredicate::IntNE, current, end); </del> <ins> let keep_going = header_bx.icmp(IntPredicate::IntULT, i, count); </ins> header_bx.cond_br(keep_going, body_bb, next_bb); let mut body_bx = Self::build(self.cx, body_bb); <del> let align = dest.align.restrict_for_offset(dest.layout.field(self.cx(), 0).size); cg_elem .val .store(&mut body_bx, PlaceRef::new_sized_aligned(current, cg_elem.layout, align)); let next = body_bx.inbounds_gep( self.backend_type(cg_elem.layout), current, &[self.const_usize(1)], ); </del> <ins> let dest_elem = dest.project_index(&mut body_bx, i); cg_elem.val.store(&mut body_bx, dest_elem); let next = body_bx.unchecked_uadd(i, self.const_usize(1)); </ins> body_bx.br(header_bb); <del> header_bx.add_incoming_to_phi(current, next, body_bb); </del> <ins> header_bx.add_incoming_to_phi(i, next, body_bb); </ins> *self = Self::build(self.cx, next_bb); }

use std::sync::Arc; <ins> // CHECK-LABEL: @new_from_array #[no_mangle] pub fn new_from_array(x: u64) -> Arc<[u64]> { // Ensure that we only generate one alloca for the array. // CHECK: alloca // CHECK-SAME: [1000 x i64] // CHECK-NOT: alloca let array = [x; 1000]; Arc::new(array) } </ins> // CHECK-LABEL: @new_uninit #[no_mangle] pub fn new_uninit(x: u64) -> Arc<[u64; 1000]> {