Skip to content

Avoid stack -> Box copy when data can be put in Box directly #101829

Open
@jrmuizel

Description

@jrmuizel

In https://bugzilla.mozilla.org/show_bug.cgi?id=1787715 (currently not public for security reasons) we ran into a stack overflow when flate2 calls Box::<CompressorOxide>>::default() in DeflateBackend::make() here:
https://github.com/rust-lang/flate2-rs/blob/cc5ed7f817cc5e5712b2bb924ed19cab4f389a47/src/ffi/rust.rs#L131

DeflateBackend::make() is getting compiled with a call to chkstk(65664)

A reduced version of this is here:
https://rust.godbolt.org/z/hTE3785xY

pub const LZ_CODE_BUF_SIZE: usize = 64 * 1024;

struct LZOxide {
    pub codes: [u8; LZ_CODE_BUF_SIZE],
}

impl LZOxide {
    #[inline(never)]
    const fn new() -> Self {
        LZOxide {
            codes: [0; LZ_CODE_BUF_SIZE],
        }
    }
}

 pub struct CompressorOxide {
    lz: LZOxide,
    huff: Box<u32>,
    pub codes: [u8; LZ_CODE_BUF_SIZE],
}

impl Default for CompressorOxide {
    #[inline(always)]
    fn default() -> Self {
        CompressorOxide {
            lz: LZOxide::new(),
            huff: Box::default(),
            codes: [0; LZ_CODE_BUF_SIZE],

        }
    }
}

pub fn f() -> Box<CompressorOxide> {
    Box::default()
}

A further reduced version that doesn't require any of the special box syntax in Default() is here:
https://rust.godbolt.org/z/8e5EPafzb

pub const LZ_CODE_BUF_SIZE: usize = 64 * 1024;

struct LZOxide {
    pub codes: [u8; LZ_CODE_BUF_SIZE],
}

impl LZOxide {
    #[inline(never)]
    const fn new() -> Self {
        LZOxide {
            codes: [0; LZ_CODE_BUF_SIZE],
        }
    }
}

 pub struct CompressorOxide {
    lz: LZOxide,
    huff: Box<u32>,
    //pub codes: [u8; LZ_CODE_BUF_SIZE],
}

impl Default for CompressorOxide {
    #[inline(always)]
    fn default() -> Self {
        CompressorOxide {
            lz: LZOxide::new(),
            huff: Box::default(),
            //codes: [0; LZ_CODE_BUF_SIZE],

        }
    }
}

use std::ptr;
use std::alloc::{Layout, alloc};

pub fn g() -> Box<CompressorOxide> {
    
    let layout = Layout::new::<CompressorOxide>();
    let ptr = 
            unsafe { alloc(layout) as *mut CompressorOxide };
            assert!(!ptr.is_null());
            unsafe { ptr::write(ptr, CompressorOxide {
            lz: LZOxide::new(),
            huff: Box::default(),

        });
    Box::from_raw(ptr)}

}

This seems to have to do with LZOxide::new() not being inlined. If it's inlined the problem goes away. If the field initialization order of CompressorOxide is swapped
so that we call Box::<u32>::default() before LZOxide::new() the problem also goes away.

@nikic

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-boxArea: Our favorite opsem complicationA-codegenArea: Code generationC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions