Setting a 4 byte enum variant results in a 64k memcopy!


#1

Hello,

I noticed that if I have an enum with a large and a small variant, creating a value using the small variant still zeros out the rest of the memory in the enum.
E.g.

pub enum MyEnum {
    A(u32),
    B([u8; 65536]),
}

pub fn set_enum_a(e: &mut MyEnum) {
    *e = MyEnum::A(35);
}

In this case initialising an enum variant ‘A’ results in a 64k memcopy, even though the variant only takes up 4 bytes. Generated assembler:

set_enum_a::h9d09841b124fc168oaa:
	lea	rsi, [rip + const2970]
	mov	edx, 1028
	jmp	memcpy@PLT
const2970:
	.byte	0
	.zero	3
	.long	35
	.zero	65532
	.size	const2970, 65540

What’s the reason for this?
(e.g. is there a way to ‘change’ the variant of the enum and thus get access to uninitialized data?).

Thanks v much,

Phil


#2

The behaviour you’re seeing seems to be an unfortunate consequence of the new value being a constant expression (i.e. the whole value can be evaluated and placed in static memory at compile time), making it dynamic avoids the memcpy:

pub fn set_enum_a(e: &mut MyEnum, x: u32) {
    *e = MyEnum::A(x);
}
_ZN10set_enum_a20h1a6cfeb63523dd01oaaE:
	.cfi_startproc
	movb	$0, (%rdi)
	movl	%esi, 4(%rdi)
	retq

The constant-value-in-static is actually an explicit decision on the part of rustc, so it seems we should be tweaking that to avoid massive deoptimisations like this. I opened #28398.


#3

Ah awesome, thanks Huon. it didn’t occur to me that this would behave differently with a dynamic expression