mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-04 06:38:47 +00:00
Merge remote-tracking branch 'offical/master'
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
|
Copyright (c) 2016-2024 Ginger Bill. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|||||||
@@ -367,23 +367,36 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
opt_write_end(w, opt, '}') or_return
|
opt_write_end(w, opt, '}') or_return
|
||||||
|
|
||||||
case runtime.Type_Info_Struct:
|
case runtime.Type_Info_Struct:
|
||||||
opt_write_start(w, opt, '{') or_return
|
marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) {
|
||||||
|
ti := runtime.type_info_base(type_info_of(v.id))
|
||||||
|
info := ti.variant.(runtime.Type_Info_Struct)
|
||||||
|
for name, i in info.names {
|
||||||
|
json_name := reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")
|
||||||
|
|
||||||
for name, i in info.names {
|
opt_write_iteration(w, opt, i) or_return
|
||||||
json_name := reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")
|
if json_name != "" {
|
||||||
|
opt_write_key(w, opt, json_name) or_return
|
||||||
|
} else {
|
||||||
|
// Marshal the fields of 'using _: T' fields directly into the parent struct
|
||||||
|
if info.usings[i] && name == "_" {
|
||||||
|
id := info.types[i].id
|
||||||
|
data := rawptr(uintptr(v.data) + info.offsets[i])
|
||||||
|
marshal_struct_fields(w, any{data, id}, opt) or_return
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
opt_write_key(w, opt, name) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opt_write_iteration(w, opt, i) or_return
|
id := info.types[i].id
|
||||||
if json_name != "" {
|
data := rawptr(uintptr(v.data) + info.offsets[i])
|
||||||
opt_write_key(w, opt, json_name) or_return
|
marshal_to_writer(w, any{data, id}, opt) or_return
|
||||||
} else {
|
|
||||||
opt_write_key(w, opt, name) or_return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
id := info.types[i].id
|
|
||||||
data := rawptr(uintptr(v.data) + info.offsets[i])
|
|
||||||
marshal_to_writer(w, any{data, id}, opt) or_return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opt_write_start(w, opt, '{') or_return
|
||||||
|
marshal_struct_fields(w, v, opt) or_return
|
||||||
opt_write_end(w, opt, '}') or_return
|
opt_write_end(w, opt, '}') or_return
|
||||||
|
|
||||||
case runtime.Type_Info_Union:
|
case runtime.Type_Info_Union:
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
package hash
|
package hash
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check {
|
crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check {
|
||||||
result = seed
|
result = seed
|
||||||
#no_bounds_check for b in data {
|
#no_bounds_check for b in data {
|
||||||
result = result<<8 ~ _crc64_table_ecma_182[((result>>56) ~ u64(b)) & 0xff]
|
result = result<<8 ~ _crc64_table_ecma_182[((result>>56) ~ u64(b)) & 0xff]
|
||||||
@@ -15,7 +15,7 @@ crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds
|
|||||||
Based on Mark Adler's v1.4 implementation in C under the ZLIB license.
|
Based on Mark Adler's v1.4 implementation in C under the ZLIB license.
|
||||||
*/
|
*/
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
crc64_xz :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
||||||
data := data
|
data := data
|
||||||
result := ~u64le(seed)
|
result := ~u64le(seed)
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ crc64_xz :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
|||||||
Generator polynomial: x^64 + x^4 + x^3 + x + 1
|
Generator polynomial: x^64 + x^4 + x^3 + x + 1
|
||||||
*/
|
*/
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
crc64_iso_3306 :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
crc64_iso_3306 :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
||||||
|
|
||||||
result := seed
|
result := seed
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ crc64_iso_3306 :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
crc64_iso_3306_inverse :: proc(data: []byte, seed := u64(0)) -> u64 {
|
crc64_iso_3306_inverse :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 {
|
||||||
result := #force_inline crc64_iso_3306(data, ~seed)
|
result := #force_inline crc64_iso_3306(data, ~seed)
|
||||||
return ~result
|
return ~result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package hash
|
|||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
||||||
crc := ~seed
|
crc := ~seed
|
||||||
buffer := raw_data(data)
|
buffer := raw_data(data)
|
||||||
length := len(data)
|
length := len(data)
|
||||||
@@ -323,7 +323,7 @@ crc32_table := [8][256]u32{
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
crc32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||||
result := ~u32(seed);
|
result := ~u32(seed);
|
||||||
#no_bounds_check for b in data {
|
#no_bounds_check for b in data {
|
||||||
result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
|
result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
|
||||||
|
|||||||
+12
-12
@@ -4,7 +4,7 @@ import "core:mem"
|
|||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check {
|
adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_check {
|
||||||
|
|
||||||
ADLER_CONST :: 65521
|
ADLER_CONST :: 65521
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
djb2 :: proc(data: []byte, seed := u32(5381)) -> u32 {
|
djb2 :: proc "contextless" (data: []byte, seed := u32(5381)) -> u32 {
|
||||||
hash: u32 = seed
|
hash: u32 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
hash = (hash << 5) + hash + u32(b) // hash * 33 + u32(b)
|
hash = (hash << 5) + hash + u32(b) // hash * 33 + u32(b)
|
||||||
@@ -55,7 +55,7 @@ djb2 :: proc(data: []byte, seed := u32(5381)) -> u32 {
|
|||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bounds_check {
|
djbx33a :: proc "contextless" (data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bounds_check {
|
||||||
state := [4]u32{seed, seed, seed, seed}
|
state := [4]u32{seed, seed, seed, seed}
|
||||||
|
|
||||||
s: u32 = 0
|
s: u32 = 0
|
||||||
@@ -74,7 +74,7 @@ djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bound
|
|||||||
|
|
||||||
// If you have a choice, prefer fnv32a
|
// If you have a choice, prefer fnv32a
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
fnv32_no_a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
fnv32_no_a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||||
h: u32 = seed
|
h: u32 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
h = (h * 0x01000193) ~ u32(b)
|
h = (h * 0x01000193) ~ u32(b)
|
||||||
@@ -87,7 +87,7 @@ fnv64 :: fnv64_no_a // NOTE(bill): Not a fan of these aliases but seems necessar
|
|||||||
|
|
||||||
// If you have a choice, prefer fnv64a
|
// If you have a choice, prefer fnv64a
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
fnv64_no_a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
||||||
h: u64 = seed
|
h: u64 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
h = (h * 0x100000001b3) ~ u64(b)
|
h = (h * 0x100000001b3) ~ u64(b)
|
||||||
@@ -95,7 +95,7 @@ fnv64_no_a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||||
h: u32 = seed
|
h: u32 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
h = (h ~ u32(b)) * 0x01000193
|
h = (h ~ u32(b)) * 0x01000193
|
||||||
@@ -104,7 +104,7 @@ fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
fnv64a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
||||||
h: u64 = seed
|
h: u64 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
h = (h ~ u64(b)) * 0x100000001b3
|
h = (h ~ u64(b)) * 0x100000001b3
|
||||||
@@ -113,7 +113,7 @@ fnv64a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
jenkins :: proc(data: []byte, seed := u32(0)) -> u32 {
|
jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||||
hash: u32 = seed
|
hash: u32 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
hash += u32(b)
|
hash += u32(b)
|
||||||
@@ -127,7 +127,7 @@ jenkins :: proc(data: []byte, seed := u32(0)) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||||
c1_32: u32 : 0xcc9e2d51
|
c1_32: u32 : 0xcc9e2d51
|
||||||
c2_32: u32 : 0x1b873593
|
c2_32: u32 : 0x1b873593
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 {
|
|||||||
|
|
||||||
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96
|
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||||
m :: 0xc6a4a7935bd1e995
|
m :: 0xc6a4a7935bd1e995
|
||||||
r :: 47
|
r :: 47
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
|||||||
|
|
||||||
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140
|
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||||
m :: 0x5bd1e995
|
m :: 0x5bd1e995
|
||||||
r :: 24
|
r :: 24
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
sdbm :: proc(data: []byte, seed := u32(0)) -> u32 {
|
sdbm :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||||
hash: u32 = seed
|
hash: u32 = seed
|
||||||
for b in data {
|
for b in data {
|
||||||
hash = u32(b) + (hash<<6) + (hash<<16) - hash
|
hash = u32(b) + (hash<<6) + (hash<<16) - hash
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
package hash
|
package hash
|
||||||
|
|
||||||
ginger_hash8 :: proc(x: u8) -> u8 {
|
ginger_hash8 :: proc "contextless" (x: u8) -> u8 {
|
||||||
h := x * 251
|
h := x * 251
|
||||||
h += ~(x << 3)
|
h += ~(x << 3)
|
||||||
h ~= (x >> 1)
|
h ~= (x >> 1)
|
||||||
@@ -11,7 +11,7 @@ ginger_hash8 :: proc(x: u8) -> u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ginger_hash16 :: proc(x: u16) -> u16 {
|
ginger_hash16 :: proc "contextless" (x: u16) -> u16 {
|
||||||
z := (x << 8) | (x >> 8)
|
z := (x << 8) | (x >> 8)
|
||||||
h := z
|
h := z
|
||||||
h += ~(z << 5)
|
h += ~(z << 5)
|
||||||
@@ -24,14 +24,14 @@ ginger_hash16 :: proc(x: u16) -> u16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ginger8 :: proc(data: []byte) -> u8 {
|
ginger8 :: proc "contextless" (data: []byte) -> u8 {
|
||||||
h := ginger_hash8(0)
|
h := ginger_hash8(0)
|
||||||
for b in data {
|
for b in data {
|
||||||
h ~= ginger_hash8(b)
|
h ~= ginger_hash8(b)
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
ginger16 :: proc(data: []byte) -> u16 {
|
ginger16 :: proc "contextless" (data: []byte) -> u16 {
|
||||||
h := ginger_hash16(0)
|
h := ginger_hash16(0)
|
||||||
for b in data {
|
for b in data {
|
||||||
h ~= ginger_hash16(u16(b))
|
h ~= ginger_hash16(u16(b))
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ XXH3_create_state :: proc(allocator := context.allocator) -> (res: ^XXH3_state,
|
|||||||
}
|
}
|
||||||
|
|
||||||
XXH3_destroy_state :: proc(state: ^XXH3_state, allocator := context.allocator) -> (err: Error) {
|
XXH3_destroy_state :: proc(state: ^XXH3_state, allocator := context.allocator) -> (err: Error) {
|
||||||
free(state)
|
free(state, allocator)
|
||||||
return .None
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ xxh_u32 :: u32
|
|||||||
XXH32_DEFAULT_SEED :: XXH32_hash(0)
|
XXH32_DEFAULT_SEED :: XXH32_hash(0)
|
||||||
|
|
||||||
XXH32_state :: struct {
|
XXH32_state :: struct {
|
||||||
total_len_32: XXH32_hash, /*!< Total length hashed, modulo 2^32 */
|
total_len_32: XXH32_hash, /*!< Total length hashed, modulo 2^32 */
|
||||||
large_len: XXH32_hash, /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */
|
large_len: XXH32_hash, /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */
|
||||||
v1: XXH32_hash, /*!< First accumulator lane */
|
v1: XXH32_hash, /*!< First accumulator lane */
|
||||||
v2: XXH32_hash, /*!< Second accumulator lane */
|
v2: XXH32_hash, /*!< Second accumulator lane */
|
||||||
v3: XXH32_hash, /*!< Third accumulator lane */
|
v3: XXH32_hash, /*!< Third accumulator lane */
|
||||||
v4: XXH32_hash, /*!< Fourth accumulator lane */
|
v4: XXH32_hash, /*!< Fourth accumulator lane */
|
||||||
mem32: [4]XXH32_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */
|
mem32: [4]XXH32_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */
|
||||||
memsize: XXH32_hash, /*!< Amount of data in @ref mem32 */
|
memsize: XXH32_hash, /*!< Amount of data in @ref mem32 */
|
||||||
reserved: XXH32_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */
|
reserved: XXH32_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */
|
||||||
}
|
}
|
||||||
|
|
||||||
XXH32_canonical :: struct {
|
XXH32_canonical :: struct {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package sync
|
package sync
|
||||||
|
|
||||||
import "core:c"
|
import "core:c"
|
||||||
|
import "core:sys/darwin"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
|
|
||||||
foreign import System "system:System.framework"
|
foreign import System "system:System.framework"
|
||||||
@@ -29,8 +30,29 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
|
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
|
||||||
timeout_ns := u32(duration) * 1000
|
when darwin.WAIT_ON_ADDRESS_AVAILABLE {
|
||||||
|
s: i32
|
||||||
|
if duration > 0 {
|
||||||
|
s = darwin.os_sync_wait_on_address_with_timeout(f, u64(expected), size_of(Futex), {}, .MACH_ABSOLUTE_TIME, u64(duration))
|
||||||
|
} else {
|
||||||
|
s = darwin.os_sync_wait_on_address(f, u64(expected), size_of(Futex), {})
|
||||||
|
}
|
||||||
|
|
||||||
|
if s >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch darwin.errno() {
|
||||||
|
case -EINTR, -EFAULT:
|
||||||
|
return true
|
||||||
|
case -ETIMEDOUT:
|
||||||
|
return false
|
||||||
|
case:
|
||||||
|
_panic("darwin.os_sync_wait_on_address_with_timeout failure")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
timeout_ns := u32(duration) * 1000
|
||||||
s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
|
s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
|
||||||
if s >= 0 {
|
if s >= 0 {
|
||||||
return true
|
return true
|
||||||
@@ -45,9 +67,27 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_futex_signal :: proc "contextless" (f: ^Futex) {
|
_futex_signal :: proc "contextless" (f: ^Futex) {
|
||||||
|
when darwin.WAIT_ON_ADDRESS_AVAILABLE {
|
||||||
|
loop: for {
|
||||||
|
s := darwin.os_sync_wake_by_address_any(f, size_of(Futex), {})
|
||||||
|
if s >= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch darwin.errno() {
|
||||||
|
case -EINTR, -EFAULT:
|
||||||
|
continue loop
|
||||||
|
case -ENOENT:
|
||||||
|
return
|
||||||
|
case:
|
||||||
|
_panic("darwin.os_sync_wake_by_address_any failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
loop: for {
|
loop: for {
|
||||||
s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0)
|
s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0)
|
||||||
if s >= 0 {
|
if s >= 0 {
|
||||||
@@ -62,9 +102,28 @@ _futex_signal :: proc "contextless" (f: ^Futex) {
|
|||||||
_panic("futex_wake_single failure")
|
_panic("futex_wake_single failure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_futex_broadcast :: proc "contextless" (f: ^Futex) {
|
_futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||||
|
when darwin.WAIT_ON_ADDRESS_AVAILABLE {
|
||||||
|
loop: for {
|
||||||
|
s := darwin.os_sync_wake_by_address_all(f, size_of(Futex), {})
|
||||||
|
if s >= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch darwin.errno() {
|
||||||
|
case -EINTR, -EFAULT:
|
||||||
|
continue loop
|
||||||
|
case -ENOENT:
|
||||||
|
return
|
||||||
|
case:
|
||||||
|
_panic("darwin.os_sync_wake_by_address_all failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
loop: for {
|
loop: for {
|
||||||
s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0)
|
s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0)
|
||||||
if s >= 0 {
|
if s >= 0 {
|
||||||
@@ -79,5 +138,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
|
|||||||
_panic("futex_wake_all failure")
|
_panic("futex_wake_all failure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package darwin
|
|||||||
|
|
||||||
import "core:c"
|
import "core:c"
|
||||||
|
|
||||||
|
foreign import system "system:System.framework"
|
||||||
|
|
||||||
Bool :: b8
|
Bool :: b8
|
||||||
|
|
||||||
RUsage :: struct {
|
RUsage :: struct {
|
||||||
@@ -24,3 +26,10 @@ RUsage :: struct {
|
|||||||
ru_nivcsw: c.long,
|
ru_nivcsw: c.long,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreign system {
|
||||||
|
__error :: proc() -> ^i32 ---
|
||||||
|
}
|
||||||
|
|
||||||
|
errno :: #force_inline proc "contextless" () -> i32 {
|
||||||
|
return __error()^
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
package darwin
|
||||||
|
|
||||||
|
foreign import system "system:System.framework"
|
||||||
|
|
||||||
|
// #define OS_WAIT_ON_ADDR_AVAILABILITY \
|
||||||
|
// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
|
||||||
|
when ODIN_OS == .Darwin {
|
||||||
|
when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION > 17_04_00 {
|
||||||
|
WAIT_ON_ADDRESS_AVAILABLE :: true
|
||||||
|
} else when ODIN_MINIMUM_OS_VERSION > 14_04_00 {
|
||||||
|
WAIT_ON_ADDRESS_AVAILABLE :: true
|
||||||
|
} else {
|
||||||
|
WAIT_ON_ADDRESS_AVAILABLE :: false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WAIT_ON_ADDRESS_AVAILABLE :: false
|
||||||
|
}
|
||||||
|
|
||||||
|
os_sync_wait_on_address_flag :: enum u32 {
|
||||||
|
// This flag should be used as a default flag when no other flags listed below are required.
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
// This flag should be used when synchronizing among multiple processes by
|
||||||
|
// placing the @addr passed to os_sync_wait_on_address and its variants
|
||||||
|
// in a shared memory region.
|
||||||
|
//
|
||||||
|
// When using this flag, it is important to pass OS_SYNC_WAKE_BY_ADDRESS_SHARED
|
||||||
|
// flag along with the exact same @addr to os_sync_wake_by_address_any and
|
||||||
|
// its variants to correctly find and wake up blocked waiters on the @addr.
|
||||||
|
//
|
||||||
|
// This flag should not be used when synchronizing among multiple threads of
|
||||||
|
// a single process. It allows the kernel to perform performance optimizations
|
||||||
|
// as the @addr is local to the calling process.
|
||||||
|
SHARED,
|
||||||
|
}
|
||||||
|
|
||||||
|
os_sync_wait_on_address_flags :: bit_set[os_sync_wait_on_address_flag; u32]
|
||||||
|
|
||||||
|
os_sync_wake_by_address_flag :: enum u32 {
|
||||||
|
// This flag should be used as a default flag when no other flags listed below are required.
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
// This flag should be used when synchronizing among multiple processes by
|
||||||
|
// placing the @addr passed to os_sync_wake_by_address_any and its variants
|
||||||
|
// in a shared memory region.
|
||||||
|
//
|
||||||
|
// When using this flag, it is important to pass OS_SYNC_WAIT_ON_ADDRESS_SHARED
|
||||||
|
// flag along with the exact same @addr to os_sync_wait_on_address and
|
||||||
|
// its variants to correctly find and wake up blocked waiters on the @addr.
|
||||||
|
//
|
||||||
|
// This flag should not be used when synchronizing among multiple threads of
|
||||||
|
// a single process. It allows the kernel to perform performance optimizations
|
||||||
|
// as the @addr is local the calling process.
|
||||||
|
SHARED,
|
||||||
|
}
|
||||||
|
|
||||||
|
os_sync_wake_by_address_flags :: bit_set[os_sync_wake_by_address_flag; u32]
|
||||||
|
|
||||||
|
os_clockid :: enum u32 {
|
||||||
|
MACH_ABSOLUTE_TIME = 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign system {
|
||||||
|
// This function provides an atomic compare-and-wait functionality that
|
||||||
|
// can be used to implement other higher level synchronization primitives.
|
||||||
|
//
|
||||||
|
// It reads a value from @addr, compares it to expected @value and blocks
|
||||||
|
// the calling thread if they are equal. This sequence of operations is
|
||||||
|
// done atomically with respect to other concurrent operations that can
|
||||||
|
// be performed on this @addr by other threads using this same function
|
||||||
|
// or os_sync_wake_by_addr variants. At this point, the blocked calling
|
||||||
|
// thread is considered to be a waiter on this @addr, waiting to be woken
|
||||||
|
// up by a call to os_sync_wake_by_addr variants. If the value at @addr
|
||||||
|
// turns out to be different than expected, the calling thread returns
|
||||||
|
// immediately without blocking.
|
||||||
|
//
|
||||||
|
// This function is expected to be used for implementing synchronization
|
||||||
|
// primitives that do not have a sense of ownership (e.g. condition
|
||||||
|
// variables, semaphores) as it does not provide priority inversion avoidance.
|
||||||
|
// For locking primitives, it is recommended that you use existing OS
|
||||||
|
// primitives such as os_unfair_lock API family / pthread mutex or
|
||||||
|
// std::mutex.
|
||||||
|
//
|
||||||
|
// @param addr
|
||||||
|
// The userspace address to be used for atomic compare-and-wait.
|
||||||
|
// This address must be aligned to @size.
|
||||||
|
//
|
||||||
|
// @param value
|
||||||
|
// The value expected at @addr.
|
||||||
|
//
|
||||||
|
// @param size
|
||||||
|
// The size of @value, in bytes. This can be either 4 or 8 today.
|
||||||
|
// For @value of @size 4 bytes, the upper 4 bytes of @value are ignored.
|
||||||
|
//
|
||||||
|
// @param flags
|
||||||
|
// Flags to alter behavior of os_sync_wait_on_address.
|
||||||
|
// See os_sync_wait_on_address_flags_t.
|
||||||
|
//
|
||||||
|
// @return
|
||||||
|
// If the calling thread is woken up by a call to os_sync_wake_by_addr
|
||||||
|
// variants or the value at @addr is different than expected, this function
|
||||||
|
// returns successfully and the return value indicates the number
|
||||||
|
// of outstanding waiters blocked on this address.
|
||||||
|
// In the event of an error, returns -1 with errno set to indicate the error.
|
||||||
|
//
|
||||||
|
// EINVAL : Invalid flags or size.
|
||||||
|
// EINVAL : The @addr passed is NULL or misaligned.
|
||||||
|
// EINVAL : The operation associated with existing kernel state
|
||||||
|
// at this @addr is inconsistent with what the caller
|
||||||
|
// has requested.
|
||||||
|
// It is important to make sure consistent values are
|
||||||
|
// passed across wait and wake APIs for @addr, @size
|
||||||
|
// and the shared memory specification
|
||||||
|
// (See os_sync_wait_on_address_flags_t).
|
||||||
|
//
|
||||||
|
// It is possible for the os_sync_wait_on_address and its variants to perform
|
||||||
|
// an early return in the event of following errors where user may want to
|
||||||
|
// re-try the wait operation. E.g. low memory conditions could cause such early
|
||||||
|
// return.
|
||||||
|
// It is important to read the current value at the @addr before re-trying
|
||||||
|
// to ensure that the new value still requires waiting on @addr.
|
||||||
|
//
|
||||||
|
// ENOMEM : Unable to allocate memory for kernel internal data
|
||||||
|
// structures.
|
||||||
|
// EINTR : The syscall was interrupted / spurious wake up.
|
||||||
|
// EFAULT : Unable to read value from the @addr. Kernel copyin failed.
|
||||||
|
// It is possible to receive EFAULT error in following cases:
|
||||||
|
// 1. The @addr is an invalid address. This is a programmer error.
|
||||||
|
// 2. The @addr is valid; but, this is a transient error such as
|
||||||
|
// due to low memory conditions. User may want to re-try the wait
|
||||||
|
// operation.
|
||||||
|
// Following code snippet illustrates a possible re-try loop.
|
||||||
|
// <code>
|
||||||
|
// retry:
|
||||||
|
// current = atomic_load_explicit(addr, memory_order_relaxed);
|
||||||
|
// if (current != expected) {
|
||||||
|
// int ret = os_sync_wait_on_address(addr, current, size, flags);
|
||||||
|
// if ((ret < 0) && ((errno == EINTR) || (errno == EFAULT))) {
|
||||||
|
// goto retry;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// </code>
|
||||||
|
os_sync_wait_on_address :: proc(
|
||||||
|
addr: rawptr,
|
||||||
|
value: u64,
|
||||||
|
size: uint,
|
||||||
|
flags: os_sync_wait_on_address_flags,
|
||||||
|
) -> i32 ---
|
||||||
|
|
||||||
|
// This function is a variant of os_sync_wait_on_address that
|
||||||
|
// allows the calling thread to specify a deadline
|
||||||
|
// until which it is willing to block.
|
||||||
|
//
|
||||||
|
// @param addr
|
||||||
|
// The userspace address to be used for atomic compare-and-wait.
|
||||||
|
// This address must be aligned to @size.
|
||||||
|
//
|
||||||
|
// @param value
|
||||||
|
// The value expected at @addr.
|
||||||
|
//
|
||||||
|
// @param size
|
||||||
|
// The size of @value, in bytes. This can be either 4 or 8 today.
|
||||||
|
// For @value of @size 4 bytes, the upper 4 bytes of @value are ignored.
|
||||||
|
//
|
||||||
|
// @param flags
|
||||||
|
// Flags to alter behavior of os_sync_wait_on_address_with_deadline.
|
||||||
|
// See os_sync_wait_on_address_flags_t.
|
||||||
|
//
|
||||||
|
// @param clockid
|
||||||
|
// This value anchors @deadline argument to a specific clock id.
|
||||||
|
// See os_clockid_t.
|
||||||
|
//
|
||||||
|
// @param deadline
|
||||||
|
// This value is used to specify a deadline until which the calling
|
||||||
|
// thread is willing to block.
|
||||||
|
// Passing zero for the @deadline results in an error being returned.
|
||||||
|
// It is recommended to use os_sync_wait_on_address API to block
|
||||||
|
// indefinitely until woken up by a call to os_sync_wake_by_address_any
|
||||||
|
// or os_sync_wake_by_address_all APIs.
|
||||||
|
//
|
||||||
|
// @return
|
||||||
|
// If the calling thread is woken up by a call to os_sync_wake_by_addr
|
||||||
|
// variants or the value at @addr is different than expected, this function
|
||||||
|
// returns successfully and the return value indicates the number
|
||||||
|
// of outstanding waiters blocked on this address.
|
||||||
|
// In the event of an error, returns -1 with errno set to indicate the error.
|
||||||
|
//
|
||||||
|
// In addition to errors returned by os_sync_wait_on_address, this function
|
||||||
|
// can return the following additional error codes.
|
||||||
|
//
|
||||||
|
// EINVAL : Invalid clock id.
|
||||||
|
// EINVAL : The @deadline passed is 0.
|
||||||
|
// ETIMEDOUT : Deadline expired.
|
||||||
|
os_sync_wait_on_address_with_deadline :: proc(
|
||||||
|
addr: rawptr,
|
||||||
|
value: u64,
|
||||||
|
size: uint,
|
||||||
|
flags: os_sync_wait_on_address_flags,
|
||||||
|
clockid: os_clockid,
|
||||||
|
deadline: u64,
|
||||||
|
) -> i32 ---
|
||||||
|
|
||||||
|
// This function is a variant of os_sync_wait_on_address that
|
||||||
|
// allows the calling thread to specify a timeout
|
||||||
|
// until which it is willing to block.
|
||||||
|
//
|
||||||
|
// @param addr
|
||||||
|
// The userspace address to be used for atomic compare-and-wait.
|
||||||
|
// This address must be aligned to @size.
|
||||||
|
//
|
||||||
|
// @param value
|
||||||
|
// The value expected at @addr.
|
||||||
|
//
|
||||||
|
// @param size
|
||||||
|
// The size of @value, in bytes. This can be either 4 or 8 today.
|
||||||
|
// For @value of @size 4 bytes, the upper 4 bytes of @value are ignored.
|
||||||
|
//
|
||||||
|
// @param flags
|
||||||
|
// Flags to alter behavior of os_sync_wait_on_address_with_timeout.
|
||||||
|
// See os_sync_wait_on_address_flags_t.
|
||||||
|
//
|
||||||
|
// @param clockid
|
||||||
|
// This value anchors @timeout_ns argument to a specific clock id.
|
||||||
|
// See os_clockid_t.
|
||||||
|
//
|
||||||
|
// @param timeout_ns
|
||||||
|
// This value is used to specify a timeout in nanoseconds until which
|
||||||
|
// the calling thread is willing to block.
|
||||||
|
// Passing zero for the @timeout_ns results in an error being returned.
|
||||||
|
// It is recommended to use os_sync_wait_on_address API to block
|
||||||
|
// indefinitely until woken up by a call to os_sync_wake_by_address_any
|
||||||
|
// or os_sync_wake_by_address_all APIs.
|
||||||
|
//
|
||||||
|
// @return
|
||||||
|
// If the calling thread is woken up by a call to os_sync_wake_by_address
|
||||||
|
// variants or the value at @addr is different than expected, this function
|
||||||
|
// returns successfully and the return value indicates the number
|
||||||
|
// of outstanding waiters blocked on this address.
|
||||||
|
// In the event of an error, returns -1 with errno set to indicate the error.
|
||||||
|
//
|
||||||
|
// In addition to errors returned by os_sync_wait_on_address, this function
|
||||||
|
// can return the following additional error codes.
|
||||||
|
//
|
||||||
|
// EINVAL : Invalid clock id.
|
||||||
|
// EINVAL : The @timeout_ns passed is 0.
|
||||||
|
// ETIMEDOUT : Timeout expired.
|
||||||
|
os_sync_wait_on_address_with_timeout :: proc(
|
||||||
|
addr: rawptr,
|
||||||
|
value: u64,
|
||||||
|
size: uint,
|
||||||
|
flags: os_sync_wait_on_address_flags,
|
||||||
|
clockid: os_clockid,
|
||||||
|
timeout_ns: u64,
|
||||||
|
) -> i32 ---
|
||||||
|
|
||||||
|
// This function wakes up one waiter out of all those blocked in os_sync_wait_on_address
|
||||||
|
// or its variants on the @addr. No guarantee is provided about which
|
||||||
|
// specific waiter is woken up.
|
||||||
|
//
|
||||||
|
// @param addr
|
||||||
|
// The userspace address to be used for waking up the blocked waiter.
|
||||||
|
// It should be same as what is passed to os_sync_wait_on_address or its variants.
|
||||||
|
//
|
||||||
|
// @param size
|
||||||
|
// The size of lock value, in bytes. This can be either 4 or 8 today.
|
||||||
|
// It should be same as what is passed to os_sync_wait_on_address or its variants.
|
||||||
|
//
|
||||||
|
// @param flags
|
||||||
|
// Flags to alter behavior of os_sync_wake_by_address_any.
|
||||||
|
// See os_sync_wake_by_address_flags_t.
|
||||||
|
//
|
||||||
|
// @return
|
||||||
|
// Returns 0 on success.
|
||||||
|
// In the event of an error, returns -1 with errno set to indicate the error.
|
||||||
|
//
|
||||||
|
// EINVAL : Invalid flags or size.
|
||||||
|
// EINVAL : The @addr passed is NULL.
|
||||||
|
// EINVAL : The operation associated with existing kernel state
|
||||||
|
// at this @addr is inconsistent with what caller
|
||||||
|
// has requested.
|
||||||
|
// It is important to make sure consistent values are
|
||||||
|
// passed across wait and wake APIs for @addr, @size
|
||||||
|
// and the shared memory specification
|
||||||
|
// (See os_sync_wake_by_address_flags_t).
|
||||||
|
// ENOENT : No waiter(s) found waiting on the @addr.
|
||||||
|
os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 ---
|
||||||
|
|
||||||
|
// This function is a variant of os_sync_wake_by_address_any that wakes up all waiters
|
||||||
|
// blocked in os_sync_wait_on_address or its variants.
|
||||||
|
//
|
||||||
|
// @param addr
|
||||||
|
// The userspace address to be used for waking up the blocked waiters.
|
||||||
|
// It should be same as what is passed to os_sync_wait_on_address or its variants.
|
||||||
|
//
|
||||||
|
// @param size
|
||||||
|
// The size of lock value, in bytes. This can be either 4 or 8 today.
|
||||||
|
// It should be same as what is passed to os_sync_wait_on_address or its variants.
|
||||||
|
//
|
||||||
|
// @param flags
|
||||||
|
// Flags to alter behavior of os_sync_wake_by_address_all.
|
||||||
|
// See os_sync_wake_by_address_flags_t.
|
||||||
|
//
|
||||||
|
// @return
|
||||||
|
// Returns 0 on success.
|
||||||
|
// In the event of an error, returns -1 with errno set to indicate the error.
|
||||||
|
//
|
||||||
|
// This function returns same error codes as returned by os_sync_wait_on_address.
|
||||||
|
os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 ---
|
||||||
|
}
|
||||||
+2
-1
@@ -238,8 +238,9 @@ time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
|
|||||||
//
|
//
|
||||||
// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case.
|
// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case.
|
||||||
// On Windows it depends but is comparable with regular sleep in the worst case.
|
// On Windows it depends but is comparable with regular sleep in the worst case.
|
||||||
// To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to
|
// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to
|
||||||
// tell Windows to use a more accurate timer for your process.
|
// tell Windows to use a more accurate timer for your process.
|
||||||
|
// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`.
|
||||||
accurate_sleep :: proc "contextless" (d: Duration) {
|
accurate_sleep :: proc "contextless" (d: Duration) {
|
||||||
to_sleep, estimate, mean, m2, count: Duration
|
to_sleep, estimate, mean, m2, count: Duration
|
||||||
|
|
||||||
|
|||||||
@@ -436,7 +436,9 @@ struct BuildContext {
|
|||||||
BlockingMutex target_features_mutex;
|
BlockingMutex target_features_mutex;
|
||||||
StringSet target_features_set;
|
StringSet target_features_set;
|
||||||
String target_features_string;
|
String target_features_string;
|
||||||
|
|
||||||
String minimum_os_version_string;
|
String minimum_os_version_string;
|
||||||
|
bool minimum_os_version_string_given;
|
||||||
};
|
};
|
||||||
|
|
||||||
gb_global BuildContext build_context = {0};
|
gb_global BuildContext build_context = {0};
|
||||||
@@ -1419,7 +1421,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
|
|||||||
|
|
||||||
bc->metrics = *metrics;
|
bc->metrics = *metrics;
|
||||||
if (metrics->os == TargetOs_darwin) {
|
if (metrics->os == TargetOs_darwin) {
|
||||||
if (bc->minimum_os_version_string.len == 0) {
|
if (!bc->minimum_os_version_string_given) {
|
||||||
bc->minimum_os_version_string = str_lit("11.0.0");
|
bc->minimum_os_version_string = str_lit("11.0.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr);
|
|||||||
|
|
||||||
gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array<Operand> const &ordered_operands);
|
gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array<Operand> const &ordered_operands);
|
||||||
|
|
||||||
|
gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish);
|
||||||
|
|
||||||
enum LoadDirectiveResult {
|
enum LoadDirectiveResult {
|
||||||
LoadDirective_Success = 0,
|
LoadDirective_Success = 0,
|
||||||
LoadDirective_Error = 1,
|
LoadDirective_Error = 1,
|
||||||
@@ -5031,6 +5033,9 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (operand->type && is_type_soa_struct(type_deref(operand->type))) {
|
||||||
|
complete_soa_type(c->checker, type_deref(operand->type), false);
|
||||||
|
}
|
||||||
|
|
||||||
if (entity == nullptr && selector->kind == Ast_Ident) {
|
if (entity == nullptr && selector->kind == Ast_Ident) {
|
||||||
String field_name = selector->Ident.token.string;
|
String field_name = selector->Ident.token.string;
|
||||||
|
|||||||
+174
-55
@@ -2683,6 +2683,118 @@ type_assign:;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SoaTypeWorkerData {
|
||||||
|
CheckerContext ctx;
|
||||||
|
Type * type;
|
||||||
|
bool wait_to_finish;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish) {
|
||||||
|
Type *original_type = t;
|
||||||
|
gb_unused(original_type);
|
||||||
|
|
||||||
|
t = base_type(t);
|
||||||
|
if (t == nullptr || !is_type_soa_struct(t)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MUTEX_GUARD(&t->Struct.soa_mutex);
|
||||||
|
|
||||||
|
if (t->Struct.fields_wait_signal.futex.load()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isize field_count = 0;
|
||||||
|
i32 extra_field_count = 0;
|
||||||
|
switch (t->Struct.soa_kind) {
|
||||||
|
case StructSoa_Fixed: extra_field_count = 0; break;
|
||||||
|
case StructSoa_Slice: extra_field_count = 1; break;
|
||||||
|
case StructSoa_Dynamic: extra_field_count = 3; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope *scope = t->Struct.scope;
|
||||||
|
i64 soa_count = t->Struct.soa_count;
|
||||||
|
Type *elem = t->Struct.soa_elem;
|
||||||
|
Type *old_struct = base_type(elem);
|
||||||
|
GB_ASSERT(old_struct->kind == Type_Struct);
|
||||||
|
|
||||||
|
if (wait_to_finish) {
|
||||||
|
wait_signal_until_available(&old_struct->Struct.fields_wait_signal);
|
||||||
|
} else {
|
||||||
|
GB_ASSERT(old_struct->Struct.fields_wait_signal.futex.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
field_count = old_struct->Struct.fields.count;
|
||||||
|
|
||||||
|
t->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
||||||
|
t->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
||||||
|
|
||||||
|
|
||||||
|
auto const &add_entity = [](Scope *scope, Entity *entity) {
|
||||||
|
String name = entity->token.string;
|
||||||
|
if (!is_blank_ident(name)) {
|
||||||
|
Entity *ie = scope_insert(scope, entity);
|
||||||
|
if (ie != nullptr) {
|
||||||
|
redeclaration_error(name, entity, ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
for_array(i, old_struct->Struct.fields) {
|
||||||
|
Entity *old_field = old_struct->Struct.fields[i];
|
||||||
|
if (old_field->kind == Entity_Variable) {
|
||||||
|
Type *field_type = nullptr;
|
||||||
|
if (t->Struct.soa_kind == StructSoa_Fixed) {
|
||||||
|
GB_ASSERT(soa_count >= 0);
|
||||||
|
field_type = alloc_type_array(old_field->type, soa_count);
|
||||||
|
} else {
|
||||||
|
field_type = alloc_type_pointer(old_field->type);
|
||||||
|
}
|
||||||
|
Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index);
|
||||||
|
t->Struct.fields[i] = new_field;
|
||||||
|
add_entity(scope, new_field);
|
||||||
|
new_field->flags |= EntityFlag_Used;
|
||||||
|
} else {
|
||||||
|
t->Struct.fields[i] = old_field;
|
||||||
|
}
|
||||||
|
|
||||||
|
t->Struct.tags[i] = old_struct->Struct.tags[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->Struct.soa_kind != StructSoa_Fixed) {
|
||||||
|
Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0);
|
||||||
|
t->Struct.fields[field_count+0] = len_field;
|
||||||
|
add_entity(scope, len_field);
|
||||||
|
len_field->flags |= EntityFlag_Used;
|
||||||
|
|
||||||
|
if (t->Struct.soa_kind == StructSoa_Dynamic) {
|
||||||
|
Entity *cap_field = alloc_entity_field(scope, make_token_ident("__$cap"), t_int, false, cast(i32)field_count+1);
|
||||||
|
t->Struct.fields[field_count+1] = cap_field;
|
||||||
|
add_entity(scope, cap_field);
|
||||||
|
cap_field->flags |= EntityFlag_Used;
|
||||||
|
|
||||||
|
init_mem_allocator(checker);
|
||||||
|
Entity *allocator_field = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, cast(i32)field_count+2);
|
||||||
|
t->Struct.fields[field_count+2] = allocator_field;
|
||||||
|
add_entity(scope, allocator_field);
|
||||||
|
allocator_field->flags |= EntityFlag_Used;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add_type_info_type(ctx, original_type);
|
||||||
|
|
||||||
|
wait_signal_set(&t->Struct.fields_wait_signal);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
gb_internal WORKER_TASK_PROC(complete_soa_type_worker) {
|
||||||
|
SoaTypeWorkerData *wd = cast(SoaTypeWorkerData *)data;
|
||||||
|
complete_soa_type(wd->ctx.checker, wd->type, wd->wait_to_finish);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem, i64 count, Type *generic_type, StructSoaKind soa_kind) {
|
gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem, i64 count, Type *generic_type, StructSoaKind soa_kind) {
|
||||||
@@ -2697,8 +2809,9 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e
|
|||||||
return alloc_type_array(elem, count, generic_type);
|
return alloc_type_array(elem, count, generic_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type *soa_struct = nullptr;
|
Type * soa_struct = nullptr;
|
||||||
Scope *scope = nullptr;
|
Scope *scope = nullptr;
|
||||||
|
bool is_complete = false;
|
||||||
|
|
||||||
isize field_count = 0;
|
isize field_count = 0;
|
||||||
i32 extra_field_count = 0;
|
i32 extra_field_count = 0;
|
||||||
@@ -2707,39 +2820,43 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e
|
|||||||
case StructSoa_Slice: extra_field_count = 1; break;
|
case StructSoa_Slice: extra_field_count = 1; break;
|
||||||
case StructSoa_Dynamic: extra_field_count = 3; break;
|
case StructSoa_Dynamic: extra_field_count = 3; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
soa_struct = alloc_type_struct();
|
||||||
|
soa_struct->Struct.soa_kind = soa_kind;
|
||||||
|
soa_struct->Struct.soa_elem = elem;
|
||||||
|
soa_struct->Struct.is_polymorphic = is_polymorphic;
|
||||||
|
soa_struct->Struct.node = array_typ_expr;
|
||||||
|
|
||||||
|
if (count > I32_MAX) {
|
||||||
|
count = I32_MAX;
|
||||||
|
error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count);
|
||||||
|
}
|
||||||
|
soa_struct->Struct.soa_count = cast(i32)count;
|
||||||
|
|
||||||
|
scope = create_scope(ctx->info, ctx->scope);
|
||||||
|
soa_struct->Struct.scope = scope;
|
||||||
|
|
||||||
|
if (elem && elem->kind == Type_Named) {
|
||||||
|
add_declaration_dependency(ctx, elem->Named.type_name);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_polymorphic) {
|
if (is_polymorphic) {
|
||||||
field_count = 0;
|
field_count = 0;
|
||||||
|
|
||||||
soa_struct = alloc_type_struct();
|
|
||||||
soa_struct->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
soa_struct->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
||||||
soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
||||||
soa_struct->Struct.node = array_typ_expr;
|
|
||||||
soa_struct->Struct.soa_kind = soa_kind;
|
|
||||||
soa_struct->Struct.soa_elem = elem;
|
|
||||||
soa_struct->Struct.soa_count = 0;
|
soa_struct->Struct.soa_count = 0;
|
||||||
soa_struct->Struct.is_polymorphic = true;
|
|
||||||
|
|
||||||
scope = create_scope(ctx->info, ctx->scope);
|
is_complete = true;
|
||||||
soa_struct->Struct.scope = scope;
|
|
||||||
} else if (is_type_array(elem)) {
|
} else if (is_type_array(elem)) {
|
||||||
Type *old_array = base_type(elem);
|
Type *old_array = base_type(elem);
|
||||||
field_count = cast(isize)old_array->Array.count;
|
field_count = cast(isize)old_array->Array.count;
|
||||||
|
|
||||||
soa_struct = alloc_type_struct();
|
|
||||||
soa_struct->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
soa_struct->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
||||||
soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
||||||
soa_struct->Struct.node = array_typ_expr;
|
|
||||||
soa_struct->Struct.soa_kind = soa_kind;
|
|
||||||
soa_struct->Struct.soa_elem = elem;
|
|
||||||
if (count > I32_MAX) {
|
|
||||||
count = I32_MAX;
|
|
||||||
error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count);
|
|
||||||
}
|
|
||||||
soa_struct->Struct.soa_count = cast(i32)count;
|
|
||||||
|
|
||||||
scope = create_scope(ctx->info, ctx->scope);
|
|
||||||
string_map_init(&scope->elements, 8);
|
string_map_init(&scope->elements, 8);
|
||||||
soa_struct->Struct.scope = scope;
|
|
||||||
|
|
||||||
String params_xyzw[4] = {
|
String params_xyzw[4] = {
|
||||||
str_lit("x"),
|
str_lit("x"),
|
||||||
@@ -2765,52 +2882,44 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e
|
|||||||
add_entity_use(ctx, nullptr, new_field);
|
add_entity_use(ctx, nullptr, new_field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_complete = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
GB_ASSERT(is_type_struct(elem));
|
GB_ASSERT(is_type_struct(elem));
|
||||||
|
|
||||||
Type *old_struct = base_type(elem);
|
Type *old_struct = base_type(elem);
|
||||||
|
|
||||||
wait_signal_until_available(&old_struct->Struct.fields_wait_signal);
|
if (old_struct->Struct.fields_wait_signal.futex.load()) {
|
||||||
field_count = old_struct->Struct.fields.count;
|
field_count = old_struct->Struct.fields.count;
|
||||||
|
|
||||||
soa_struct = alloc_type_struct();
|
soa_struct->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
||||||
soa_struct->Struct.fields = slice_make<Entity *>(permanent_allocator(), field_count+extra_field_count);
|
soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
||||||
soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count);
|
|
||||||
soa_struct->Struct.node = array_typ_expr;
|
|
||||||
soa_struct->Struct.soa_kind = soa_kind;
|
|
||||||
soa_struct->Struct.soa_elem = elem;
|
|
||||||
if (count > I32_MAX) {
|
|
||||||
count = I32_MAX;
|
|
||||||
error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count);
|
|
||||||
}
|
|
||||||
soa_struct->Struct.soa_count = cast(i32)count;
|
|
||||||
|
|
||||||
scope = create_scope(ctx->info, old_struct->Struct.scope->parent);
|
for_array(i, old_struct->Struct.fields) {
|
||||||
soa_struct->Struct.scope = scope;
|
Entity *old_field = old_struct->Struct.fields[i];
|
||||||
|
if (old_field->kind == Entity_Variable) {
|
||||||
for_array(i, old_struct->Struct.fields) {
|
Type *field_type = nullptr;
|
||||||
Entity *old_field = old_struct->Struct.fields[i];
|
if (soa_kind == StructSoa_Fixed) {
|
||||||
if (old_field->kind == Entity_Variable) {
|
GB_ASSERT(count >= 0);
|
||||||
Type *field_type = nullptr;
|
field_type = alloc_type_array(old_field->type, count);
|
||||||
if (soa_kind == StructSoa_Fixed) {
|
} else {
|
||||||
GB_ASSERT(count >= 0);
|
field_type = alloc_type_pointer(old_field->type);
|
||||||
field_type = alloc_type_array(old_field->type, count);
|
}
|
||||||
|
Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index);
|
||||||
|
soa_struct->Struct.fields[i] = new_field;
|
||||||
|
add_entity(ctx, scope, nullptr, new_field);
|
||||||
|
add_entity_use(ctx, nullptr, new_field);
|
||||||
} else {
|
} else {
|
||||||
field_type = alloc_type_pointer(old_field->type);
|
soa_struct->Struct.fields[i] = old_field;
|
||||||
}
|
}
|
||||||
Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index);
|
|
||||||
soa_struct->Struct.fields[i] = new_field;
|
|
||||||
add_entity(ctx, scope, nullptr, new_field);
|
|
||||||
add_entity_use(ctx, nullptr, new_field);
|
|
||||||
} else {
|
|
||||||
soa_struct->Struct.fields[i] = old_field;
|
|
||||||
}
|
|
||||||
|
|
||||||
soa_struct->Struct.tags[i] = old_struct->Struct.tags[i];
|
soa_struct->Struct.tags[i] = old_struct->Struct.tags[i];
|
||||||
|
}
|
||||||
|
is_complete = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (soa_kind != StructSoa_Fixed) {
|
if (is_complete && soa_kind != StructSoa_Fixed) {
|
||||||
Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0);
|
Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0);
|
||||||
soa_struct->Struct.fields[field_count+0] = len_field;
|
soa_struct->Struct.fields[field_count+0] = len_field;
|
||||||
add_entity(ctx, scope, nullptr, len_field);
|
add_entity(ctx, scope, nullptr, len_field);
|
||||||
@@ -2835,8 +2944,18 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e
|
|||||||
Entity *base_type_entity = alloc_entity_type_name(scope, token, elem, EntityState_Resolved);
|
Entity *base_type_entity = alloc_entity_type_name(scope, token, elem, EntityState_Resolved);
|
||||||
add_entity(ctx, scope, nullptr, base_type_entity);
|
add_entity(ctx, scope, nullptr, base_type_entity);
|
||||||
|
|
||||||
add_type_info_type(ctx, soa_struct);
|
if (is_complete) {
|
||||||
wait_signal_set(&soa_struct->Struct.fields_wait_signal);
|
add_type_info_type(ctx, soa_struct);
|
||||||
|
wait_signal_set(&soa_struct->Struct.fields_wait_signal);
|
||||||
|
} else {
|
||||||
|
SoaTypeWorkerData *wd = gb_alloc_item(permanent_allocator(), SoaTypeWorkerData);
|
||||||
|
wd->ctx = *ctx;
|
||||||
|
wd->type = soa_struct;
|
||||||
|
wd->wait_to_finish = true;
|
||||||
|
|
||||||
|
mpsc_enqueue(&ctx->checker->soa_types_to_complete, soa_struct);
|
||||||
|
thread_pool_add_task(complete_soa_type_worker, wd);
|
||||||
|
}
|
||||||
|
|
||||||
return soa_struct;
|
return soa_struct;
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-4
@@ -1097,6 +1097,15 @@ gb_internal void init_universal(void) {
|
|||||||
scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name);
|
scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int minimum_os_version = 0;
|
||||||
|
if (build_context.minimum_os_version_string != "") {
|
||||||
|
int major, minor, revision = 0;
|
||||||
|
sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
|
||||||
|
minimum_os_version = (major*10000)+(minor*100)+revision;
|
||||||
|
}
|
||||||
|
add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version));
|
||||||
|
}
|
||||||
|
|
||||||
add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG);
|
add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG);
|
||||||
add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT);
|
add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT);
|
||||||
@@ -1227,9 +1236,9 @@ gb_internal void init_universal(void) {
|
|||||||
|
|
||||||
// intrinsics types for objective-c stuff
|
// intrinsics types for objective-c stuff
|
||||||
{
|
{
|
||||||
t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct());
|
t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete());
|
||||||
t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct());
|
t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete());
|
||||||
t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct());
|
t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete());
|
||||||
|
|
||||||
t_objc_id = alloc_type_pointer(t_objc_object);
|
t_objc_id = alloc_type_pointer(t_objc_object);
|
||||||
t_objc_SEL = alloc_type_pointer(t_objc_selector);
|
t_objc_SEL = alloc_type_pointer(t_objc_selector);
|
||||||
@@ -1371,6 +1380,7 @@ gb_internal void init_checker(Checker *c) {
|
|||||||
array_init(&c->nested_proc_lits, heap_allocator(), 0, 1<<20);
|
array_init(&c->nested_proc_lits, heap_allocator(), 0, 1<<20);
|
||||||
|
|
||||||
mpsc_init(&c->global_untyped_queue, a); // , 1<<20);
|
mpsc_init(&c->global_untyped_queue, a); // , 1<<20);
|
||||||
|
mpsc_init(&c->soa_types_to_complete, a); // , 1<<20);
|
||||||
|
|
||||||
c->builtin_ctx = make_checker_context(c);
|
c->builtin_ctx = make_checker_context(c);
|
||||||
}
|
}
|
||||||
@@ -1383,6 +1393,7 @@ gb_internal void destroy_checker(Checker *c) {
|
|||||||
array_free(&c->nested_proc_lits);
|
array_free(&c->nested_proc_lits);
|
||||||
array_free(&c->procs_to_check);
|
array_free(&c->procs_to_check);
|
||||||
mpsc_destroy(&c->global_untyped_queue);
|
mpsc_destroy(&c->global_untyped_queue);
|
||||||
|
mpsc_destroy(&c->soa_types_to_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1682,6 +1693,26 @@ gb_internal bool add_entity_with_name(CheckerContext *c, Scope *scope, Ast *iden
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gb_internal bool add_entity_with_name(CheckerInfo *info, Scope *scope, Ast *identifier, Entity *entity, String name) {
|
||||||
|
if (scope == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!is_blank_ident(name)) {
|
||||||
|
Entity *ie = scope_insert(scope, entity);
|
||||||
|
if (ie != nullptr) {
|
||||||
|
return redeclaration_error(name, entity, ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (identifier != nullptr) {
|
||||||
|
GB_ASSERT(entity->file != nullptr);
|
||||||
|
add_entity_definition(info, identifier, entity);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
gb_internal bool add_entity(CheckerContext *c, Scope *scope, Ast *identifier, Entity *entity) {
|
gb_internal bool add_entity(CheckerContext *c, Scope *scope, Ast *identifier, Entity *entity) {
|
||||||
return add_entity_with_name(c, scope, identifier, entity, entity->token.string);
|
return add_entity_with_name(c, scope, identifier, entity, entity->token.string);
|
||||||
}
|
}
|
||||||
@@ -2006,6 +2037,8 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Type_Struct:
|
case Type_Struct:
|
||||||
|
if (bt->Struct.fields_wait_signal.futex.load() == 0)
|
||||||
|
return;
|
||||||
if (bt->Struct.scope != nullptr) {
|
if (bt->Struct.scope != nullptr) {
|
||||||
for (auto const &entry : bt->Struct.scope->elements) {
|
for (auto const &entry : bt->Struct.scope->elements) {
|
||||||
Entity *e = entry.value;
|
Entity *e = entry.value;
|
||||||
@@ -2026,7 +2059,9 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
|
|||||||
add_type_info_type_internal(c, bt->Struct.polymorphic_params);
|
add_type_info_type_internal(c, bt->Struct.polymorphic_params);
|
||||||
for_array(i, bt->Struct.fields) {
|
for_array(i, bt->Struct.fields) {
|
||||||
Entity *f = bt->Struct.fields[i];
|
Entity *f = bt->Struct.fields[i];
|
||||||
add_type_info_type_internal(c, f->type);
|
if (f && f->type) {
|
||||||
|
add_type_info_type_internal(c, f->type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
add_comparison_procedures_for_fields(c, bt);
|
add_comparison_procedures_for_fields(c, bt);
|
||||||
break;
|
break;
|
||||||
@@ -4440,6 +4475,10 @@ gb_internal void check_all_global_entities(Checker *c) {
|
|||||||
DeclInfo *d = e->decl_info;
|
DeclInfo *d = e->decl_info;
|
||||||
check_single_global_entity(c, e, d);
|
check_single_global_entity(c, e, d);
|
||||||
if (e->type != nullptr && is_type_typed(e->type)) {
|
if (e->type != nullptr && is_type_typed(e->type)) {
|
||||||
|
for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) {
|
||||||
|
complete_soa_type(c, t, false);
|
||||||
|
}
|
||||||
|
|
||||||
(void)type_size_of(e->type);
|
(void)type_size_of(e->type);
|
||||||
(void)type_align_of(e->type);
|
(void)type_align_of(e->type);
|
||||||
}
|
}
|
||||||
@@ -6108,6 +6147,9 @@ gb_internal void check_add_definitions_from_queues(Checker *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gb_internal void check_merge_queues_into_arrays(Checker *c) {
|
gb_internal void check_merge_queues_into_arrays(Checker *c) {
|
||||||
|
for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) {
|
||||||
|
complete_soa_type(c, t, false);
|
||||||
|
}
|
||||||
check_add_entities_from_queues(c);
|
check_add_entities_from_queues(c);
|
||||||
check_add_definitions_from_queues(c);
|
check_add_definitions_from_queues(c);
|
||||||
}
|
}
|
||||||
@@ -6318,6 +6360,8 @@ gb_internal void check_parsed_files(Checker *c) {
|
|||||||
TIME_SECTION("check bodies have all been checked");
|
TIME_SECTION("check bodies have all been checked");
|
||||||
check_unchecked_bodies(c);
|
check_unchecked_bodies(c);
|
||||||
|
|
||||||
|
TIME_SECTION("check #soa types");
|
||||||
|
|
||||||
check_merge_queues_into_arrays(c);
|
check_merge_queues_into_arrays(c);
|
||||||
thread_pool_wait();
|
thread_pool_wait();
|
||||||
|
|
||||||
|
|||||||
@@ -500,6 +500,7 @@ struct Checker {
|
|||||||
|
|
||||||
|
|
||||||
MPSCQueue<UntypedExprInfo> global_untyped_queue;
|
MPSCQueue<UntypedExprInfo> global_untyped_queue;
|
||||||
|
MPSCQueue<Type *> soa_types_to_complete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+8
-3
@@ -502,11 +502,16 @@ gb_internal i32 linker_stage(LinkerData *gen) {
|
|||||||
platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib ");
|
platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build_context.minimum_os_version_string.len) {
|
// Only specify this flag if the user has given a minimum version to target.
|
||||||
|
// This will cause warnings to show up for mismatched libraries.
|
||||||
|
if (build_context.minimum_os_version_string_given) {
|
||||||
link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string));
|
link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string));
|
||||||
}
|
}
|
||||||
// This points the linker to where the entry point is
|
|
||||||
link_settings = gb_string_appendc(link_settings, "-e _main ");
|
if (build_context.build_mode != BuildMode_DynamicLibrary) {
|
||||||
|
// This points the linker to where the entry point is
|
||||||
|
link_settings = gb_string_appendc(link_settings, "-e _main ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!build_context.no_crt) {
|
if (!build_context.no_crt) {
|
||||||
|
|||||||
+1
-1
@@ -1343,7 +1343,7 @@ namespace lbAbiWasm {
|
|||||||
// ignore padding
|
// ignore padding
|
||||||
LLVMStructGetTypeAtIndex(type, 2)
|
LLVMStructGetTypeAtIndex(type, 2)
|
||||||
};
|
};
|
||||||
LLVMTypeRef new_type = LLVMStructTypeInContext(c, types, gb_count_of(types), true);
|
LLVMTypeRef new_type = LLVMStructTypeInContext(c, types, gb_count_of(types), false);
|
||||||
return lb_arg_type_direct(type, new_type, nullptr, nullptr);
|
return lb_arg_type_direct(type, new_type, nullptr, nullptr);
|
||||||
} else {
|
} else {
|
||||||
return is_struct(c, type, calling_convention);
|
return is_struct(c, type, calling_convention);
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ gb_internal LLVMTypeRef *lb_setup_modified_types_for_type_info(lbModule *m, isiz
|
|||||||
stypes[1] = lb_type(m, tibt->Struct.fields[1]->type);
|
stypes[1] = lb_type(m, tibt->Struct.fields[1]->type);
|
||||||
stypes[2] = lb_type(m, tibt->Struct.fields[2]->type);
|
stypes[2] = lb_type(m, tibt->Struct.fields[2]->type);
|
||||||
isize variant_index = 0;
|
isize variant_index = 0;
|
||||||
if (build_context.int_size == 8) {
|
if (build_context.ptr_size == 8) {
|
||||||
stypes[3] = lb_type(m, t_i32); // padding
|
stypes[3] = lb_type(m, t_i32); // padding
|
||||||
stypes[4] = lb_type(m, tibt->Struct.fields[3]->type);
|
stypes[4] = lb_type(m, tibt->Struct.fields[3]->type);
|
||||||
variant_index = 5;
|
variant_index = 5;
|
||||||
@@ -385,7 +385,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
|
|||||||
small_const_values[2] = type_info_flags.value;
|
small_const_values[2] = type_info_flags.value;
|
||||||
|
|
||||||
unsigned variant_index = 0;
|
unsigned variant_index = 0;
|
||||||
if (build_context.int_size == 8) {
|
if (build_context.ptr_size == 8) {
|
||||||
small_const_values[3] = LLVMConstNull(LLVMStructGetTypeAtIndex(stype, 3));
|
small_const_values[3] = LLVMConstNull(LLVMStructGetTypeAtIndex(stype, 3));
|
||||||
small_const_values[4] = id.value;
|
small_const_values[4] = id.value;
|
||||||
variant_index = 5;
|
variant_index = 5;
|
||||||
@@ -860,7 +860,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
|
|||||||
Entity *f = t->Struct.fields[source_index];
|
Entity *f = t->Struct.fields[source_index];
|
||||||
i64 foffset = 0;
|
i64 foffset = 0;
|
||||||
if (!t->Struct.is_raw_union) {
|
if (!t->Struct.is_raw_union) {
|
||||||
GB_ASSERT(t->Struct.offsets != nullptr);
|
GB_ASSERT_MSG(t->Struct.offsets != nullptr, "%s", type_to_string(t));
|
||||||
GB_ASSERT(0 <= f->Variable.field_index && f->Variable.field_index < count);
|
GB_ASSERT(0 <= f->Variable.field_index && f->Variable.field_index < count);
|
||||||
foffset = t->Struct.offsets[source_index];
|
foffset = t->Struct.offsets[source_index];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ gb_internal lbValue lb_emit_select(lbProcedure *p, lbValue cond, lbValue x, lbVa
|
|||||||
gb_internal lbValue lb_emit_min(lbProcedure *p, Type *t, lbValue x, lbValue y) {
|
gb_internal lbValue lb_emit_min(lbProcedure *p, Type *t, lbValue x, lbValue y) {
|
||||||
x = lb_emit_conv(p, x, t);
|
x = lb_emit_conv(p, x, t);
|
||||||
y = lb_emit_conv(p, y, t);
|
y = lb_emit_conv(p, y, t);
|
||||||
bool use_llvm_intrinsic = is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t)));
|
bool use_llvm_intrinsic = !is_arch_wasm() && (is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t))));
|
||||||
if (use_llvm_intrinsic) {
|
if (use_llvm_intrinsic) {
|
||||||
LLVMValueRef args[2] = {x.value, y.value};
|
LLVMValueRef args[2] = {x.value, y.value};
|
||||||
LLVMTypeRef types[1] = {lb_type(p->module, t)};
|
LLVMTypeRef types[1] = {lb_type(p->module, t)};
|
||||||
@@ -140,7 +140,7 @@ gb_internal lbValue lb_emit_min(lbProcedure *p, Type *t, lbValue x, lbValue y) {
|
|||||||
gb_internal lbValue lb_emit_max(lbProcedure *p, Type *t, lbValue x, lbValue y) {
|
gb_internal lbValue lb_emit_max(lbProcedure *p, Type *t, lbValue x, lbValue y) {
|
||||||
x = lb_emit_conv(p, x, t);
|
x = lb_emit_conv(p, x, t);
|
||||||
y = lb_emit_conv(p, y, t);
|
y = lb_emit_conv(p, y, t);
|
||||||
bool use_llvm_intrinsic = is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t)));
|
bool use_llvm_intrinsic = !is_arch_wasm() && (is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t))));
|
||||||
if (use_llvm_intrinsic) {
|
if (use_llvm_intrinsic) {
|
||||||
LLVMValueRef args[2] = {x.value, y.value};
|
LLVMValueRef args[2] = {x.value, y.value};
|
||||||
LLVMTypeRef types[1] = {lb_type(p->module, t)};
|
LLVMTypeRef types[1] = {lb_type(p->module, t)};
|
||||||
|
|||||||
+2
-1
@@ -1066,6 +1066,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
|||||||
case BuildFlag_MinimumOSVersion: {
|
case BuildFlag_MinimumOSVersion: {
|
||||||
GB_ASSERT(value.kind == ExactValue_String);
|
GB_ASSERT(value.kind == ExactValue_String);
|
||||||
build_context.minimum_os_version_string = value.value_string;
|
build_context.minimum_os_version_string = value.value_string;
|
||||||
|
build_context.minimum_os_version_string_given = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BuildFlag_RelocMode: {
|
case BuildFlag_RelocMode: {
|
||||||
@@ -1926,7 +1927,7 @@ gb_internal void print_show_help(String const arg0, String const &command) {
|
|||||||
print_usage_line(1, "-minimum-os-version:<string>");
|
print_usage_line(1, "-minimum-os-version:<string>");
|
||||||
print_usage_line(2, "Sets the minimum OS version targeted by the application.");
|
print_usage_line(2, "Sets the minimum OS version targeted by the application.");
|
||||||
print_usage_line(2, "Default: -minimum-os-version:11.0.0");
|
print_usage_line(2, "Default: -minimum-os-version:11.0.0");
|
||||||
print_usage_line(2, "(Only used when target is Darwin.)");
|
print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning.");
|
||||||
print_usage_line(0, "");
|
print_usage_line(0, "");
|
||||||
|
|
||||||
print_usage_line(1, "-extra-linker-flags:<string>");
|
print_usage_line(1, "-extra-linker-flags:<string>");
|
||||||
|
|||||||
@@ -2885,6 +2885,10 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
|
|||||||
elem = parse_expr(f, true);
|
elem = parse_expr(f, true);
|
||||||
f->allow_range = prev_allow_range;
|
f->allow_range = prev_allow_range;
|
||||||
|
|
||||||
|
if (elem == nullptr) {
|
||||||
|
syntax_error(token, "Expected a type or range, got nothing");
|
||||||
|
}
|
||||||
|
|
||||||
if (allow_token(f, Token_Semicolon)) {
|
if (allow_token(f, Token_Semicolon)) {
|
||||||
underlying = parse_type(f);
|
underlying = parse_type(f);
|
||||||
} else if (allow_token(f, Token_Comma)) {
|
} else if (allow_token(f, Token_Comma)) {
|
||||||
@@ -2894,6 +2898,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
|
|||||||
underlying = parse_type(f);
|
underlying = parse_type(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
expect_token(f, Token_CloseBracket);
|
expect_token(f, Token_CloseBracket);
|
||||||
return ast_bit_set_type(f, token, elem, underlying);
|
return ast_bit_set_type(f, token, elem, underlying);
|
||||||
}
|
}
|
||||||
@@ -5009,6 +5014,7 @@ gb_internal Ast *parse_stmt(AstFile *f) {
|
|||||||
case Token_Xor:
|
case Token_Xor:
|
||||||
case Token_Not:
|
case Token_Not:
|
||||||
case Token_And:
|
case Token_And:
|
||||||
|
case Token_Mul: // Used for error handling when people do C-like things
|
||||||
s = parse_simple_stmt(f, StmtAllowFlag_Label);
|
s = parse_simple_stmt(f, StmtAllowFlag_Label);
|
||||||
expect_semicolon(f);
|
expect_semicolon(f);
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@@ -758,6 +758,11 @@ gb_internal void futex_wait(Futex *f, Footex val) {
|
|||||||
|
|
||||||
#elif defined(GB_SYSTEM_OSX)
|
#elif defined(GB_SYSTEM_OSX)
|
||||||
|
|
||||||
|
#if __has_include(<os/os_sync_wait_on_address.h>)
|
||||||
|
#define DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
#include <os/os_sync_wait_on_address.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define UL_COMPARE_AND_WAIT 0x00000001
|
#define UL_COMPARE_AND_WAIT 0x00000001
|
||||||
#define ULF_NO_ERRNO 0x01000000
|
#define ULF_NO_ERRNO 0x01000000
|
||||||
|
|
||||||
@@ -765,6 +770,23 @@ extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint
|
|||||||
extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
|
extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
|
||||||
|
|
||||||
gb_internal void futex_signal(Futex *f) {
|
gb_internal void futex_signal(Futex *f) {
|
||||||
|
#ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
if (__builtin_available(macOS 14.4, *)) {
|
||||||
|
for (;;) {
|
||||||
|
int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE);
|
||||||
|
if (ret >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (errno == EINTR || errno == EFAULT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GB_PANIC("Failed in futex wake %d %d!\n", ret, errno);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0);
|
int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0);
|
||||||
if (ret >= 0) {
|
if (ret >= 0) {
|
||||||
@@ -778,9 +800,29 @@ gb_internal void futex_signal(Futex *f) {
|
|||||||
}
|
}
|
||||||
GB_PANIC("Failed in futex wake!\n");
|
GB_PANIC("Failed in futex wake!\n");
|
||||||
}
|
}
|
||||||
|
#ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gb_internal void futex_broadcast(Futex *f) {
|
gb_internal void futex_broadcast(Futex *f) {
|
||||||
|
#ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
if (__builtin_available(macOS 14.4, *)) {
|
||||||
|
for (;;) {
|
||||||
|
int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE);
|
||||||
|
if (ret >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (errno == EINTR || errno == EFAULT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GB_PANIC("Failed in futext wake %d %d!\n", ret, errno);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
for (;;) {
|
for (;;) {
|
||||||
enum { ULF_WAKE_ALL = 0x00000100 };
|
enum { ULF_WAKE_ALL = 0x00000100 };
|
||||||
int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0);
|
int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0);
|
||||||
@@ -795,9 +837,32 @@ gb_internal void futex_broadcast(Futex *f) {
|
|||||||
}
|
}
|
||||||
GB_PANIC("Failed in futex wake!\n");
|
GB_PANIC("Failed in futex wake!\n");
|
||||||
}
|
}
|
||||||
|
#ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gb_internal void futex_wait(Futex *f, Footex val) {
|
gb_internal void futex_wait(Futex *f, Footex val) {
|
||||||
|
#ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
if (__builtin_available(macOS 14.4, *)) {
|
||||||
|
for (;;) {
|
||||||
|
int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE);
|
||||||
|
if (ret >= 0) {
|
||||||
|
if (*f != val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (errno == EINTR || errno == EFAULT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GB_PANIC("Failed in futex wait %d %d!\n", ret, errno);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0);
|
int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0);
|
||||||
if (ret >= 0) {
|
if (ret >= 0) {
|
||||||
@@ -815,7 +880,11 @@ gb_internal void futex_wait(Futex *f, Footex val) {
|
|||||||
|
|
||||||
GB_PANIC("Failed in futex wait!\n");
|
GB_PANIC("Failed in futex wait!\n");
|
||||||
}
|
}
|
||||||
|
#ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(GB_SYSTEM_WINDOWS)
|
#elif defined(GB_SYSTEM_WINDOWS)
|
||||||
|
|
||||||
gb_internal void futex_signal(Futex *f) {
|
gb_internal void futex_signal(Futex *f) {
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ struct TypeStruct {
|
|||||||
i32 soa_count;
|
i32 soa_count;
|
||||||
StructSoaKind soa_kind;
|
StructSoaKind soa_kind;
|
||||||
Wait_Signal fields_wait_signal;
|
Wait_Signal fields_wait_signal;
|
||||||
|
BlockingMutex soa_mutex;
|
||||||
BlockingMutex offset_mutex; // for settings offsets
|
BlockingMutex offset_mutex; // for settings offsets
|
||||||
|
|
||||||
bool is_polymorphic;
|
bool is_polymorphic;
|
||||||
@@ -1060,6 +1061,13 @@ gb_internal Type *alloc_type_struct() {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gb_internal Type *alloc_type_struct_complete() {
|
||||||
|
Type *t = alloc_type(Type_Struct);
|
||||||
|
wait_signal_set(&t->Struct.fields_wait_signal);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
gb_internal Type *alloc_type_union() {
|
gb_internal Type *alloc_type_union() {
|
||||||
Type *t = alloc_type(Type_Union);
|
Type *t = alloc_type(Type_Union);
|
||||||
return t;
|
return t;
|
||||||
@@ -3771,6 +3779,8 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type_set_offsets(t);
|
||||||
|
|
||||||
i64 max = 1;
|
i64 max = 1;
|
||||||
for_array(i, t->Struct.fields) {
|
for_array(i, t->Struct.fields) {
|
||||||
Type *field_type = t->Struct.fields[i]->type;
|
Type *field_type = t->Struct.fields[i]->type;
|
||||||
|
|||||||
Reference in New Issue
Block a user