From 8974181e7ec2c61a10841fbf77dc341461b82657 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 19 Jun 2026 23:22:56 +0300 Subject: [PATCH] builtin: skip heap allocation for zero-capacity arrays (#27487) (#27500) --- vlib/builtin/array.v | 26 +++++++++----- vlib/builtin/array_d_gcboehm_opt.v | 14 +++++--- vlib/v/tests/array_empty_no_alloc_test.v | 46 ++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 vlib/v/tests/array_empty_no_alloc_test.v diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 39c42dd9d..d0574b148 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -181,7 +181,7 @@ fn __new_array(mylen int, cap int, elm_size int) array { mut data := unsafe { nil } if cap_ > 0 && mylen == 0 { data = alloc_array_data_uninit(total_size) - } else { + } else if cap_ > 0 { data = alloc_array_data(total_size) } arr := array{ @@ -211,7 +211,7 @@ fn __new_array_with_default(mylen int, cap int, elm_size int, val voidptr) array total_size := u64(cap_) * u64(elm_size) if cap_ > 0 && mylen == 0 { arr.data = alloc_array_data_uninit(total_size) - } else { + } else if cap_ > 0 { arr.data = alloc_array_data(total_size) } if val != 0 { @@ -250,7 +250,9 @@ fn __new_array_with_multi_default(mylen int, cap int, elm_size int, val voidptr) // -> total_size == 0 -> malloc(0) -> panic; // to avoid it, just allocate a single byte total_size := u64(cap_) * u64(elm_size) - arr.data = alloc_array_data(total_size) + if cap_ > 0 { + arr.data = alloc_array_data(total_size) + } if val != 0 { mut eptr := &u8(arr.data) unsafe { @@ -271,11 +273,13 @@ fn __new_array_with_array_default(mylen int, cap int, elm_size int, val array, d cap_ := if cap < mylen { mylen } else { cap } mut arr := array{ element_size: elm_size - data: alloc_array_data(u64(cap_) * u64(elm_size)) len: mylen cap: cap_ flags: .managed } + if cap_ > 0 { + arr.data = alloc_array_data(u64(cap_) * u64(elm_size)) + } mut eptr := &u8(arr.data) unsafe { if eptr != nil { @@ -295,11 +299,13 @@ fn __new_array_with_map_default(mylen int, cap int, elm_size int, val map) array cap_ := if cap < mylen { mylen } else { cap } mut arr := array{ element_size: elm_size - data: alloc_array_data(u64(cap_) * u64(elm_size)) len: mylen cap: cap_ flags: .managed } + if cap_ > 0 { + arr.data = alloc_array_data(u64(cap_) * u64(elm_size)) + } mut eptr := &u8(arr.data) unsafe { if eptr != nil { @@ -1050,10 +1056,12 @@ pub fn (a &array) clone_to_depth(depth int) array { source_capacity_in_bytes := u64(a.cap) * u64(a.element_size) use_noscan_data := depth == 0 && a.uses_noscan_data() mut data := unsafe { nil } - if use_noscan_data { - data = a.alloc_array_data_like(source_capacity_in_bytes) - } else { - data = alloc_array_data(source_capacity_in_bytes) + if a.cap > 0 { + if use_noscan_data { + data = a.alloc_array_data_like(source_capacity_in_bytes) + } else { + data = alloc_array_data(source_capacity_in_bytes) + } } mut arr := array{ element_size: a.element_size diff --git a/vlib/builtin/array_d_gcboehm_opt.v b/vlib/builtin/array_d_gcboehm_opt.v index 7ea8775e6..cb0092a77 100644 --- a/vlib/builtin/array_d_gcboehm_opt.v +++ b/vlib/builtin/array_d_gcboehm_opt.v @@ -50,7 +50,7 @@ fn __new_array_noscan(mylen int, cap int, elm_size int) array { mut data := unsafe { nil } if cap_ > 0 && mylen == 0 { data = alloc_array_data_noscan_uninit(total_size) - } else { + } else if cap_ > 0 { data = alloc_array_data_noscan(total_size) } arr := array{ @@ -69,11 +69,13 @@ fn __new_array_with_default_noscan(mylen int, cap int, elm_size int, val voidptr cap_ := if cap < mylen { mylen } else { cap } mut arr := array{ element_size: elm_size - data: alloc_array_data_noscan(u64(cap_) * u64(elm_size)) len: mylen cap: cap_ flags: .managed | .noscan_data } + if cap_ > 0 { + arr.data = alloc_array_data_noscan(u64(cap_) * u64(elm_size)) + } if val != 0 && arr.data != unsafe { nil } { if elm_size == 1 { byte_value := *(&u8(val)) @@ -98,11 +100,13 @@ fn __new_array_with_multi_default_noscan(mylen int, cap int, elm_size int, val v cap_ := if cap < mylen { mylen } else { cap } mut arr := array{ element_size: elm_size - data: alloc_array_data_noscan(u64(cap_) * u64(elm_size)) len: mylen cap: cap_ flags: .managed | .noscan_data } + if cap_ > 0 { + arr.data = alloc_array_data_noscan(u64(cap_) * u64(elm_size)) + } if val != 0 && arr.data != unsafe { nil } { for i in 0 .. arr.len { unsafe { arr.set_unsafe(i, charptr(val) + i * elm_size) } @@ -117,11 +121,13 @@ fn __new_array_with_array_default_noscan(mylen int, cap int, elm_size int, val a cap_ := if cap < mylen { mylen } else { cap } mut arr := array{ element_size: elm_size - data: alloc_array_data_noscan(u64(cap_) * u64(elm_size)) len: mylen cap: cap_ flags: .managed | .noscan_data } + if cap_ > 0 { + arr.data = alloc_array_data_noscan(u64(cap_) * u64(elm_size)) + } for i in 0 .. arr.len { val_clone := val.clone() unsafe { arr.set_unsafe(i, &val_clone) } diff --git a/vlib/v/tests/array_empty_no_alloc_test.v b/vlib/v/tests/array_empty_no_alloc_test.v new file mode 100644 index 000000000..ec68aeb95 --- /dev/null +++ b/vlib/v/tests/array_empty_no_alloc_test.v @@ -0,0 +1,46 @@ +struct Holder { + q []int +} + +fn test_empty_array_literal_has_nil_data() { + a := []int{} + assert isnil(a.data) + assert a.len == 0 + assert a.cap == 0 +} + +fn test_default_struct_array_field_has_nil_data() { + h := Holder{} + assert isnil(h.q.data) + assert h.q.len == 0 + assert h.q.cap == 0 +} + +fn test_empty_array_grows_on_push() { + mut b := []int{} + assert isnil(b.data) + b << 42 + assert b.len == 1 + assert b[0] == 42 + assert !isnil(b.data) +} + +fn test_empty_array_clone_has_nil_data() { + a := []int{} + c := a.clone() + assert isnil(c.data) + assert c.len == 0 + assert c.cap == 0 +} + +fn test_array_with_cap_still_allocates() { + d := []int{cap: 5} + assert !isnil(d.data) + assert d.cap == 5 +} + +fn test_array_with_len_still_allocates() { + e := []int{len: 3} + assert !isnil(e.data) + assert e.len == 3 +} -- 2.39.5