mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
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)
This commit is contained in:
parent
e01dac885c
commit
fc5826b7ca
@ -355,7 +355,7 @@ fn test_array_append_empty_struct() {
|
|||||||
assert (XYZ{} in names) == true
|
assert (XYZ{} in names) == true
|
||||||
|
|
||||||
// test fixed array
|
// test fixed array
|
||||||
array := [XYZ{}]
|
array := [XYZ{}]!
|
||||||
assert (XYZ{} in names) == true
|
assert (XYZ{} in names) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +47,13 @@ fn __new_array_with_default(mylen int, cap int, elm_size int, val voidptr) array
|
|||||||
len: mylen
|
len: mylen
|
||||||
cap: cap_
|
cap: cap_
|
||||||
}
|
}
|
||||||
|
// x := []EmptyStruct{cap:5} ; for clang/gcc with -gc none,
|
||||||
|
// -> sizeof(EmptyStruct) == 0 -> elm_size == 0
|
||||||
|
// -> total_size == 0 -> malloc(0) -> panic;
|
||||||
|
// to avoid it, just allocate a single byte
|
||||||
total_size := u64(cap_) * u64(elm_size)
|
total_size := u64(cap_) * u64(elm_size)
|
||||||
if cap_ > 0 && mylen == 0 {
|
if cap_ > 0 && mylen == 0 {
|
||||||
arr.data = unsafe { malloc(total_size) }
|
arr.data = unsafe { malloc(__at_least_one(total_size)) }
|
||||||
} else {
|
} else {
|
||||||
arr.data = vcalloc(total_size)
|
arr.data = vcalloc(total_size)
|
||||||
}
|
}
|
||||||
@ -78,7 +82,7 @@ fn __new_array_with_array_default(mylen int, cap int, elm_size int, val array, d
|
|||||||
cap_ := if cap < mylen { mylen } else { cap }
|
cap_ := if cap < mylen { mylen } else { cap }
|
||||||
mut arr := array{
|
mut arr := array{
|
||||||
element_size: elm_size
|
element_size: elm_size
|
||||||
data: unsafe { malloc(u64(cap_) * u64(elm_size)) }
|
data: unsafe { malloc(__at_least_one(u64(cap_) * u64(elm_size))) }
|
||||||
len: mylen
|
len: mylen
|
||||||
cap: cap_
|
cap: cap_
|
||||||
}
|
}
|
||||||
@ -99,7 +103,7 @@ fn __new_array_with_map_default(mylen int, cap int, elm_size int, val map) array
|
|||||||
cap_ := if cap < mylen { mylen } else { cap }
|
cap_ := if cap < mylen { mylen } else { cap }
|
||||||
mut arr := array{
|
mut arr := array{
|
||||||
element_size: elm_size
|
element_size: elm_size
|
||||||
data: unsafe { malloc(u64(cap_) * u64(elm_size)) }
|
data: unsafe { malloc(__at_least_one(u64(cap_) * u64(elm_size))) }
|
||||||
len: mylen
|
len: mylen
|
||||||
cap: cap_
|
cap: cap_
|
||||||
}
|
}
|
||||||
@ -156,7 +160,7 @@ fn (mut a array) ensure_cap(required int) {
|
|||||||
cap *= 2
|
cap *= 2
|
||||||
}
|
}
|
||||||
new_size := u64(cap) * u64(a.element_size)
|
new_size := u64(cap) * u64(a.element_size)
|
||||||
new_data := unsafe { malloc(new_size) }
|
new_data := unsafe { malloc(__at_least_one(new_size)) }
|
||||||
if a.data != unsafe { nil } {
|
if a.data != unsafe { nil } {
|
||||||
unsafe { vmemcpy(new_data, a.data, u64(a.len) * u64(a.element_size)) }
|
unsafe { vmemcpy(new_data, a.data, u64(a.len) * u64(a.element_size)) }
|
||||||
// TODO: the old data may be leaked when no GC is used (ref-counting?)
|
// TODO: the old data may be leaked when no GC is used (ref-counting?)
|
||||||
@ -601,13 +605,9 @@ pub fn (a &array) clone() array {
|
|||||||
// recursively clone given array - `unsafe` when called directly because depth is not checked
|
// recursively clone given array - `unsafe` when called directly because depth is not checked
|
||||||
[unsafe]
|
[unsafe]
|
||||||
pub fn (a &array) clone_to_depth(depth int) array {
|
pub fn (a &array) clone_to_depth(depth int) array {
|
||||||
mut size := u64(a.cap) * u64(a.element_size)
|
|
||||||
if size == 0 {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
mut arr := array{
|
mut arr := array{
|
||||||
element_size: a.element_size
|
element_size: a.element_size
|
||||||
data: vcalloc(size)
|
data: vcalloc(u64(a.cap) * u64(a.element_size))
|
||||||
len: a.len
|
len: a.len
|
||||||
cap: a.cap
|
cap: a.cap
|
||||||
}
|
}
|
||||||
|
@ -399,6 +399,16 @@ pub fn malloc_noscan(n isize) &u8 {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn __at_least_one(how_many u64) u64 {
|
||||||
|
// handle the case for allocating memory for empty structs, which have sizeof(EmptyStruct) == 0
|
||||||
|
// in this case, just allocate a single byte, avoiding the panic for malloc(0)
|
||||||
|
if how_many == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return how_many
|
||||||
|
}
|
||||||
|
|
||||||
// malloc_uncollectable dynamically allocates a `n` bytes block of memory
|
// malloc_uncollectable dynamically allocates a `n` bytes block of memory
|
||||||
// on the heap, which will NOT be garbage-collected (but its contents will).
|
// on the heap, which will NOT be garbage-collected (but its contents will).
|
||||||
[unsafe]
|
[unsafe]
|
||||||
|
@ -100,8 +100,8 @@ fn new_dense_array(key_bytes int, value_bytes int) DenseArray {
|
|||||||
len: 0
|
len: 0
|
||||||
deletes: 0
|
deletes: 0
|
||||||
all_deleted: 0
|
all_deleted: 0
|
||||||
keys: unsafe { malloc(cap * key_bytes) }
|
keys: unsafe { malloc(__at_least_one(u64(cap) * u64(key_bytes))) }
|
||||||
values: unsafe { malloc(cap * value_bytes) }
|
values: unsafe { malloc(__at_least_one(u64(cap) * u64(value_bytes))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,19 +6,17 @@
|
|||||||
|
|
||||||
module builtin
|
module builtin
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn __malloc_at_least_one(how_many_bytes u64, noscan bool) &u8 {
|
||||||
|
if noscan {
|
||||||
|
return unsafe { malloc_noscan(__at_least_one(how_many_bytes)) }
|
||||||
|
}
|
||||||
|
return unsafe { malloc(__at_least_one(how_many_bytes)) }
|
||||||
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
fn new_dense_array_noscan(key_bytes int, key_noscan bool, value_bytes int, value_noscan bool) DenseArray {
|
fn new_dense_array_noscan(key_bytes int, key_noscan bool, value_bytes int, value_noscan bool) DenseArray {
|
||||||
cap := 8
|
cap := 8
|
||||||
keys := if key_noscan {
|
|
||||||
unsafe { malloc_noscan(cap * key_bytes) }
|
|
||||||
} else {
|
|
||||||
unsafe { malloc(cap * key_bytes) }
|
|
||||||
}
|
|
||||||
values := if value_noscan {
|
|
||||||
unsafe { malloc_noscan(cap * value_bytes) }
|
|
||||||
} else {
|
|
||||||
unsafe { malloc(cap * value_bytes) }
|
|
||||||
}
|
|
||||||
return DenseArray{
|
return DenseArray{
|
||||||
key_bytes: key_bytes
|
key_bytes: key_bytes
|
||||||
value_bytes: value_bytes
|
value_bytes: value_bytes
|
||||||
@ -26,8 +24,8 @@ fn new_dense_array_noscan(key_bytes int, key_noscan bool, value_bytes int, value
|
|||||||
len: 0
|
len: 0
|
||||||
deletes: 0
|
deletes: 0
|
||||||
all_deleted: 0
|
all_deleted: 0
|
||||||
keys: keys
|
keys: __malloc_at_least_one(u64(cap) * u64(key_bytes), key_noscan)
|
||||||
values: values
|
values: __malloc_at_least_one(u64(cap) * u64(value_bytes), value_noscan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,6 @@ fn (mut g Gen) array_init(node ast.ArrayInit, var_name string) {
|
|||||||
noscan := g.check_noscan(elem_type.typ)
|
noscan := g.check_noscan(elem_type.typ)
|
||||||
if elem_type.unaliased_sym.kind == .function {
|
if elem_type.unaliased_sym.kind == .function {
|
||||||
g.write('new_array_from_c_array(${len}, ${len}, sizeof(voidptr), _MOV((voidptr[${len}]){')
|
g.write('new_array_from_c_array(${len}, ${len}, sizeof(voidptr), _MOV((voidptr[${len}]){')
|
||||||
} else if g.is_empty_struct(elem_type) {
|
|
||||||
g.write('new_array_from_c_array${noscan}(${len}, ${len}, sizeof(voidptr), _MOV((${elem_styp}[${len}]){')
|
|
||||||
} else {
|
} else {
|
||||||
g.write('new_array_from_c_array${noscan}(${len}, ${len}, sizeof(${elem_styp}), _MOV((${elem_styp}[${len}]){')
|
g.write('new_array_from_c_array${noscan}(${len}, ${len}, sizeof(${elem_styp}), _MOV((${elem_styp}[${len}]){')
|
||||||
}
|
}
|
||||||
@ -216,7 +214,7 @@ fn (mut g Gen) array_init_with_fields(node ast.ArrayInit, elem_type Type, is_amp
|
|||||||
} else {
|
} else {
|
||||||
g.write('0, ')
|
g.write('0, ')
|
||||||
}
|
}
|
||||||
if elem_type.unaliased_sym.kind == .function || g.is_empty_struct(elem_type) {
|
if elem_type.unaliased_sym.kind == .function {
|
||||||
g.write('sizeof(voidptr), ')
|
g.write('sizeof(voidptr), ')
|
||||||
} else {
|
} else {
|
||||||
g.write('sizeof(${elem_styp}), ')
|
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
|
|||||||
} else {
|
} else {
|
||||||
g.write('0, ')
|
g.write('0, ')
|
||||||
}
|
}
|
||||||
if elem_type.unaliased_sym.kind == .function || g.is_empty_struct(elem_type) {
|
if elem_type.unaliased_sym.kind == .function {
|
||||||
g.write('sizeof(voidptr), ')
|
g.write('sizeof(voidptr), ')
|
||||||
} else {
|
} else {
|
||||||
g.write('sizeof(${elem_styp}), ')
|
g.write('sizeof(${elem_styp}), ')
|
||||||
|
@ -253,8 +253,8 @@ static void* __closure_create(void* fn, void* data) {
|
|||||||
|
|
||||||
const c_common_macros = '
|
const c_common_macros = '
|
||||||
#define EMPTY_VARG_INITIALIZATION 0
|
#define EMPTY_VARG_INITIALIZATION 0
|
||||||
#define EMPTY_STRUCT_INITIALIZATION 0
|
#define EMPTY_STRUCT_DECLARATION
|
||||||
#define EMPTY_STRUCT_DECLARATION voidptr _dummy_pad
|
#define EMPTY_STRUCT_INITIALIZATION
|
||||||
// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is...
|
// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is...
|
||||||
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[])
|
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[])
|
||||||
#define TCCSKIP(x) x
|
#define TCCSKIP(x) x
|
||||||
@ -312,9 +312,12 @@ const c_common_macros = '
|
|||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
#undef __V_GCC__
|
#undef __V_GCC__
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#undef __V_GCC__
|
#undef __V_GCC__
|
||||||
|
#undef EMPTY_STRUCT_DECLARATION
|
||||||
#undef EMPTY_STRUCT_INITIALIZATION
|
#undef EMPTY_STRUCT_INITIALIZATION
|
||||||
|
#define EMPTY_STRUCT_DECLARATION unsigned char _dummy_pad
|
||||||
#define EMPTY_STRUCT_INITIALIZATION 0
|
#define EMPTY_STRUCT_INITIALIZATION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -332,7 +335,9 @@ const c_common_macros = '
|
|||||||
#ifdef __TINYC__
|
#ifdef __TINYC__
|
||||||
#define _Atomic volatile
|
#define _Atomic volatile
|
||||||
#undef EMPTY_STRUCT_DECLARATION
|
#undef EMPTY_STRUCT_DECLARATION
|
||||||
#define EMPTY_STRUCT_DECLARATION voidptr _dummy_pad
|
#undef EMPTY_STRUCT_INITIALIZATION
|
||||||
|
#define EMPTY_STRUCT_DECLARATION unsigned char _dummy_pad
|
||||||
|
#define EMPTY_STRUCT_INITIALIZATION 0
|
||||||
#undef EMPTY_ARRAY_OF_ELEMS
|
#undef EMPTY_ARRAY_OF_ELEMS
|
||||||
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n])
|
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n])
|
||||||
#undef __NOINLINE
|
#undef __NOINLINE
|
||||||
@ -594,10 +599,7 @@ voidptr memdup(voidptr src, int sz);
|
|||||||
#define _Atomic volatile
|
#define _Atomic volatile
|
||||||
|
|
||||||
// MSVC cannot parse some things properly
|
// MSVC cannot parse some things properly
|
||||||
#undef EMPTY_STRUCT_DECLARATION
|
|
||||||
#undef OPTION_CAST
|
#undef OPTION_CAST
|
||||||
|
|
||||||
#define EMPTY_STRUCT_DECLARATION voidptr _dummy_pad
|
|
||||||
#define OPTION_CAST(x)
|
#define OPTION_CAST(x)
|
||||||
#undef __NOINLINE
|
#undef __NOINLINE
|
||||||
#undef __IRQHANDLER
|
#undef __IRQHANDLER
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
struct EmptyStruct {}
|
||||||
|
|
||||||
|
fn test_array_append_empty_struct() {
|
||||||
|
size_of_empty_struct := dump(sizeof(EmptyStruct))
|
||||||
|
assert size_of_empty_struct <= 1
|
||||||
|
mut names := []EmptyStruct{cap: 2}
|
||||||
|
names << EmptyStruct{}
|
||||||
|
dump(names)
|
||||||
|
assert (EmptyStruct{} in names) == true
|
||||||
|
//
|
||||||
|
mut fa := [EmptyStruct{}, EmptyStruct{}]!
|
||||||
|
assert fa.len == 2
|
||||||
|
dump(fa[0])
|
||||||
|
fa[0] = EmptyStruct{}
|
||||||
|
dump(fa[0])
|
||||||
|
assert fa[0] == EmptyStruct{}
|
||||||
|
assert fa[1] == EmptyStruct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_map_of_int_set_empty_struct() {
|
||||||
|
mut names := map[int]EmptyStruct{}
|
||||||
|
names[10] = EmptyStruct{}
|
||||||
|
names[99] = EmptyStruct{}
|
||||||
|
dump(names)
|
||||||
|
assert 10 in names
|
||||||
|
assert 99 in names
|
||||||
|
assert names[10] == EmptyStruct{}
|
||||||
|
assert names[99] == EmptyStruct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_map_of_string_set_empty_struct() {
|
||||||
|
mut names := map[string]EmptyStruct{}
|
||||||
|
names['ab'] = EmptyStruct{}
|
||||||
|
names['cd'] = EmptyStruct{}
|
||||||
|
dump(names)
|
||||||
|
assert 'ab' in names
|
||||||
|
assert 'cd' in names
|
||||||
|
assert names['ab'] == EmptyStruct{}
|
||||||
|
assert names['cd'] == EmptyStruct{}
|
||||||
|
}
|
53
vlib/v/tests/empty_struct_test.v
Normal file
53
vlib/v/tests/empty_struct_test.v
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
struct Z0 {}
|
||||||
|
|
||||||
|
struct Z1 {
|
||||||
|
padding1 char
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Z2 {
|
||||||
|
padding1 char
|
||||||
|
padding2 char
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Z3 {
|
||||||
|
padding1 char
|
||||||
|
padding2 char
|
||||||
|
padding3 char
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Z4 {
|
||||||
|
padding1 char
|
||||||
|
padding2 char
|
||||||
|
padding3 char
|
||||||
|
padding4 char
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_struct_sizes() {
|
||||||
|
assert dump(sizeof(Z0)) <= 1 // valid for all
|
||||||
|
$if tinyc {
|
||||||
|
// TCC has no problems with 0 sized structs in almost cases,
|
||||||
|
// except when they are used in fixed arrays, or their address is taken,
|
||||||
|
// in which case, it produces a compilation error. To avoid it, for it
|
||||||
|
// empty structs are 1 byte in size.
|
||||||
|
assert dump(sizeof(Z0)) == 1
|
||||||
|
}
|
||||||
|
$if msvc {
|
||||||
|
// MSVC seems to have no way at all to have empty structs in C mode. It produces the following error:
|
||||||
|
// `error c2016: C requires that a struct or union have at least one member`.
|
||||||
|
// Note that MSVC allows empty structs in C++ mode, but that has other restrictions,
|
||||||
|
// and is not suitable for the generated code of most V programs. Besides, even in C++ mode, the size of
|
||||||
|
// an empty struct is still 1, not 0.
|
||||||
|
// For that reason, empty structs are 1 byte in size for MSVC too.
|
||||||
|
assert dump(sizeof(Z0)) == 1
|
||||||
|
}
|
||||||
|
$if clang {
|
||||||
|
assert dump(sizeof(Z0)) == 0
|
||||||
|
}
|
||||||
|
$if gcc {
|
||||||
|
assert dump(sizeof(Z0)) == 0
|
||||||
|
}
|
||||||
|
assert dump(sizeof(Z1)) < sizeof(Z2)
|
||||||
|
assert dump(sizeof(Z2)) < sizeof(Z3)
|
||||||
|
assert dump(sizeof(Z3)) < sizeof(Z4)
|
||||||
|
assert dump(sizeof(Z4)) == 4
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user