Skip to content

Commit fc5826b

Browse files
authored
cgen: minimise sizeof(EmptyStruct) to 0 for gcc/clang and to 1 for tcc/msvc, by changing EMPTY_STRUCT_DECLARATION and EMPTY_STRUCT_INITIALIZATION (#16733)
1 parent e01dac8 commit fc5826b

File tree

9 files changed

+135
-34
lines changed

9 files changed

+135
-34
lines changed

vlib/arrays/arrays_test.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ fn test_array_append_empty_struct() {
355355
assert (XYZ{} in names) == true
356356

357357
// test fixed array
358-
array := [XYZ{}]
358+
array := [XYZ{}]!
359359
assert (XYZ{} in names) == true
360360
}
361361

vlib/builtin/array.v

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ fn __new_array_with_default(mylen int, cap int, elm_size int, val voidptr) array
4747
len: mylen
4848
cap: cap_
4949
}
50+
// x := []EmptyStruct{cap:5} ; for clang/gcc with -gc none,
51+
// -> sizeof(EmptyStruct) == 0 -> elm_size == 0
52+
// -> total_size == 0 -> malloc(0) -> panic;
53+
// to avoid it, just allocate a single byte
5054
total_size := u64(cap_) * u64(elm_size)
5155
if cap_ > 0 && mylen == 0 {
52-
arr.data = unsafe { malloc(total_size) }
56+
arr.data = unsafe { malloc(__at_least_one(total_size)) }
5357
} else {
5458
arr.data = vcalloc(total_size)
5559
}
@@ -78,7 +82,7 @@ fn __new_array_with_array_default(mylen int, cap int, elm_size int, val array, d
7882
cap_ := if cap < mylen { mylen } else { cap }
7983
mut arr := array{
8084
element_size: elm_size
81-
data: unsafe { malloc(u64(cap_) * u64(elm_size)) }
85+
data: unsafe { malloc(__at_least_one(u64(cap_) * u64(elm_size))) }
8286
len: mylen
8387
cap: cap_
8488
}
@@ -99,7 +103,7 @@ fn __new_array_with_map_default(mylen int, cap int, elm_size int, val map) array
99103
cap_ := if cap < mylen { mylen } else { cap }
100104
mut arr := array{
101105
element_size: elm_size
102-
data: unsafe { malloc(u64(cap_) * u64(elm_size)) }
106+
data: unsafe { malloc(__at_least_one(u64(cap_) * u64(elm_size))) }
103107
len: mylen
104108
cap: cap_
105109
}
@@ -156,7 +160,7 @@ fn (mut a array) ensure_cap(required int) {
156160
cap *= 2
157161
}
158162
new_size := u64(cap) * u64(a.element_size)
159-
new_data := unsafe { malloc(new_size) }
163+
new_data := unsafe { malloc(__at_least_one(new_size)) }
160164
if a.data != unsafe { nil } {
161165
unsafe { vmemcpy(new_data, a.data, u64(a.len) * u64(a.element_size)) }
162166
// TODO: the old data may be leaked when no GC is used (ref-counting?)
@@ -601,13 +605,9 @@ pub fn (a &array) clone() array {
601605
// recursively clone given array - `unsafe` when called directly because depth is not checked
602606
[unsafe]
603607
pub fn (a &array) clone_to_depth(depth int) array {
604-
mut size := u64(a.cap) * u64(a.element_size)
605-
if size == 0 {
606-
size++
607-
}
608608
mut arr := array{
609609
element_size: a.element_size
610-
data: vcalloc(size)
610+
data: vcalloc(u64(a.cap) * u64(a.element_size))
611611
len: a.len
612612
cap: a.cap
613613
}

vlib/builtin/builtin.c.v

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,16 @@ pub fn malloc_noscan(n isize) &u8 {
399399
return res
400400
}
401401

402+
[inline]
403+
fn __at_least_one(how_many u64) u64 {
404+
// handle the case for allocating memory for empty structs, which have sizeof(EmptyStruct) == 0
405+
// in this case, just allocate a single byte, avoiding the panic for malloc(0)
406+
if how_many == 0 {
407+
return 1
408+
}
409+
return how_many
410+
}
411+
402412
// malloc_uncollectable dynamically allocates a `n` bytes block of memory
403413
// on the heap, which will NOT be garbage-collected (but its contents will).
404414
[unsafe]

vlib/builtin/map.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ fn new_dense_array(key_bytes int, value_bytes int) DenseArray {
100100
len: 0
101101
deletes: 0
102102
all_deleted: 0
103-
keys: unsafe { malloc(cap * key_bytes) }
104-
values: unsafe { malloc(cap * value_bytes) }
103+
keys: unsafe { malloc(__at_least_one(u64(cap) * u64(key_bytes))) }
104+
values: unsafe { malloc(__at_least_one(u64(cap) * u64(value_bytes))) }
105105
}
106106
}
107107

vlib/builtin/map_d_gcboehm_opt.v

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,26 @@
66

77
module builtin
88

9+
[inline]
10+
fn __malloc_at_least_one(how_many_bytes u64, noscan bool) &u8 {
11+
if noscan {
12+
return unsafe { malloc_noscan(__at_least_one(how_many_bytes)) }
13+
}
14+
return unsafe { malloc(__at_least_one(how_many_bytes)) }
15+
}
16+
917
[inline]
1018
fn new_dense_array_noscan(key_bytes int, key_noscan bool, value_bytes int, value_noscan bool) DenseArray {
1119
cap := 8
12-
keys := if key_noscan {
13-
unsafe { malloc_noscan(cap * key_bytes) }
14-
} else {
15-
unsafe { malloc(cap * key_bytes) }
16-
}
17-
values := if value_noscan {
18-
unsafe { malloc_noscan(cap * value_bytes) }
19-
} else {
20-
unsafe { malloc(cap * value_bytes) }
21-
}
2220
return DenseArray{
2321
key_bytes: key_bytes
2422
value_bytes: value_bytes
2523
cap: cap
2624
len: 0
2725
deletes: 0
2826
all_deleted: 0
29-
keys: keys
30-
values: values
27+
keys: __malloc_at_least_one(u64(cap) * u64(key_bytes), key_noscan)
28+
values: __malloc_at_least_one(u64(cap) * u64(value_bytes), value_noscan)
3129
}
3230
}
3331

vlib/v/gen/c/array.v

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ fn (mut g Gen) array_init(node ast.ArrayInit, var_name string) {
3434
noscan := g.check_noscan(elem_type.typ)
3535
if elem_type.unaliased_sym.kind == .function {
3636
g.write('new_array_from_c_array(${len}, ${len}, sizeof(voidptr), _MOV((voidptr[${len}]){')
37-
} else if g.is_empty_struct(elem_type) {
38-
g.write('new_array_from_c_array${noscan}(${len}, ${len}, sizeof(voidptr), _MOV((${elem_styp}[${len}]){')
3937
} else {
4038
g.write('new_array_from_c_array${noscan}(${len}, ${len}, sizeof(${elem_styp}), _MOV((${elem_styp}[${len}]){')
4139
}
@@ -216,7 +214,7 @@ fn (mut g Gen) array_init_with_fields(node ast.ArrayInit, elem_type Type, is_amp
216214
} else {
217215
g.write('0, ')
218216
}
219-
if elem_type.unaliased_sym.kind == .function || g.is_empty_struct(elem_type) {
217+
if elem_type.unaliased_sym.kind == .function {
220218
g.write('sizeof(voidptr), ')
221219
} else {
222220
g.write('sizeof(${elem_styp}), ')
@@ -292,7 +290,7 @@ fn (mut g Gen) array_init_with_fields(node ast.ArrayInit, elem_type Type, is_amp
292290
} else {
293291
g.write('0, ')
294292
}
295-
if elem_type.unaliased_sym.kind == .function || g.is_empty_struct(elem_type) {
293+
if elem_type.unaliased_sym.kind == .function {
296294
g.write('sizeof(voidptr), ')
297295
} else {
298296
g.write('sizeof(${elem_styp}), ')

vlib/v/gen/c/cheaders.v

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ static void* __closure_create(void* fn, void* data) {
253253

254254
const c_common_macros = '
255255
#define EMPTY_VARG_INITIALIZATION 0
256-
#define EMPTY_STRUCT_INITIALIZATION 0
257-
#define EMPTY_STRUCT_DECLARATION voidptr _dummy_pad
256+
#define EMPTY_STRUCT_DECLARATION
257+
#define EMPTY_STRUCT_INITIALIZATION
258258
// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is...
259259
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[])
260260
#define TCCSKIP(x) x
@@ -312,9 +312,12 @@ const c_common_macros = '
312312
#ifdef __clang__
313313
#undef __V_GCC__
314314
#endif
315+
315316
#ifdef _MSC_VER
316317
#undef __V_GCC__
318+
#undef EMPTY_STRUCT_DECLARATION
317319
#undef EMPTY_STRUCT_INITIALIZATION
320+
#define EMPTY_STRUCT_DECLARATION unsigned char _dummy_pad
318321
#define EMPTY_STRUCT_INITIALIZATION 0
319322
#endif
320323
@@ -332,7 +335,9 @@ const c_common_macros = '
332335
#ifdef __TINYC__
333336
#define _Atomic volatile
334337
#undef EMPTY_STRUCT_DECLARATION
335-
#define EMPTY_STRUCT_DECLARATION voidptr _dummy_pad
338+
#undef EMPTY_STRUCT_INITIALIZATION
339+
#define EMPTY_STRUCT_DECLARATION unsigned char _dummy_pad
340+
#define EMPTY_STRUCT_INITIALIZATION 0
336341
#undef EMPTY_ARRAY_OF_ELEMS
337342
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n])
338343
#undef __NOINLINE
@@ -594,10 +599,7 @@ voidptr memdup(voidptr src, int sz);
594599
#define _Atomic volatile
595600
596601
// MSVC cannot parse some things properly
597-
#undef EMPTY_STRUCT_DECLARATION
598602
#undef OPTION_CAST
599-
600-
#define EMPTY_STRUCT_DECLARATION voidptr _dummy_pad
601603
#define OPTION_CAST(x)
602604
#undef __NOINLINE
603605
#undef __IRQHANDLER
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
struct EmptyStruct {}
2+
3+
fn test_array_append_empty_struct() {
4+
size_of_empty_struct := dump(sizeof(EmptyStruct))
5+
assert size_of_empty_struct <= 1
6+
mut names := []EmptyStruct{cap: 2}
7+
names << EmptyStruct{}
8+
dump(names)
9+
assert (EmptyStruct{} in names) == true
10+
//
11+
mut fa := [EmptyStruct{}, EmptyStruct{}]!
12+
assert fa.len == 2
13+
dump(fa[0])
14+
fa[0] = EmptyStruct{}
15+
dump(fa[0])
16+
assert fa[0] == EmptyStruct{}
17+
assert fa[1] == EmptyStruct{}
18+
}
19+
20+
fn test_map_of_int_set_empty_struct() {
21+
mut names := map[int]EmptyStruct{}
22+
names[10] = EmptyStruct{}
23+
names[99] = EmptyStruct{}
24+
dump(names)
25+
assert 10 in names
26+
assert 99 in names
27+
assert names[10] == EmptyStruct{}
28+
assert names[99] == EmptyStruct{}
29+
}
30+
31+
fn test_map_of_string_set_empty_struct() {
32+
mut names := map[string]EmptyStruct{}
33+
names['ab'] = EmptyStruct{}
34+
names['cd'] = EmptyStruct{}
35+
dump(names)
36+
assert 'ab' in names
37+
assert 'cd' in names
38+
assert names['ab'] == EmptyStruct{}
39+
assert names['cd'] == EmptyStruct{}
40+
}

vlib/v/tests/empty_struct_test.v

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
struct Z0 {}
2+
3+
struct Z1 {
4+
padding1 char
5+
}
6+
7+
struct Z2 {
8+
padding1 char
9+
padding2 char
10+
}
11+
12+
struct Z3 {
13+
padding1 char
14+
padding2 char
15+
padding3 char
16+
}
17+
18+
struct Z4 {
19+
padding1 char
20+
padding2 char
21+
padding3 char
22+
padding4 char
23+
}
24+
25+
fn test_struct_sizes() {
26+
assert dump(sizeof(Z0)) <= 1 // valid for all
27+
$if tinyc {
28+
// TCC has no problems with 0 sized structs in almost cases,
29+
// except when they are used in fixed arrays, or their address is taken,
30+
// in which case, it produces a compilation error. To avoid it, for it
31+
// empty structs are 1 byte in size.
32+
assert dump(sizeof(Z0)) == 1
33+
}
34+
$if msvc {
35+
// MSVC seems to have no way at all to have empty structs in C mode. It produces the following error:
36+
// `error c2016: C requires that a struct or union have at least one member`.
37+
// Note that MSVC allows empty structs in C++ mode, but that has other restrictions,
38+
// and is not suitable for the generated code of most V programs. Besides, even in C++ mode, the size of
39+
// an empty struct is still 1, not 0.
40+
// For that reason, empty structs are 1 byte in size for MSVC too.
41+
assert dump(sizeof(Z0)) == 1
42+
}
43+
$if clang {
44+
assert dump(sizeof(Z0)) == 0
45+
}
46+
$if gcc {
47+
assert dump(sizeof(Z0)) == 0
48+
}
49+
assert dump(sizeof(Z1)) < sizeof(Z2)
50+
assert dump(sizeof(Z2)) < sizeof(Z3)
51+
assert dump(sizeof(Z3)) < sizeof(Z4)
52+
assert dump(sizeof(Z4)) == 4
53+
}

0 commit comments

Comments
 (0)