compiler: restore compact types and reduce self-build work (#31036)

Restore compact type descriptors and reduce repeated work across interning, checking, transforms, names, and builtin paths. Add descriptor ownership and backend regressions.

Preserve promoted pointer-embed update fields, nil/defaultless aliases, and fixed-array storage in FastC. Retain typed atomic cache access from master. Document payload ownership, corrected FastC behavior, and the original benchmark scope.

Review validation and environment limitations are recorded in the pull request.
This commit is contained in:
Alexander Medvednikov authored and GitHub committed 2026-10-22 20:59:59 +03:00
1 parent 0fc7e4665b
commit ffc38ada48
78 files changed
+3838 -921

No files matched your search

+3
View File
@@ -112,6 +112,9 @@ array literals uses their element storage; integer arrays and pointers retain Fa
Arrays of anonymous functions retain their parameter and return types when indexed and called.
Membership tests compare fixed-array elements by value. Single-file bootstrap programs apply
struct field defaults to empty initializers.
Promoted fields in pointer embeds support scalar and fixed-array overrides. Overrides copy the
embedded value, preserving shared defaults and unchanged fields from struct updates. A nil embed
uses the embedded type's field defaults before applying overrides.
Sum-type and interface tests narrow later operands of a conjunction, preserving pointer values.
Generic pointer instances retain pointer depth and mutable storage, including forwarded references.
`sizeof(int)` uses FastC's target integer width. Returned, global, and struct-field callbacks retain
+10 -6
View File
@@ -58,7 +58,7 @@ fn _ht_free(p voidptr) {
// malloc dynamically allocates a `n` bytes block of memory on the heap.
// malloc returns a `byteptr` pointing to the memory address of the allocated space.
// unlike the `calloc` family of functions - malloc will not zero the memory block.
@[unsafe]
@[inline; unsafe]
pub fn malloc(n isize) &u8 {
$if trace_malloc ? {
total_m += n
@@ -118,7 +118,8 @@ pub fn malloc(n isize) &u8 {
return res
}
@[unsafe]
// malloc_noscan allocates n bytes that do not contain garbage-collected pointers.
@[inline; unsafe]
pub fn malloc_noscan(n isize) &u8 {
$if trace_malloc ? {
total_m += n
@@ -191,7 +192,7 @@ fn malloc_string_buffer(n isize) &u8 {
return unsafe { malloc_noscan(n) }
}
@[unsafe]
@[inline; unsafe]
fn malloc_uninit(n isize) &u8 {
if n < 0 {
_memory_panic(@FN, n)
@@ -204,7 +205,7 @@ fn malloc_uninit(n isize) &u8 {
return malloc(n)
}
@[unsafe]
@[inline; unsafe]
fn malloc_noscan_uninit(n isize) &u8 {
if n < 0 {
_memory_panic(@FN, n)
@@ -411,6 +412,7 @@ pub fn realloc_data(old_data &u8, old_size int, new_size int) &u8 {
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
// vcalloc checks for negative values given in `n`.
@[inline]
pub fn vcalloc(n isize) &u8 {
$if trace_vcalloc ? {
total_m += n
@@ -460,6 +462,7 @@ pub fn vcalloc(n isize) &u8 {
// special versions of the above that allocate memory which is not scanned
// for pointers (but is collected) when the Boehm garbage collection is used
@[inline]
pub fn vcalloc_noscan(n isize) &u8 {
$if trace_vcalloc ? {
total_m += n
@@ -595,7 +598,7 @@ fn free_unaliased(ptr voidptr) {
// memdup dynamically allocates a `sz` bytes block of memory on the heap
// memdup then copies the contents of `src` into the allocated space and
// returns a pointer to the newly allocated space.
@[unsafe]
@[inline; unsafe]
pub fn memdup(src voidptr, sz isize) voidptr {
$if trace_memdup ? {
C.fprintf(C.stderr, c'memdup size: %10d\n', sz)
@@ -612,7 +615,8 @@ pub fn memdup(src voidptr, sz isize) voidptr {
}
}
@[unsafe]
// memdup_noscan copies `sz` bytes into newly allocated pointer-free storage.
@[inline; unsafe]
pub fn memdup_noscan(src voidptr, sz isize) voidptr {
$if trace_memdup ? {
C.fprintf(C.stderr, c'memdup_noscan size: %10d\n', sz)
+136
View File
@@ -1,5 +1,9 @@
module builtin
fn C.__builtin_ctzll(x u64) i32
fn C.__builtin_clzll(x u64) i32
// string_index_byte searches exactly size readable bytes, including embedded NULs.
@[inline]
fn string_index_byte(data &u8, size int, needle u8) int {
@@ -14,6 +18,27 @@ fn string_index_byte(data &u8, size int, needle u8) int {
}
return -1
} $else {
$if prod && !tinyc && !msvc && !native && little_endian {
if size >= 8 && size <= 32 {
repeated := u64(needle) * u64(0x0101010101010101)
mut offset := 0
for {
mut word := u64(0)
// Each unaligned load remains entirely inside the readable span.
unsafe { C.memcpy(&word, data + offset, 8) }
diff := word ^ repeated
mask := (diff - u64(0x0101010101010101)) & ~diff & u64(0x8080808080808080)
if mask != 0 {
// Borrow can mark later bytes, but the first marked byte is exact.
return offset + int(C.__builtin_ctzll(mask) >> 3)
}
if offset + 8 == size {
return -1
}
offset = if offset + 16 > size { size - 8 } else { offset + 8 }
}
}
}
found := unsafe { C.memchr(data, i32(needle), usize(size)) }
if isnil(found) {
return -1
@@ -22,9 +47,46 @@ fn string_index_byte(data &u8, size int, needle u8) int {
}
}
// string_last_index_byte searches complete words without reading outside the span.
@[inline]
fn string_last_index_byte(data &u8, size int, needle u8) int {
mut end := size
$if prod && !tinyc && !msvc && !native {
repeated := u64(needle) * u64(0x0101010101010101)
for end >= 8 {
start := end - 8
mut word := u64(0)
// A fixed-size copy permits unaligned views and never crosses their bounds.
unsafe { C.memcpy(&word, data + start, 8) }
diff := word ^ repeated
$if little_endian {
// Unlike subtraction, this mask marks only zero bytes: a borrow from
// an earlier match cannot create a false match at the high end.
low_bits := u64(0x7f7f7f7f7f7f7f7f)
mask := ~(((diff & low_bits) + low_bits) | diff | low_bits)
if mask != 0 {
return start + 7 - int(C.__builtin_clzll(mask) >> 3)
}
} $else {
if (diff - u64(0x0101010101010101)) & ~diff & u64(0x8080808080808080) != 0 {
for i := end - 1; i >= start; i-- {
if unsafe { data[i] } == needle { return i }
}
}
}
end = start
}
}
for i := end - 1; i >= 0; i-- {
if unsafe { data[i] } == needle { return i }
}
return -1
}
// string_bytes_equal compares readable byte spans without allocating or reading past size.
@[inline]
fn string_bytes_equal(a &u8, b &u8, size int) bool {
if a == b { return true }
$if prod && !tinyc && !msvc && !native {
if size >= 0 && size <= 16 {
if size >= 8 {
@@ -107,6 +169,34 @@ pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr {
if n == 0 || usize(dest) <= 0xFFFF || usize(const_src) <= 0xFFFF {
return dest
}
$if prod && !tinyc && !msvc && !native {
if n > 0 && n <= 64 {
// Fixed-size copies become unaligned loads/stores. The source and
// destination do not overlap, so overlapping head/tail copies are safe.
unsafe {
dst := &u8(dest)
src := &u8(const_src)
if n >= 32 {
C.memcpy(dst, src, 32)
C.memcpy(dst + n - 32, src + n - 32, 32)
} else if n >= 16 {
C.memcpy(dst, src, 16)
C.memcpy(dst + n - 16, src + n - 16, 16)
} else if n >= 8 {
C.memcpy(dst, src, 8)
C.memcpy(dst + n - 8, src + n - 8, 8)
} else if n >= 4 {
C.memcpy(dst, src, 4)
C.memcpy(dst + n - 4, src + n - 4, 4)
} else {
dst[0] = src[0]
dst[n >> 1] = src[n >> 1]
dst[n - 1] = src[n - 1]
}
}
return dest
}
}
unsafe {
return C.memcpy(dest, const_src, n)
}
@@ -125,6 +215,52 @@ pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr {
if n == 0 || usize(dest) <= 0xFFFF || usize(const_src) <= 0xFFFF {
return dest
}
$if prod && !tinyc && !msvc && !native {
if n > 0 && n <= 64 {
// Load both ends before storing either: the source and destination may overlap.
unsafe {
dst := &u8(dest)
src := &u8(const_src)
if n >= 32 {
mut head := [32]u8{}
mut tail := [32]u8{}
C.memcpy(&head[0], src, 32)
C.memcpy(&tail[0], src + n - 32, 32)
C.memcpy(dst, &head[0], 32)
C.memcpy(dst + n - 32, &tail[0], 32)
} else if n >= 16 {
mut head := [16]u8{}
mut tail := [16]u8{}
C.memcpy(&head[0], src, 16)
C.memcpy(&tail[0], src + n - 16, 16)
C.memcpy(dst, &head[0], 16)
C.memcpy(dst + n - 16, &tail[0], 16)
} else if n >= 8 {
mut head := u64(0)
mut tail := u64(0)
C.memcpy(&head, src, 8)
C.memcpy(&tail, src + n - 8, 8)
C.memcpy(dst, &head, 8)
C.memcpy(dst + n - 8, &tail, 8)
} else if n >= 4 {
mut head := u32(0)
mut tail := u32(0)
C.memcpy(&head, src, 4)
C.memcpy(&tail, src + n - 4, 4)
C.memcpy(dst, &head, 4)
C.memcpy(dst + n - 4, &tail, 4)
} else {
head := src[0]
middle := src[n >> 1]
tail := src[n - 1]
dst[0] = head
dst[n >> 1] = middle
dst[n - 1] = tail
}
}
return dest
}
}
unsafe {
return C.memmove(dest, const_src, n)
}
+17 -7
View File
@@ -20,6 +20,7 @@ fn fast_string_eq(a string, b string) bool {
return unsafe { C.memcmp(a.str, b.str, b.len) == 0 }
}
@[inline]
fn map_hash_string(pkey voidptr) u64 {
key := unsafe { &string(pkey) }
$if prod && !tinyc && !msvc {
@@ -62,7 +63,7 @@ fn map_hash_half(p &u8) u64 {
// each 128-bit product, which `_wymix` folds into 64 bits, and three such products
// side by side for a long key. A key of up to 16 bytes takes two products.
// The hash is only for the maps of a program: `hash.wyhash_c` does not change.
@[unsafe]
@[inline; unsafe]
fn map_hash_bytes(key &u8, len int) u64 {
unsafe {
mut p := key
@@ -70,12 +71,21 @@ fn map_hash_bytes(key &u8, len int) u64 {
mut a := u64(0)
mut b := u64(0)
if len <= 16 {
if len >= 4 {
// The first and the last 4 bytes, and the 4 bytes that follow or
// precede them in a key of 8 bytes or more: they overlap in a shorter one.
mid := (len >> 3) << 2
a = (map_hash_half(p) << 32) | map_hash_half(p + mid)
b = (map_hash_half(p + len - 4) << 32) | map_hash_half(p + len - 4 - mid)
if len >= 8 {
if len < 16 {
// These halves are adjacent, so one unaligned word load suffices.
first := map_hash_word(p)
a = (first << 32) | (first >> 32)
b = map_hash_word(p + len - 8)
} else {
a = (map_hash_half(p) << 32) | map_hash_half(p + 8)
b = (map_hash_half(p + 12) << 32) | map_hash_half(p + 4)
}
} else if len >= 4 {
first := map_hash_half(p)
last := map_hash_half(p + len - 4)
a = (first << 32) | first
b = (last << 32) | last
} else if len > 0 {
a = (u64(p[0]) << 16) | (u64(p[len >> 1]) << 8) | u64(p[len - 1])
}
+135 -24
View File
@@ -453,6 +453,16 @@ pub fn (mut m map) clear() {
m.len = 0
}
// keys_equal exposes the standard string comparator to inlining while preserving
// the callback for all other key representations and custom comparators.
@[inline]
fn (m &map) keys_equal(a voidptr, b voidptr) bool {
if voidptr(m.key_eq_fn) == voidptr(map_eq_string) {
return map_eq_string(a, b)
}
return m.key_eq_fn(a, b)
}
@[inline]
fn (m &map) key_to_index(pkey voidptr) (u32, u32) {
if voidptr(m.hash_fn) == unsafe { nil } {
@@ -463,7 +473,11 @@ fn (m &map) key_to_index(pkey voidptr) (u32, u32) {
panic('map.hash_fn is nil map_ptr=${usize(m)} key_bytes=${m.key_bytes} value_bytes=${m.value_bytes} even_index=${m.even_index} shift=${m.shift} metas=${usize(m.metas)} prev2=${prev2} prev1=${prev1} w0=${p[0]} w1=${p[1]} w2=${p[2]} w3=${p[3]} w4=${p[4]} w5=${p[5]} w6=${p[6]} w7=${p[7]} hash_fn=${usize(voidptr(m.hash_fn))}')
}
}
hash := m.hash_fn(pkey)
hash := if voidptr(m.hash_fn) == voidptr(map_hash_string) {
map_hash_string(pkey)
} else {
m.hash_fn(pkey)
}
index := hash & m.even_index
meta := ((hash >> m.shift) & hash_mask) | probe_inc
return u32(index), u32(meta)
@@ -545,7 +559,16 @@ fn (mut m map) ensure_extra_metas(probe_count u32) {
// Insert new element to the map. The element is inserted if its key is
// not equivalent to the key of any other element already in the container.
// If the key already exists, its value is changed to the value of the new element.
@[inline]
fn (mut m map) set(key voidptr, value voidptr) {
if m.len == 1 && m.key_values.len == 1 && m.keys_equal(key, m.key_values.key(0)) {
unsafe { vmemcpy(m.key_values.value(0), value, m.value_bytes) }
return
}
m.set_general(key, value)
}
fn (mut m map) set_general(key voidptr, value voidptr) {
if m.metas == unsafe { nil } {
// Most compiler bookkeeping maps remain empty. Allocate backing storage
// only on the first insertion or an explicit reservation.
@@ -563,7 +586,7 @@ fn (mut m map) set(key voidptr, value voidptr) {
for meta == unsafe { m.metas[index] } {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
unsafe {
pval := m.key_values.value(kv_index)
vmemcpy(pval, value, m.value_bytes)
@@ -702,7 +725,7 @@ fn (mut m map) get_and_set(key voidptr, zero voidptr) voidptr {
if meta == unsafe { m.metas[index] } {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
pval := unsafe { m.key_values.value(kv_index) }
return unsafe { &u8(pval) }
}
@@ -719,28 +742,59 @@ fn (mut m map) get_and_set(key voidptr, zero voidptr) voidptr {
return unsafe { nil }
}
// At most two dense slots cost less to compare than to hash and probe.
// Tombstones are checked even when only one live mapping remains.
@[inline]
fn (m &map) small_key_index(key voidptr) int {
for i in 0 .. m.key_values.len {
if m.key_values.has_index(i) && m.keys_equal(key, m.key_values.key(i)) {
return i
}
}
return -1
}
// If `key` matches the key of an element in the container,
// the method returns a reference to its mapped value.
// If not, a zero/default value is returned.
@[inline]
fn (m &map) get(key voidptr, zero voidptr) voidptr {
if m.len == 0 {
return zero
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return m.key_values.value(0)
}
return zero
}
return m.get_nonempty(key, zero)
}
@[inline]
fn (m &map) get_nonempty(key voidptr, zero voidptr) voidptr {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return m.key_values.value(i)
}
return zero
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
pval := unsafe { m.key_values.value(kv_index) }
return unsafe { &u8(pval) }
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return zero
}
@@ -749,25 +803,44 @@ fn (m &map) get(key voidptr, zero voidptr) voidptr {
// the method returns a reference to its mapped value.
// If not, a zero pointer is returned.
// This is used in `x := m['key'] or { ... }`
@[inline]
fn (m &map) get_check(key voidptr) voidptr {
if m.len == 0 {
return 0
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return m.key_values.value(0)
}
return 0
}
return m.get_check_nonempty(key)
}
@[inline]
fn (m &map) get_check_nonempty(key voidptr) voidptr {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return m.key_values.value(i)
}
return 0
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
pval := unsafe { m.key_values.value(kv_index) }
return unsafe { &u8(pval) }
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return 0
}
@@ -775,47 +848,85 @@ fn (m &map) get_check(key voidptr) voidptr {
// If `key` matches the key of an element in the container,
// the method returns a reference to the stored key.
// If not, a zero pointer is returned.
@[inline]
fn (m &map) get_key_check(key voidptr) voidptr {
if m.len == 0 {
return 0
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return m.key_values.key(0)
}
return 0
}
return m.get_key_check_nonempty(key)
}
@[inline]
fn (m &map) get_key_check_nonempty(key voidptr) voidptr {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return m.key_values.key(i)
}
return 0
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
return unsafe { &u8(pkey) }
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return 0
}
// Checks whether a particular key exists in the map.
@[inline]
fn (m &map) exists(key voidptr) bool {
if m.len == 0 {
return false
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return true
}
return false
}
return m.exists_nonempty(key)
}
@[inline]
fn (m &map) exists_nonempty(key voidptr) bool {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return true
}
return false
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
return true
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return false
}
@@ -848,7 +959,7 @@ pub fn (mut m map) delete(key voidptr) {
for meta == unsafe { m.metas[index] } {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
for (unsafe { m.metas[index + 2] } >> hashbits) > 1 {
unsafe {
m.metas[index] = m.metas[index + 2] - probe_inc
+136 -24
View File
@@ -499,12 +499,26 @@ fn (m &VMapData) panic_nil_map_hash_fn() {
}
}
// keys_equal exposes the standard string comparator to inlining while preserving
// the callback for all other key representations and custom comparators.
@[inline]
fn (m &VMapData) keys_equal(a voidptr, b voidptr) bool {
if voidptr(m.key_eq_fn) == voidptr(map_eq_string) {
return map_eq_string(a, b)
}
return m.key_eq_fn(a, b)
}
@[inline]
fn (m &VMapData) key_to_index(pkey voidptr) (u32, u32) {
if voidptr(m.hash_fn) == unsafe { nil } {
m.panic_nil_map_hash_fn()
}
hash := m.hash_fn(pkey)
hash := if voidptr(m.hash_fn) == voidptr(map_hash_string) {
map_hash_string(pkey)
} else {
m.hash_fn(pkey)
}
index := hash & m.even_index
meta := ((hash >> m.shift) & hash_mask) | probe_inc
return u32(index), u32(meta)
@@ -586,16 +600,26 @@ fn (mut m VMapData) ensure_extra_metas(probe_count u32) {
// Insert new element to the map. The element is inserted if its key is
// not equivalent to the key of any other element already in the container.
// If the key already exists, its value is changed to the value of the new element.
@[inline]
fn (mut m map) set(key voidptr, value voidptr) {
m.data.set(key, value)
}
@[inline]
fn (mut m VMapData) set(key voidptr, value voidptr) {
$if race ? {
// Like Go, every insertion is a write of the map, even when it only replaces the
// value of an existing key: it races with an unsynchronized `m.len`.
racewrite(&m.count)
}
if m.count == 1 && m.key_values.len == 1 && m.keys_equal(key, m.key_values.key(0)) {
unsafe { vmemcpy(m.key_values.value(0), value, m.value_bytes) }
return
}
m.set_general(key, value)
}
fn (mut m VMapData) set_general(key voidptr, value voidptr) {
if m.metas == unsafe { nil } {
// Most compiler bookkeeping maps remain empty. Allocate backing storage
// only on the first insertion or an explicit reservation.
@@ -613,7 +637,7 @@ fn (mut m VMapData) set(key voidptr, value voidptr) {
for meta == unsafe { m.metas[index] } {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
unsafe {
pval := m.key_values.value(kv_index)
vmemcpy(pval, value, m.value_bytes)
@@ -762,7 +786,7 @@ fn (mut m VMapData) get_and_set(key voidptr, zero voidptr) voidptr {
if meta == unsafe { m.metas[index] } {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
pval := unsafe { m.key_values.value(kv_index) }
return unsafe { &u8(pval) }
}
@@ -779,6 +803,18 @@ fn (mut m VMapData) get_and_set(key voidptr, zero voidptr) voidptr {
return unsafe { nil }
}
// At most two dense slots cost less to compare than to hash and probe.
// Tombstones are checked even when only one live mapping remains.
@[inline]
fn (m &VMapData) small_key_index(key voidptr) int {
for i in 0 .. m.key_values.len {
if m.key_values.has_index(i) && m.keys_equal(key, m.key_values.key(i)) {
return i
}
}
return -1
}
// If `key` matches the key of an element in the container,
// the method returns a reference to its mapped value.
// If not, a zero/default value is returned.
@@ -786,25 +822,44 @@ fn (m &map) get(key voidptr, zero voidptr) voidptr {
return m.data.get(key, zero)
}
@[inline]
fn (m &VMapData) get(key voidptr, zero voidptr) voidptr {
if m.count == 0 {
return zero
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return m.key_values.value(0)
}
return zero
}
return m.get_nonempty(key, zero)
}
@[inline]
fn (m &VMapData) get_nonempty(key voidptr, zero voidptr) voidptr {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return m.key_values.value(i)
}
return zero
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
pval := unsafe { m.key_values.value(kv_index) }
return unsafe { &u8(pval) }
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return zero
}
@@ -817,25 +872,44 @@ fn (m &map) get_check(key voidptr) voidptr {
return m.data.get_check(key)
}
@[inline]
fn (m &VMapData) get_check(key voidptr) voidptr {
if m.count == 0 {
return 0
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return m.key_values.value(0)
}
return 0
}
return m.get_check_nonempty(key)
}
@[inline]
fn (m &VMapData) get_check_nonempty(key voidptr) voidptr {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return m.key_values.value(i)
}
return 0
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
pval := unsafe { m.key_values.value(kv_index) }
return unsafe { &u8(pval) }
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return 0
}
@@ -847,24 +921,43 @@ fn (m &map) get_key_check(key voidptr) voidptr {
return m.data.get_key_check(key)
}
@[inline]
fn (m &VMapData) get_key_check(key voidptr) voidptr {
if m.count == 0 {
return 0
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return m.key_values.key(0)
}
return 0
}
return m.get_key_check_nonempty(key)
}
@[inline]
fn (m &VMapData) get_key_check_nonempty(key voidptr) voidptr {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return m.key_values.key(i)
}
return 0
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
return unsafe { &u8(pkey) }
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return 0
}
@@ -874,24 +967,43 @@ fn (m &map) exists(key voidptr) bool {
return m.data.exists(key)
}
@[inline]
fn (m &VMapData) exists(key voidptr) bool {
if m.count == 0 {
return false
}
if m.key_values.len == 1 {
if m.keys_equal(key, m.key_values.key(0)) {
return true
}
return false
}
return m.exists_nonempty(key)
}
@[inline]
fn (m &VMapData) exists_nonempty(key voidptr) bool {
if m.key_values.len <= 2 {
i := m.small_key_index(key)
if i >= 0 {
return true
}
return false
}
mut index, mut meta := m.key_to_index(key)
for {
if meta == unsafe { m.metas[index] } {
stored_meta := unsafe { m.metas[index] }
if meta == stored_meta {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
return true
}
} else if meta > stored_meta {
break
}
index += 2
meta += probe_inc
if meta > unsafe { m.metas[index] } {
break
}
}
return false
}
@@ -933,7 +1045,7 @@ fn (mut m VMapData) delete(key voidptr) {
for meta == unsafe { m.metas[index] } {
kv_index := int(unsafe { m.metas[index + 1] })
pkey := unsafe { m.key_values.key(kv_index) }
if m.key_eq_fn(key, pkey) {
if m.keys_equal(key, pkey) {
for (unsafe { m.metas[index + 2] } >> hashbits) > 1 {
unsafe {
m.metas[index] = m.metas[index + 2] - probe_inc
@@ -0,0 +1,30 @@
module builtin
// Preserve the previous four-half-word formulation independently of the new
// word-load shortcut, including its different midpoint at exactly 16 bytes.
fn previous_short_map_hash(key &u8, len int) u64 {
mut a := u64(0)
mut b := u64(0)
unsafe {
if len >= 4 {
mid := (len >> 3) << 2
a = (map_hash_half(key) << 32) | map_hash_half(key + mid)
b = (map_hash_half(key + len - 4) << 32) | map_hash_half(key + len - 4 - mid)
} else if len > 0 {
a = (u64(key[0]) << 16) | (u64(key[len >> 1]) << 8) | u64(key[len - 1])
}
}
return C._wymix(C._wymix(a ^ map_hash_k1, b ^ map_hash_k0) ^ map_hash_k0 ^ u64(len),
map_hash_k1)
}
fn test_map_hash_bytes_preserves_short_hash_at_every_length_and_alignment() {
for offset in 0 .. 16 {
for len in 0 .. 17 {
bytes := []u8{len: offset + len + 16, init: u8(index * 73 + len * 19)}
key := unsafe { &u8(bytes.data) + offset }
expected := previous_short_map_hash(key, len)
assert unsafe { map_hash_bytes(key, len) } == expected, 'length ${len}, offset ${offset}'
}
}
}
+114
View File
@@ -0,0 +1,114 @@
module builtin
fn string_length_hash(key voidptr) u64 {
return u64(unsafe { (&string(key)).len })
}
fn string_length_equal(a voidptr, b voidptr) bool {
return unsafe { (&string(a)).len == (&string(b)).len }
}
fn test_map_custom_callbacks_are_preserved() {
// Equal lengths intentionally identify different strings as the same key.
mut values := new_map(int(sizeof(string)), int(sizeof(int)), string_length_hash,
string_length_equal, map_clone_string, map_free_string)
for i in 1 .. 12 {
key := 'a'.repeat(i)
value := i * 10
values.set(&key, &value)
}
for i in 1 .. 12 {
key := 'b'.repeat(i)
found := values.get_check(&key)
assert found != unsafe { nil }
assert unsafe { *&int(found) } == i * 10
assert values.exists(&key)
zero := -1
assert unsafe { *&int(values.get(&key, &zero)) } == i * 10
value := i * 100
values.set(&key, &value)
assert unsafe { *&int(values.get_check(&key)) } == value
}
for i in 1 .. 12 {
key := 'c'.repeat(i)
values.delete(&key)
assert !values.exists(&key)
assert values.get_check(&key) == unsafe { nil }
}
}
fn integer_mod_hash(key voidptr) u64 {
return u64(unsafe { *&u64(key) } % 16)
}
fn integer_mod_equal(a voidptr, b voidptr) bool {
return unsafe { *&u64(a) % 16 == *&u64(b) % 16 }
}
fn test_map_custom_integer_callbacks_are_preserved() {
mut values := new_map(int(sizeof(u64)), int(sizeof(int)), integer_mod_hash,
integer_mod_equal, map_clone_int_8, map_free_nop)
for i in 0 .. 12 {
key := u64(i)
values.set(&key, &i)
// Exercise the one-entry and two-entry paths before the normal hash path.
for j in 0 .. i + 1 {
equivalent := u64(j + 16)
found := values.get_check(&equivalent)
assert found != unsafe { nil }
assert unsafe { *&int(found) } == j
assert values.exists(&equivalent)
stored := values.get_key_check(&equivalent)
assert stored != unsafe { nil }
assert unsafe { *&u64(stored) } == u64(j)
}
}
for i in 0 .. 12 {
key := u64(i + 32)
value := i * 10
values.set(&key, &value)
assert unsafe { *&int(values.get_check(&key)) } == value
values.delete(&key)
assert !values.exists(&key)
}
}
fn map_probe_cluster_hash(key voidptr) u64 {
// Colliding home buckets exercise both matching and unequal probe metadata.
return u64(unsafe { *&u64(key) } % 8)
}
fn test_map_read_probes_terminate_across_collisions_and_deleted_slots() {
mut values := new_map(int(sizeof(u64)), int(sizeof(int)), map_probe_cluster_hash,
map_eq_int_8, map_clone_int_8, map_free_nop)
for i in 0 .. 128 {
key := u64(i)
value := i + 10
values.set(&key, &value)
}
for i in 0 .. 128 {
if i % 3 == 0 {
key := u64(i)
values.delete(&key)
}
}
zero := -1
for i in 0 .. 256 {
key := u64(i)
present := i < 128 && i % 3 != 0
assert values.exists(&key) == present
found := values.get_check(&key)
stored := values.get_key_check(&key)
if present {
assert !isnil(found)
assert !isnil(stored)
assert unsafe { *&int(found) } == i + 10
assert unsafe { *&u64(stored) } == key
assert unsafe { *&int(values.get(&key, &zero)) } == i + 10
} else {
assert isnil(found)
assert isnil(stored)
assert values.get(&key, &zero) == voidptr(&zero)
}
}
}
+66
View File
@@ -1102,3 +1102,69 @@ fn test_map_clear() {
s.add2(item1)
assert s.state.len == 2
}
fn test_small_map_reads_after_tombstones_and_growth() {
mut m := {
'first': 11
'second': 22
}
assert m['first'] == 11
assert m['second'] or { -1 } == 22
assert 'first' in m
assert 'missing' !in m
assert m['missing'] == 0
assert m['missing'] or { -1 } == -1
m.delete('first')
assert 'first' !in m
assert m['first'] or { -1 } == -1
assert m['second'] == 22
m['third'] = 33
assert m['second'] == 22
assert m['third'] or { -1 } == 33
m.delete('third')
assert m['second'] == 22
mut copy := m.clone()
copy['second'] = 44
assert m['second'] == 22
assert copy['second'] == 44
m.clear()
assert 'second' !in m
m['new'] = 55
assert m['new'] == 55
assert m.keys() == ['new']
mut ints := {
7: 'seven'
9: 'nine'
}
assert ints[7] == 'seven'
assert ints[9] or { 'absent' } == 'nine'
ints.delete(7)
assert 7 !in ints
assert ints[9] == 'nine'
assert ints[7] == ''
}
fn test_small_map_lookup_thresholds_and_deleted_slots() {
for size in 1 .. 10 {
mut values := map[string]int{}
keys := ['a', 'bb', 'ccc', 'dddd', 'eeeee', 'ffffff', 'ggggggg', 'hhhhhhhh', 'iiiiiiiii']
for i in 0 .. size {
values[keys[i]] = i + 1
}
for i in 0 .. size {
assert values[keys[i]] == i + 1
assert values[keys[i]] or { -1 } == i + 1
assert keys[i] in values
}
for i in 0 .. size {
values.delete(keys[i])
assert keys[i] !in values
assert values[keys[i]] == 0
assert values[keys[i]] or { -1 } == -1
for j in i + 1 .. size {
assert values[keys[j]] == j + 1
}
}
}
}
+47
View File
@@ -0,0 +1,47 @@
module builtin
fn test_memcpy_preserves_unaligned_spans_and_surrounding_bytes() {
for size in 0 .. 66 {
for src_offset in 0 .. 16 {
for dst_offset in 0 .. 16 {
source := []u8{len: size + src_offset, init: u8(index * 37 + 1)}
mut target := []u8{len: size + dst_offset + 16, init: 0xa5}
unsafe {
dst := &u8(target.data) + dst_offset
src := &u8(source.data) + src_offset
assert vmemcpy(dst, src, size) == dst
}
for i, value in target {
assert value == if i >= dst_offset && i < dst_offset + size {
source[src_offset + i - dst_offset]
} else {
u8(0xa5)
}
}
}
}
}
}
fn test_memmove_preserves_overlapping_spans_and_surrounding_bytes() {
for size in 0 .. 66 {
for src_offset in 0 .. 66 {
for dst_offset in 0 .. 66 {
mut target := []u8{len: 132, init: u8(index * 37 + 1)}
original := target.clone()
unsafe {
dst := &u8(target.data) + dst_offset
src := &u8(target.data) + src_offset
assert vmemmove(dst, src, size) == dst
}
for i, value in target {
assert value == if i >= dst_offset && i < dst_offset + size {
original[src_offset + i - dst_offset]
} else {
original[i]
}
}
}
}
}
}
+2 -2
View File
@@ -1132,7 +1132,7 @@ pub fn prealloc_scope_free_after(scope_ptr voidptr) {
}
}
@[unsafe]
@[inline; unsafe]
fn prealloc_malloc(n isize) &u8 {
return unsafe { vmemory_block_malloc(n, 0) }
}
@@ -1173,7 +1173,7 @@ fn prealloc_realloc(old_data &u8, old_size isize, new_size isize) &u8 {
return new_ptr
}
@[unsafe]
@[inline; unsafe]
fn prealloc_calloc(n isize) &u8 {
new_ptr := unsafe { vmemory_block_malloc(n, 0) }
unsafe { C.memset(new_ptr, 0, n) }
+66 -75
View File
@@ -341,6 +341,7 @@ fn (a string) option_clone_static() ?string {
}
// clone returns a copy of the V string `a`.
@[inline]
pub fn (a string) clone() string {
if a.len <= 0 {
return ''
@@ -927,12 +928,12 @@ fn (s string) < (a string) bool {
return s.len < a.len
}
@[direct_array_access]
@[direct_array_access; inline]
fn (s string) + (a string) string {
return string_concat_pair(s, a)
}
@[direct_array_access]
@[direct_array_access; inline]
fn string_concat_pair(s string, a string) string {
slen := if s.len > 0 { s.len } else { 0 }
alen := if a.len > 0 { a.len } else { 0 }
@@ -994,7 +995,7 @@ fn string_plus_many_general(data_len int, input_base &string) string {
}
// for `s + s2 + s3`, an optimization (faster than string_plus(string_plus(s1, s2), s3))
@[direct_array_access]
@[direct_array_access; inline]
fn (s string) plus_two(a string, b string) string {
slen := if s.len > 0 { s.len } else { 0 }
alen := if a.len > 0 { a.len } else { 0 }
@@ -1364,14 +1365,13 @@ pub fn (s string) split_by_space() []string {
// substr returns the string between index positions `start` and `end`.
// Example: assert 'ABCD'.substr(1,3) == 'BC'
@[direct_array_access]
@[direct_array_access; inline]
pub fn (s string) substr(start int, _end int) string {
// WARNNING: The is a temp solution for bootstrap!
end := if _end == max_i64 || _end == max_i32 { s.len } else { _end } // max_int
$if !no_bounds_checking {
if start > end || start > s.len || end > s.len || start < 0 || end < 0 {
panic('substr(' + impl_i64_to_string(start) + ', ' + impl_i64_to_string(end) +
') out of bounds (len=' + impl_i64_to_string(s.len) + ') s=' + s)
s.substr_panic(start, end)
}
}
len := end - start
@@ -1389,13 +1389,19 @@ pub fn (s string) substr(start int, _end int) string {
return res
}
@[noinline; noreturn]
fn (s string) substr_panic(start int, end int) {
panic('substr(' + impl_i64_to_string(start) + ', ' + impl_i64_to_string(end) +
') out of bounds (len=' + impl_i64_to_string(s.len) + ') s=' + s)
}
// substr_borrowed supplies the checked, non-owning view used by addressed ranges.
@[inline]
fn (s string) substr_borrowed(start int, _end int) string {
end := if _end == max_i64 || _end == max_i32 { s.len } else { _end }
$if !no_bounds_checking {
if start > end || start > s.len || end > s.len || start < 0 || end < 0 {
panic('substr(' + impl_i64_to_string(start) + ', ' + impl_i64_to_string(end) +
') out of bounds (len=' + impl_i64_to_string(s.len) + ') s=' + s)
s.substr_panic(start, end)
}
}
return string{
@@ -1501,7 +1507,7 @@ pub fn (s string) substr_ni(_start int, _end int) string {
// index_ returns the position of the first character of the input string.
// It will return `-1` if the input string can't be found, or `0` for an empty input string.
@[direct_array_access]
@[direct_array_access; inline]
pub fn (s string) index_(p string) int {
if p.len == 0 {
return 0
@@ -1512,9 +1518,17 @@ pub fn (s string) index_(p string) int {
if p.len == 1 {
return string_index_byte(s.str, s.len, unsafe { p.str[0] })
}
return s.index_multibyte(p)
}
@[direct_array_access]
fn (s string) index_multibyte(p string) int {
if p.len > max_direct_index_needle_len {
return s.index_kmp(p)
}
if p.len == 2 {
return s.index_pair(p)
}
// Short needles: scanning for the first byte and comparing the rest in place
// needs no prefix table, and costs at most p.len compares per position.
first := unsafe { p.str[0] }
@@ -1527,17 +1541,30 @@ pub fn (s string) index_(p string) int {
}
i += next + 1
}
mut j := 1
for j < p.len && unsafe { s.str[i + j] == p.str[j] } {
j++
}
if j == p.len {
// The first byte already matched; compare only the remaining bytes.
if string_bytes_equal(unsafe { s.str + i + 1 }, unsafe { p.str + 1 }, p.len - 1) {
return i
}
}
return -1
}
@[direct_array_access]
fn (s string) index_pair(p string) int {
first := unsafe { p.str[0] }
second := unsafe { p.str[1] }
last_start := s.len - 2
for i := 0; i <= last_start; i++ {
if unsafe { s.str[i] } != first {
next := string_index_byte(unsafe { s.str + i + 1 }, last_start - i, first)
if next < 0 { return -1 }
i += next + 1
}
if unsafe { s.str[i + 1] } == second { return i }
}
return -1
}
// Needles up to this length are searched directly; longer ones use KMP, which
// keeps the search linear in `s.len` whatever the needle.
const max_direct_index_needle_len = 16
@@ -1635,16 +1662,16 @@ fn (s string) index_last_(p string) int {
if p.len == 1 {
return s.last_index_u8(unsafe { p.str[0] })
}
mut i := s.len - p.len
for i >= 0 {
mut j := 0
for j < p.len && unsafe { s.str[i + j] == p.str[j] } {
j++
mut end := s.len - p.len + 1
for end > 0 {
i := string_last_index_byte(s.str, end, unsafe { p.str[0] })
if i < 0 {
return -1
}
if j == p.len {
if string_bytes_equal(unsafe { s.str + i }, p.str, p.len) {
return i
}
i--
end = i
}
return -1
}
@@ -1652,64 +1679,35 @@ fn (s string) index_last_(p string) int {
// index_after returns the position of the input string, starting search from `start` position.
@[direct_array_access]
pub fn (s string) index_after(p string, start int) ?int {
if p.len > s.len {
i := s.index_after_(p, start)
if i < 0 {
return none
}
mut strt := start
if start < 0 {
strt = 0
}
if start >= s.len {
return none
}
mut i := strt
for i < s.len {
mut j := 0
mut ii := i
for j < p.len && unsafe { s.str[ii] == p.str[j] } {
j++
ii++
}
if j == p.len {
return i
}
i++
}
return none
return i
}
// index_after_ returns the position of the input string, starting search from `start` position.
@[direct_array_access]
pub fn (s string) index_after_(p string, start int) int {
if p.len > s.len {
return -1
}
mut strt := start
if start < 0 {
strt = 0
}
if start >= s.len {
if strt >= s.len {
return -1
}
mut i := strt
for i < s.len {
mut j := 0
mut ii := i
for j < p.len && unsafe { s.str[ii] == p.str[j] } {
j++
ii++
}
if j == p.len {
return i
}
i++
if p.len > s.len - strt {
return -1
}
return -1
// Borrow only the remaining bytes; the search never examines the backing tail.
remaining := unsafe { tos(s.str + strt, s.len - strt) }
i := remaining.index_(p)
return if i < 0 { -1 } else { strt + i }
}
// index_u8 returns the index of byte `c` if found in the string.
// index_u8 returns -1 if the byte can not be found.
@[direct_array_access]
@[direct_array_access; inline]
pub fn (s string) index_u8(c u8) int {
return string_index_byte(s.str, s.len, c)
}
@@ -1717,12 +1715,7 @@ pub fn (s string) index_u8(c u8) int {
// last_index_u8 returns the index of the last occurrence of byte `c` if it was found in the string.
@[direct_array_access; inline]
pub fn (s string) last_index_u8(c u8) int {
for i := s.len - 1; i >= 0; i-- {
if s[i] == c {
return i
}
}
return -1
return string_last_index_byte(s.str, s.len, c)
}
// count returns the number of occurrences of `substr` in the string.
@@ -1764,12 +1757,14 @@ pub fn (s string) count(substr string) int {
// contains_u8 returns `true` if the string contains the byte value `x`.
// See also: [`string.index_u8`](#string.index_u8) , to get the index of the byte as well.
@[inline]
pub fn (s string) contains_u8(x u8) bool {
return string_index_byte(s.str, s.len, x) >= 0
}
// contains returns `true` if the string contains `substr`.
// See also: [`string.index`](#string.index)
@[inline]
pub fn (s string) contains(substr string) bool {
if substr.len == 0 {
return true
@@ -1821,25 +1816,21 @@ pub fn (s string) contains_any_substr(substrs []string) bool {
}
// starts_with returns `true` if the string starts with `p`.
@[direct_array_access]
@[direct_array_access; inline]
pub fn (s string) starts_with(p string) bool {
if p.len > s.len || usize(s.str) <= 0xFFFF || usize(p.str) <= 0xFFFF {
return false
} else if unsafe { vmemcmp(s.str, p.str, p.len) == 0 } {
return true
}
return false
return string_content_equal(s.str, p.str, p.len)
}
// ends_with returns `true` if the string ends with `p`.
@[direct_array_access]
@[direct_array_access; inline]
pub fn (s string) ends_with(p string) bool {
if p.len > s.len || usize(s.str) <= 0xFFFF || usize(p.str) <= 0xFFFF {
return false
} else if unsafe { vmemcmp(s.str + s.len - p.len, p.str, p.len) == 0 } {
return true
}
return false
return string_content_equal(unsafe { s.str + s.len - p.len }, p.str, p.len)
}
// to_lower_ascii returns the string in all lowercase characters.
+57
View File
@@ -23,6 +23,7 @@ fn test_byte_search_respects_unaligned_string_boundaries() {
assert view.index_(needle) == first
assert view.contains(needle) == (first >= 0)
assert view.index_last_(needle) == last
assert view.last_index_u8(u8(byte)) == last
}
}
}
@@ -58,8 +59,64 @@ fn test_short_needle_search_handles_repeated_prefixes_and_nuls() {
expected := byte_search_reference_index(view, needle)
assert view.index_(needle) == expected
assert view.contains(needle) == (expected >= 0)
assert view.starts_with(needle) == (expected == 0)
mut suffix := false
if needle.len <= view.len {
suffix = byte_search_reference_index(view[view.len - needle.len..], needle) == 0
}
assert view.ends_with(needle) == suffix
mut last := -1
for offset in 0 .. view.len + 1 {
if offset + needle.len <= view.len
&& byte_search_reference_index(view[offset..], needle) == 0 {
last = offset
}
}
assert view.index_last_(needle) == last
for offset in [-1, 0, view.len / 2, view.len, view.len + 1] {
from := if offset < 0 { 0 } else { offset }
mut after := -1
if from < view.len {
found := byte_search_reference_index(view[from..], needle)
if found >= 0 { after = from + found }
}
assert view.index_after_(needle, offset) == after
assert (view.index_after(needle, offset) or { -1 }) == after
}
}
}
}
}
}
fn test_short_needle_search_respects_borrowed_match_boundaries() {
for size in 2 .. 17 {
mut needle_bytes := []u8{len: size, init: u8(index % 3)}
needle := needle_bytes.bytestr()
for start in 0 .. 20 {
mut bytes := []u8{len: start + size + 20, init: 0xff}
copy(mut bytes[start..], needle_bytes)
for end in start .. bytes.len + 1 {
view := unsafe { tos(bytes.data, end) }
assert view.index_(needle) == byte_search_reference_index(view, needle)
}
}
}
}
fn test_byte_search_handles_adjacent_borrows_and_repeated_bytes() {
for needle in [u8(0), 1, 0x7f, 0x80, 0xff] {
mut bytes := []u8{len: 96, init: needle ^ 1}
for offset in 0 .. bytes.len {
bytes[offset] = needle
for start in 0 .. offset + 1 {
for end in offset + 1 .. bytes.len + 1 {
view := unsafe { tos(&bytes[start], end - start) }
assert view.index_u8(needle) == offset - start
assert view.last_index_u8(needle) == offset - start
}
}
bytes[offset] = needle ^ 1
}
}
}
+62 -12
View File
@@ -4331,6 +4331,22 @@ fn transform_scope_fits_retention_budget(scope voidptr) bool {
return false
}
// Keep helper text alive through cgen only when all its arenas fit one budget.
// A settled arena's size cannot grow after the transform workers have joined.
fn transform_helper_scopes_fit_retention_budget(scopes []voidptr) bool {
$if prealloc {
if scopes.len == 0 { return false }
mut remaining := usize(256 * 1024 * 1024)
for scope in scopes {
size := unsafe { prealloc_scope_allocated_size(scope) }
if size == 0 || size > remaining { return false }
remaining -= size
}
return true
}
return false
}
fn should_parallel_monomorphize() bool {
// Compiler executables built by TinyCC can corrupt their heap while several
// specialization workers merge their results. Keep that build serial until
@@ -13581,9 +13597,9 @@ pub fn run(args []string) {
mut retained_transform_scope := unsafe { nil }
mut retained_transform_prepare_scope := unsafe { nil }
mut retained_transform_prescan_scopes := []voidptr{}
// Helper arenas of the transform stage whose texts are published, kept until
// the type cache that can name their memory is dropped.
// Helper arenas can back both checker caches and lowered AST text.
mut transform_helper_scopes := []voidptr{}
mut retain_transform_helper_texts := false
mut trivial_literal_output := false
if !cgen_cache_hit {
pre_tc.verbose = prefs.verbose
@@ -14421,14 +14437,22 @@ pub fn run(args []string) {
if retained_transform_prepare_scope != unsafe { nil }
|| retained_transform_prescan_scopes.len > 0 {
// The preparation and pre-scan arenas back only a few thousand lowered
// node texts. Publish those into the compilation arena and release the
// arenas now instead of keeping their index scratch alive through codegen.
// node texts. Publish those before releasing large helpers; bounded
// helpers can share the retained transform arena's lifetime through codegen.
mut helper_scopes := []voidptr{cap: retained_transform_prescan_scopes.len + 1}
if retained_transform_prepare_scope != unsafe { nil } {
helper_scopes << retained_transform_prepare_scope
}
helper_scopes << retained_transform_prescan_scopes
publish_transform_helper_scope_texts(mut a, helper_scopes)
// Retained self-host transform state already outlives cgen. A bounded
// set of helper arenas can share that lifetime, avoiding an AST scan
// and text copies. Larger helpers keep the publication path below.
retain_transform_helper_texts = retain_scope_without_regions
&& retained_transform_regions.len == 0
&& transform_helper_scopes_fit_retention_budget(helper_scopes)
if !retain_transform_helper_texts {
publish_transform_helper_scope_texts(mut a, helper_scopes)
}
// The type cache of this stage holds lookups keyed by the texts of
// these arenas. It is dropped before the transform arena is released,
// or kept with that arena through Cgen: release them with it.
@@ -15297,14 +15321,15 @@ pub fn run(args []string) {
// Rebuild fork-shared lookup indexes in Cgen's parent arena. Scoped serial
// batches otherwise can inherit map storage populated by an earlier arena.
pre_tc.set_fresh_type_cache(cgen_parse_cache_enabled)
// The transform helper arenas were kept only for the transform-stage type
// cache, whose keys can name their texts; that cache is gone now. Their node
// texts were published, so release them before Cgen's peak. Unmapping them
// takes a few milliseconds; a helper thread does it while cgen starts.
if transform_helper_scopes.len > 0 {
track_release_thread(spawn free_prealloc_scopes(transform_helper_scopes))
// The transform cache is gone. Helpers whose node texts were published
// can now be released; retained helpers still back node texts through Cgen.
// A helper thread unmaps the published arenas while Cgen starts.
if !retain_transform_helper_texts {
if transform_helper_scopes.len > 0 {
track_release_thread(spawn free_prealloc_scopes(transform_helper_scopes))
}
transform_helper_scopes = []voidptr{}
}
transform_helper_scopes = []voidptr{}
pre_tc.freeze_type_cache_for_forks()
pre_tc.discard_type_cache_overlay_after_forks()
mut scoped_generated_c_flags := []string{}
@@ -15843,6 +15868,8 @@ pub fn run(args []string) {
}
b.print_report()
wait_release_threads()
free_prealloc_scopes(transform_helper_scopes)
transform_helper_scopes = []voidptr{}
if retained_transform_scope != unsafe { nil }
|| retained_transform_prepare_scope != unsafe { nil } {
free_retained_scopes(retained_transform_scope, retained_transform_prepare_scope)
@@ -15855,6 +15882,10 @@ pub fn run(args []string) {
// Nothing below reads the transformed AST or its checker payloads: the C
// compiler consumes the generated file. Release those arenas alongside it
// so their unmapping is not serial work after the compile.
if transform_helper_scopes.len > 0 {
track_release_thread(spawn free_prealloc_scopes(transform_helper_scopes))
transform_helper_scopes = []voidptr{}
}
if retained_transform_scope != unsafe { nil }
|| retained_transform_prepare_scope != unsafe { nil } {
track_release_thread(spawn free_retained_scopes(retained_transform_scope,
@@ -24011,6 +24042,17 @@ fn resolve_eager_selfhost_wave(a &flat.FlatAst, prefs &pref.Preferences, request
return results
}
// Ordinary code bytes cannot change the import scan's lexical context.
const import_scan_delimiters = make_import_scan_delimiters()
fn make_import_scan_delimiters() [256]bool {
mut delimiters := [256]bool{}
for c in [`\n`, `/`, `r`, `'`, `"`, `\``, `{`, `}`] {
delimiters[c] = true
}
return delimiters
}
// source_imports_fast extracts the compiler tree's one-line import declarations
// without constructing tokens. It is used only to assemble the trusted
// -building-v parse batch; the real parser remains authoritative immediately
@@ -24088,6 +24130,14 @@ fn source_imports_fast(path string) []string {
continue
}
for i < source.len {
if !at_line_head {
for i < source.len && !import_scan_delimiters[source[i]] {
i++
}
if i == source.len {
break
}
}
c := source[i]
if c == `\n` {
at_line_head = true
+16
View File
@@ -101,3 +101,19 @@ after\'
assert source_imports_fast(path) == ['real_module']
}
fn test_source_imports_fast_skips_ordinary_runs_between_context_delimiters() {
path := os.join_path(os.temp_dir(), 'v3_eager_import_runs_${os.getpid()}.v')
defer { os.rm(path) or {} }
dollar := '$'
source := 'module fixture\nimport first\n' +
'fn ordinary(value int) { abcdefghijklmnopqrstuvwxyz_0123456789 := value }\n' +
'// ordinary bytes before a fake import\n' +
"const quoted = 'plain text\nimport fake_quoted\n'\n" +
"const raw = r'raw text\nimport fake_raw\n'\n" +
'/* ordinary block /* nested */\nimport fake_comment\n*/\n' +
"const interpolated = 'ordinary ${dollar}{call('nested')}\nimport fake_tail\n'\n" +
'\timport second.module\nimport third'
os.write_file(path, source)!
assert source_imports_fast(path) == ['first', 'second.module', 'third']
}
@@ -12,3 +12,26 @@ fn test_transform_scope_retention_budget_bounds_allocation_size() {
fn test_transform_scope_retention_requires_an_actual_arena() {
assert !transform_scope_fits_retention_budget(unsafe { nil })
}
fn test_helper_scope_retention_checks_the_combined_budget() {
assert !transform_helper_scopes_fit_retention_budget([]voidptr{})
assert !transform_helper_scopes_fit_retention_budget([voidptr(unsafe { nil })])
$if prealloc {
first := prealloc_scope_begin_for_v3()
text := 'borrowed helper text'.clone()
prealloc_scope_leave_for_v3(first)
second := prealloc_scope_begin_for_v3()
prealloc_scope_leave_for_v3(second)
defer {
prealloc_scope_free_for_v3(second)
prealloc_scope_free_for_v3(first)
}
assert transform_helper_scopes_fit_retention_budget([first, second])
assert text == 'borrowed helper text'
// Repeating a real arena exercises accumulation without a large allocation.
size := unsafe { prealloc_scope_allocated_size(first) }
count := int(usize(256 * 1024 * 1024) / size)
assert transform_helper_scopes_fit_retention_budget([]voidptr{len: count, init: first})
assert !transform_helper_scopes_fit_retention_budget([]voidptr{len: count + 1, init: first})
}
}
+6
View File
@@ -19,6 +19,8 @@ fn C.atomic_store_u64(voidptr, u64)
fn C.atomic_load_u64(voidptr) u64
fn C.cpu_relax()
@[inline]
fn payload_ptr_get(base voidptr, index usize) voidptr {
// The payload chunks are arrays of pointers with validated indices.
@@ -74,6 +76,10 @@ fn node_payload_lock() {
if C.atomic_compare_exchange_strong_u32(&g_node_payload_lock, &expected, 1) {
break
}
// Poll without taking exclusive cache-line ownership while a writer runs.
for C.atomic_load_u32(&g_node_payload_lock) != 0 {
C.cpu_relax()
}
}
}
+18 -2
View File
@@ -19,8 +19,24 @@ fn test_source_type_recent_id_collisions_recheck_the_owned_spelling() {
left := source_type_id(text)
right := source_type_id('recent_collision_rite')
assert left != right
// A valid ID for another spelling is a cache collision, never an answer.
g_source_type_recent_ids[source_type_recent_slot(text)] = right
// A valid owned entry for another spelling is a cache collision, never an answer.
g_source_type_recent_entries[source_type_recent_slot(text)] = source_type_entry(source_type_table_load(), right)
assert source_type_id(text) == left
assert source_type_name(right) == 'recent_collision_rite'
}
fn test_source_type_recent_ids_accept_bounded_unaligned_views() {
for length in 1 .. 81 {
for offset in 0 .. 16 {
mut bytes := []u8{len: offset + length, init: `a` + u8(index % 23)}
view := unsafe { tos(&u8(bytes.data) + offset, length) }
text := view.clone()
id := source_type_id(view)
assert source_type_name(id) == text
assert source_type_id(text) == id
bytes[offset + length - 1] = `Z`
assert source_type_id(view) != id
assert source_type_name(id) == text
}
}
}
+58 -15
View File
@@ -41,6 +41,7 @@ struct SourceTypeEntry {
text string
hash u64
shape SourceTypeShape
id u32
}
// SourceTypeShape records spelling syntax once when a type is interned.
@@ -72,6 +73,9 @@ pub enum SourceTypeShape {
pointer_map
pointer_option
pointer_chain
bare_generic
anonymous_struct
generic_c_name
}
fn source_type_spelling_shape(text string) SourceTypeShape {
@@ -109,6 +113,15 @@ fn source_type_spelling_shape(text string) SourceTypeShape {
'u64', 'f32', 'f64', 'rune', 'isize', 'usize', 'voidptr', 'byteptr', 'charptr'] {
shape.set(.builtin_alias)
}
if text == 'struct' || text.ends_with('.struct') { shape.set(.anonymous_struct) }
if text.contains('_T_') { shape.set(.generic_c_name) }
mut start := 0
mut end := text.len
for start < end && source_type_space(text[start]) { start++ }
for end > start && source_type_space(text[end - 1]) { end-- }
if end - start == 1 && text[start] >= `A` && text[start] <= `Z` && text[start] != `C` {
shape.set(.bare_generic)
}
return shape
}
@@ -130,7 +143,7 @@ struct SourceTypeIndex {
__global g_source_types &SourceTypeTable
__global g_source_types_lock u32
__global g_source_type_recent_ids [4096]u32
__global g_source_type_recent_entries [16384]voidptr
fn source_type_lock() {
for {
@@ -138,6 +151,10 @@ fn source_type_lock() {
if C.atomic_compare_exchange_strong_u32(&g_source_types_lock, &expected, 1) {
return
}
// Readers need no lock; contending writers only retry once it is free.
for C.atomic_load_u32(&g_source_types_lock) != 0 {
C.cpu_relax()
}
}
}
@@ -236,34 +253,56 @@ fn source_type_find(table &SourceTypeTable, index &SourceTypeIndex, text string,
// Zero denotes an empty spelling. IDs are spelling identities, not checked types:
// resolving an unqualified name still requires its module and import context.
// Existing spellings are read without taking the insertion lock.
@[direct_array_access]
@[direct_array_access; inline]
pub fn source_type_id(text string) u32 {
if text.len == 0 {
return 0
}
// Only immutable process-owned IDs are cached. Inputs can be borrowed from
// Only immutable process-owned entries are cached. Inputs can be borrowed from
// a disposable arena, so every hit still checks the canonical spelling.
slot := source_type_recent_slot(text)
cached_id := C.atomic_load_u32(&g_source_type_recent_ids[slot])
if cached_id != 0 {
cached := source_type_name(cached_id)
if cached.len == text.len && (cached.str == text.str
|| unsafe { C.memcmp(cached.str, text.str, usize(text.len)) == 0 }) {
return cached_id
}
cached := source_type_recent_entry(slot)
if !isnil(cached) && cached.text == text {
return cached.id
}
id := source_type_id_uncached(text)
C.atomic_store_u32(&g_source_type_recent_ids[slot], id)
entry := source_type_entry(source_type_table_load(), id)
$if x32 {
C.atomic_store_u32(voidptr(&g_source_type_recent_entries[slot]), u32(voidptr(entry)))
} $else {
C.atomic_store_u64(voidptr(&g_source_type_recent_entries[slot]), u64(voidptr(entry)))
}
return id
}
@[direct_array_access; inline]
fn source_type_recent_entry(slot int) &SourceTypeEntry {
$if x32 {
return unsafe { &SourceTypeEntry(voidptr(C.atomic_load_u32(voidptr(&g_source_type_recent_entries[slot])))) }
} $else {
return unsafe { &SourceTypeEntry(voidptr(C.atomic_load_u64(voidptr(&g_source_type_recent_entries[slot])))) }
}
}
@[direct_array_access; inline]
fn source_type_recent_slot(text string) int {
// Equal spellings share a slot even when callers borrow different buffers.
// The complete canonical spelling is still checked before accepting a hit.
key := u32(text.len) ^ (u32(text[0]) << 1) ^ (u32(text[text.len >> 1]) << 5) ^
(u32(text[text.len - 1]) << 9)
return int((key ^ (key >> 12)) & 4095)
mut key := u64(text.len)
if text.len >= 8 {
mut first := u64(0)
mut last := u64(0)
// Word copies stay within the borrowed slice, including unaligned views.
unsafe {
vmemcpy(&first, text.str, 8)
vmemcpy(&last, text.str + text.len - 8, 8)
}
key ^= first ^ (last * u64(0x9e3779b97f4a7c15))
} else {
key ^= (u64(text[0]) << 8) ^ (u64(text[text.len >> 1]) << 16) ^ (u64(text[text.len - 1]) << 24)
}
key = (key ^ (key >> 32)) * u64(0x9e3779b97f4a7c15)
return int((key >> 32) & 16383)
}
fn source_type_id_uncached(text string) u32 {
@@ -278,6 +317,9 @@ fn source_type_id_uncached(text string) u32 {
return id
}
}
// Spelling classification is independent of the shared table. Keep it out
// of the insertion critical section so other writers can publish meanwhile.
shape := source_type_spelling_shape(text)
source_type_lock()
defer { C.atomic_store_u32(&g_source_types_lock, 0) }
table = source_type_table_load()
@@ -334,7 +376,8 @@ fn source_type_id_uncached(text string) u32 {
*entry = SourceTypeEntry{
text: tos(bytes, text.len)
hash: hashed
shape: source_type_spelling_shape(text)
shape: shape
id: id
}
}
C.atomic_store_u32(&table.count, id)
+84
View File
@@ -1,5 +1,54 @@
module flat
fn publish_source_types_in_parallel(names []string, ready chan bool, start chan bool) []u32 {
ready <- true
_ := <-start
mut ids := []u32{cap: names.len}
for name in names {
id := source_type_id(name)
assert source_type_name(id) == name
assert source_type_shape(id).has(.map_type)
payload := node_payload_with_type_metadata([name], id, [id])
entry := node_payload_at(payload)
assert entry.generic_params == [name]
assert entry.enum_backing_type == id
assert entry.debugger_value_types.ids == [id]
ids << id
}
return ids
}
fn test_source_type_and_payload_publication_under_writer_contention() {
mut names := []string{cap: 2048}
for n in 0 .. 2048 {
names << 'map[string][]parallel_publication.Item${n}'
}
ready := chan bool{cap: 4}
start := chan bool{cap: 4}
mut workers := []thread []u32{}
for _ in 0 .. 4 {
workers << spawn publish_source_types_in_parallel(names, ready, start)
}
for _ in 0 .. 4 {
_ := <-ready
}
for _ in 0 .. 4 {
start <- true
}
mut expected := []u32{}
for worker in workers {
ids := worker.wait()
if expected.len == 0 {
expected = ids.clone()
} else {
assert ids == expected
}
}
for n, name in names {
assert source_type_id(name) == expected[n]
}
}
fn test_type_text_forks_share_immutable_spelling_ids() {
mut parent := TypeTextBindings{}
parent.set('value', 'pkg.Sum')
@@ -64,6 +113,24 @@ fn test_type_text_pointer_probe_validates_reused_input_bytes() {
assert texts.text(second) == '[]Other'
}
fn test_source_type_recent_entry_checks_bytes_and_survives_table_growth() {
mut bytes := 'cache.Abc'.bytes()
borrowed := unsafe { tos(bytes.data, bytes.len) }
first := source_type_id(borrowed)
// Changing an unsampled middle byte deliberately preserves the cache slot.
bytes[6] = `x`
second := source_type_id(borrowed)
assert first != second
assert source_type_name(first) == 'cache.Abc'
assert source_type_name(second) == 'cache.xbc'
for i in 0 .. 12000 {
source_type_id('cache.Growing${i}')
}
assert source_type_id('cache.Abc'.clone()) == first
assert source_type_id('cache.xbc'.clone()) == second
assert source_type_shape(first).has(.dotted)
}
fn test_ast_type_ids_use_the_shared_spelling_namespace() {
mut first := FlatAst.new()
first.intern_text('ordinary payload')
@@ -192,3 +259,20 @@ fn test_source_type_prefix_flags_describe_raw_spelling() {
assert !source_type_shape(source_type_id('threaded')).has(.thread_type)
assert !source_type_shape(source_type_id('typeof(')).has(.typeof_expr)
}
fn test_source_type_shape_caches_spelling_only_generic_probes() {
for name in ['T', ' A ', '\nZ\t', 'Q'] {
assert source_type_shape(source_type_id(name)).has(.bare_generic)
}
for name in ['', 'C', ' C ', 'JS', 't', 'TT', 'pkg.T', '[T]', 'T\x00'] {
assert !source_type_shape(source_type_id(name)).has(.bare_generic)
}
for name in ['struct', 'pkg.struct', 'one.two.struct'] {
assert source_type_shape(source_type_id(name)).has(.anonymous_struct)
}
for name in ['Struct', 'astruct', 'struct ', 'pkg.struct.field', 'x__struct'] {
assert !source_type_shape(source_type_id(name)).has(.anonymous_struct)
}
assert source_type_shape(source_type_id('mod__fn_T_int')).has(.generic_c_name)
assert !source_type_shape(source_type_id('mod__fn_Tint')).has(.generic_c_name)
}
+33 -47
View File
@@ -46,22 +46,16 @@ const body_method_query_slots = 256
// c_short_name_view returns the suffix after the final dot without allocating.
@[direct_array_access; inline]
fn c_short_name_view(name string) string {
for i := name.len - 1; i >= 0; i-- {
if name[i] == `.` {
return unsafe { name.substr_unsafe(i + 1, name.len) }
}
}
i := name.last_index_u8(`.`)
if i >= 0 { return unsafe { name.substr_unsafe(i + 1, name.len) } }
return name
}
// c_owner_name_view borrows the prefix before the final dot for local name comparisons.
@[direct_array_access; inline]
fn c_owner_name_view(name string) string {
for i := name.len - 1; i >= 0; i-- {
if name[i] == `.` {
return unsafe { name.substr_unsafe(0, i) }
}
}
i := name.last_index_u8(`.`)
if i >= 0 { return unsafe { name.substr_unsafe(0, i) } }
return name
}
@@ -1604,6 +1598,10 @@ fn (g &FlatGen) local_storage_is_shared(name string) bool {
return false
}
owner := g.local_storage_owner(name) or { return false }
return g.binding_storage_is_shared(name, owner)
}
fn (g &FlatGen) binding_storage_is_shared(name string, owner types.ScopeBindingOwner) bool {
// Most functions declare no shared locals; skip hashing the owner key then.
if g.local_shared_storage_by_owner.len > 0
&& g.local_shared_storage_by_owner[owner.storage_key()] {
@@ -14836,7 +14834,7 @@ fn (mut g FlatGen) register_const_value(module_name string, file string, name st
// const_primary_name supports const primary name handling for FlatGen.
fn (g &FlatGen) const_primary_name(name string) string {
mod := if name in g.const_modules { g.const_modules[name] } else { '' }
mod := g.const_modules[name] or { '' }
qname := g.const_storage_name(mod, name)
if qname != name && qname in g.const_vals {
return qname
@@ -15025,8 +15023,8 @@ fn (g &FlatGen) build_unique_const_ref_names() map[string]string {
}
short_name := name.all_after_last('.')
resolved := g.const_primary_name(name)
if short_name in unique_index {
if unique_index[short_name] != resolved {
if existing := unique_index[short_name] {
if existing != resolved {
unique_index[short_name] = ''
}
} else {
@@ -15054,8 +15052,8 @@ fn (g &FlatGen) const_ref_name_fast_for_collect(name string, unique_index map[st
return g.const_primary_name(name)
}
}
if name in unique_index {
return unique_index[name]
if unique := unique_index[name] {
return unique
}
return ''
}
@@ -15085,8 +15083,8 @@ fn (g &FlatGen) const_ref_name_from_node_cached_for_collect(node flat.Node, uniq
return ''
}
cache_key := '${g.tc.cur_module}|ident|${node.value}'
if cache_key in cache {
return cache[cache_key]
if cached := cache[cache_key] {
return cached
}
const_name := g.const_ref_name_fast_for_collect(node.value, unique_index)
cache[cache_key] = const_name
@@ -15108,8 +15106,8 @@ fn (g &FlatGen) const_ref_name_from_node_cached_for_collect(node flat.Node, uniq
return ''
}
cache_key := '${g.tc.cur_file}|${g.tc.cur_module}|selector|${base.value}|${node.value}'
if cache_key in cache {
return cache[cache_key]
if cached := cache[cache_key] {
return cached
}
resolved_base := g.selector_base_module_for_member(base.value, node.value) or {
base.value
@@ -15121,22 +15119,12 @@ fn (g &FlatGen) const_ref_name_from_node_cached_for_collect(node flat.Node, uniq
return ''
}
@[direct_array_access]
fn (g &FlatGen) fixed_storage_candidate_short_name(name string) string {
mut sep := -1
mut i := name.len - 1
for i >= 0 {
if name[i] == `.` {
return unsafe { name.substr_unsafe(i + 1, name.len) }
}
if sep < 0 && i > 0 && name[i] == `_` && name[i - 1] == `_` {
sep = i - 1
i--
}
i--
if dot := name.last_index('.') {
return unsafe { name.substr_unsafe(dot + 1, name.len) }
}
if sep >= 0 {
return unsafe { name.substr_unsafe(sep + 2, name.len) }
if separator := name.last_index('__') {
return unsafe { name.substr_unsafe(separator + 2, name.len) }
}
return name
}
@@ -15242,8 +15230,8 @@ fn (g &FlatGen) const_ref_node_may_match_fixed_candidate(node &flat.Node, ident_
}
fn (g &FlatGen) const_primary_name_cached(name string, mut cache map[string]string) string {
if name in cache {
return cache[name]
if cached := cache[name] {
return cached
}
primary := g.const_primary_name(name)
cache[name] = primary
@@ -15251,8 +15239,8 @@ fn (g &FlatGen) const_primary_name_cached(name string, mut cache map[string]stri
}
fn (mut g FlatGen) const_ref_has_fixed_array_literal_storage(const_name string, mut cache map[string]bool) bool {
if const_name in cache {
return cache[const_name]
if cached := cache[const_name] {
return cached
}
mut has_fixed := false
if val_id := g.const_vals[const_name] {
@@ -16583,14 +16571,12 @@ fn (mut g FlatGen) gen_nonliteral_expr(id flat.NodeId, node &flat.Node) {
return
}
}
is_current_param := g.current_param_type(node.value) != none
is_local := if is_current_param {
true
} else if owner := g.tc.cur_scope.lookup_owner(node.value) {
!owner.belongs_to_scope(g.tc.file_scope)
} else {
false
}
owner := g.local_storage_owner(node.value) or { types.ScopeBindingOwner{} }
owner_key := owner.storage_key()
scoped_local := owner_key.len > 0 && !owner.belongs_to_scope(g.tc.file_scope)
// A scoped local already has value precedence, including parameters.
is_current_param := !scoped_local && g.current_param_type(node.value) != none
is_local := scoped_local || is_current_param
current_const_name := if !is_local && !is_current_param
&& !g.local_shadows_global(node.value) {
g.current_module_const_ref_name(node.value) or { '' }
@@ -16621,10 +16607,10 @@ fn (mut g FlatGen) gen_nonliteral_expr(id flat.NodeId, node &flat.Node) {
g.write('->val')
}
}
} else if g.local_storage_is_shared(node.value) {
} else if owner_key.len > 0 && g.binding_storage_is_shared(node.value, owner) {
g.write(g.local_cname(node.value))
g.write('->val')
} else if g.local_storage_needs_implicit_deref(node.value) {
} else if owner_key.len > 0 && g.local_implicit_deref_by_owner[owner_key] {
g.write('(*${g.local_decl_cname(node.value)})')
} else if is_current_param && g.local_name_needs_global_suffix(node.value) {
g.write(g.local_decl_cname(node.value))
+49
View File
@@ -36,3 +36,52 @@ fn test_bare_main_and_builtin_globals_keep_value_precedence_after_foreign_owner(
assert g.current_module_selector_const_name(current, 'value') == none
}
}
fn test_ident_emission_keeps_scoped_and_parameter_value_precedence() {
for scoped in [false, true] {
mut a := flat.FlatAst.new()
id := a.add_val(.ident, 'value')
mut tc := types.TypeChecker.new(&a)
tc.cur_module = 'main'
tc.cur_scope = types.new_scope(tc.file_scope)
if scoped {
tc.cur_scope.insert('value', types.Type(types.int_))
}
mut g := FlatGen.new()
g.a = &a
g.tc = &tc
g.global_types['value'] = types.Type(types.int_)
g.global_modules['value'] = 'main'
g.const_vals['value'] = flat.NodeId(1)
g.const_modules['value'] = 'main'
g.cur_param_types['value'] = &types.Type(types.int_)
expected := g.local_decl_cname('value')
g.gen_nonliteral_expr(id, a.node(id))
assert g.sb.str() == expected
}
}
fn test_ident_emission_uses_nearest_binding_storage_flags() {
for shared_storage in [false, true] {
mut a := flat.FlatAst.new()
id := a.add_val(.ident, 'value')
mut tc := types.TypeChecker.new(&a)
tc.cur_module = 'main'
tc.cur_scope = types.new_scope(tc.file_scope)
outer := tc.cur_scope.insert_with_owner('value', types.Type(types.int_))
mut g := FlatGen.new()
g.a = &a
g.tc = &tc
if shared_storage {
g.declare_local_shared_storage(outer, true)
} else {
g.declare_local_implicit_deref(outer, true)
}
g.gen_nonliteral_expr(id, a.node(id))
assert g.sb.str() == if shared_storage { 'value->val' } else { '(*value)' }
tc.cur_scope = types.new_scope(tc.cur_scope)
tc.cur_scope.insert('value', types.Type(types.int_))
g.gen_nonliteral_expr(id, a.node(id))
assert g.sb.str() == 'value'
}
}
+20 -4
View File
@@ -11190,6 +11190,7 @@ fn (g &FlatGen) embedded_receiver_type_names_match(actual_id u32, expected_id u3
}
// current_param_type returns current param type data for FlatGen.
@[inline]
fn (g &FlatGen) current_param_type(name string) ?&types.Type {
if g.cur_param_types.len == 0 {
return none
@@ -11197,6 +11198,10 @@ fn (g &FlatGen) current_param_type(name string) ?&types.Type {
if g.cur_param_name_bits != 0 && g.cur_param_name_bits & current_param_name_bit(name) == 0 {
return none
}
return g.current_param_type_present(name)
}
fn (g &FlatGen) current_param_type_present(name string) ?&types.Type {
typ := g.current_param_type_value(name)?
if g.cur_mut_params.len > 0 && g.current_mut_param_binding_is_shadowed(name) {
return none
@@ -17160,12 +17165,23 @@ fn (mut g FlatGen) c_extern_referenced_symbols() map[string]bool {
return g.c_extern_refs
}
@[inline]
fn (g &FlatGen) collect_c_extern_referenced_symbols_from_node(id flat.NodeId, mut refs map[string]bool) {
if int(id) < 0 || int(id) >= g.a.nodes.len {
return
}
node := g.a.node(id)
g.collect_c_extern_ref_from_node_into(node, mut refs)
// Symbol collection does not grow the AST, so recursive calls can borrow nodes.
node := unsafe { &g.a.nodes[int(id)] }
if (node.kind == .ident && node.value.starts_with('C.'))
|| (node.kind == .selector && node.children_count > 0 && node.value.len > 0) {
g.collect_c_extern_ref_from_node_into(node, mut refs)
}
if node.children_count > 0 {
g.collect_c_extern_referenced_symbols_from_children(node, mut refs)
}
}
fn (g &FlatGen) collect_c_extern_referenced_symbols_from_children(node &flat.Node, mut refs map[string]bool) {
for i in 0 .. node.children_count {
g.collect_c_extern_referenced_symbols_from_node(g.a.child(node, i), mut refs)
}
@@ -19802,8 +19818,8 @@ fn (mut g FlatGen) resolve_fn_ptr_type(typ string) string {
}
fn (mut g FlatGen) register_fn_ptr_type(typ string) string {
if typ in g.fn_ptr_types {
return g.fn_ptr_types[typ]
if cached := g.fn_ptr_types[typ] {
return cached
}
name := naming.fn_ptr_type_name(typ)
g.fn_ptr_types[typ] = name
@@ -3526,7 +3526,6 @@ fn (g &FlatGen) new_parallel_worker_config(worker_id int, result_only bool) &Fla
local_global_suffix_names_ready: g.local_global_suffix_names_ready
c_typedef_shadow_names: g.c_typedef_shadow_names
c_typedef_shadow_names_ready: g.c_typedef_shadow_names_ready
c_typedef_shadow_names_plain: g.c_typedef_shadow_names_plain
generic_app_cache: &GenericAppCache{
base: if !isnil(g.generic_app_cache.base) {
g.generic_app_cache.base
+13 -12
View File
@@ -293,13 +293,14 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
mut map_copyback_dirty_var := ''
mut map_copyback_guard := MapLoopCopybackGuard{}
if clean_container_type is types.Map {
c_key := g.map_key_temp_c_type(clean_container_type.key_type)
c_val := g.value_c_type(clean_container_type.value_type)
map_type := clean_container_type as types.Map
c_key := g.map_key_temp_c_type(map_type.key_type)
c_val := g.value_c_type(map_type.value_type)
container_str := g.expr_to_string(g.a.child(&node, 2))
storage_container_type := g.usable_expr_type(g.a.child(&node, 2))
container_storage_is_pointer := unalias_type(storage_container_type) is types.Pointer
container_is_mutable_value_storage := g.for_in_mutable_value_storage(container_id)
mut clean_value_type := *clean_container_type.value_type
mut clean_value_type := *map_type.value_type
for clean_value_type is types.Alias {
clean_value_type = clean_value_type.base_type
}
@@ -342,7 +343,7 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
g.gen_labelled_continue_skip_drops_var(label_state.label)
g.writeln('if (${key_values}.all_deleted && ${key_values}.all_deleted[${iter_var}]) continue;')
key_slot := '${key_values}.keys + ${iter_var} * ${key_values}.key_bytes'
if key_fixed := array_fixed_type(clean_container_type.key_type) {
if key_fixed := array_fixed_type(map_type.key_type) {
c_elem, dims := g.fixed_array_decl_parts(key_fixed)
g.writeln('${c_elem} ${key_var}${dims};')
g.writeln('memmove(${key_var}, ${key_slot}, sizeof(${key_var}));')
@@ -350,7 +351,7 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
} else {
g.writeln('${c_key} ${key_var} = *(${c_key}*)(${key_slot});')
if has_index && idx_binding_name != '_'
&& clean_container_type.key_type is types.String && !node.borrows_map_key() {
&& map_type.key_type is types.String && !node.borrows_map_key() {
// Authored keys may escape or survive map mutation. Equality
// keys only serve read-only lookups while both maps are alive.
g.writeln('${key_var} = string__clone(${key_var});')
@@ -366,7 +367,7 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
g.writeln('if (${val_slot_var} == 0) ${val_slot_var} = (void*)(${snapshot_val_slot});')
val_slot = val_slot_var
}
if val_fixed := array_fixed_type(clean_container_type.value_type) {
if val_fixed := array_fixed_type(map_type.value_type) {
c_elem, dims := g.fixed_array_decl_parts(val_fixed)
if map_value_by_ref {
g.writeln('${c_elem} (*${val_var_})${dims} = (${c_elem} (*)${dims})(${val_slot});')
@@ -381,17 +382,17 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
g.writeln('${c_val} ${val_var_} = *(${c_val}*)(${val_slot});')
}
if has_index {
key_owner := g.tc.cur_scope.insert_with_owner(idx_binding_name, clean_container_type.key_type)
key_owner := g.tc.cur_scope.insert_with_owner(idx_binding_name, map_type.key_type)
g.track_shadowed_global_local(idx_binding_name, key_owner)
g.declare_local_pointer_storage(key_owner, clean_container_type.key_type is types.Pointer
g.declare_local_pointer_storage(key_owner, map_type.key_type is types.Pointer
|| c_type_is_pointer_storage(c_key))
}
val_scope_type := if map_value_by_ref && !val_is_fixed_copy {
types.Type(types.Pointer{
base_type: clean_container_type.value_type
base_type: map_type.value_type
})
} else {
*clean_container_type.value_type
*map_type.value_type
}
val_owner := g.tc.cur_scope.insert_with_owner(elem_binding_name, val_scope_type)
g.track_shadowed_global_local(elem_binding_name, val_owner)
@@ -399,10 +400,10 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
&& !(container_storage_is_pointer && !container_is_mutable_value_storage
&& !ref_container_keeps_value))
if map_value_by_ref && !val_is_fixed_copy {
g.declare_local_indirect_value_type(val_owner, clean_container_type.value_type)
g.declare_local_indirect_value_type(val_owner, map_type.value_type)
}
g.declare_local_pointer_storage(val_owner, val_scope_type is types.Pointer
|| (!val_is_fixed_copy && clean_container_type.value_type is types.Pointer)
|| (!val_is_fixed_copy && map_type.value_type is types.Pointer)
|| c_type_is_pointer_storage(c_val))
if node.op == .amp && val_is_fixed_copy {
map_writeback_target = if container_storage_is_pointer {
+67 -60
View File
@@ -21,56 +21,60 @@ mut:
base &CNameCache = unsafe { nil }
last_name string
last_value string
recent_ptrs [1024]voidptr
recent_lens [1024]u16
recent_names [1024]string
recent_values [1024]string
recent_populated [1024]bool
}
@[inline]
@[direct_array_access; inline]
fn c_name_recent_slot(name string) int {
return int(((u64(voidptr(name.str)) >> 4) ^ u64(name.len)) & 1023)
}
// c_name_is_pre_sanitized reports names that are already unambiguously in V's
// module-qualified C spelling. C keywords, libc collisions and special builtin
// rewrites never contain `__`, so these names can bypass the map-backed slow
// path after their direct-cache miss.
@[direct_array_access; inline]
fn c_name_is_pre_sanitized(name string) bool {
mut previous_underscore := false
mut has_double_underscore := false
for i in 0 .. name.len {
c := name[i]
if (c < `a` || c > `z`) && (c < `A` || c > `Z`) && (c < `0` || c > `9`) && c != `_` {
return false
}
if c == `_` {
if previous_underscore {
has_double_underscore = true
}
previous_underscore = true
} else {
previous_underscore = false
}
if name.len == 0 {
return 0
}
return has_double_underscore
mut key := u64(name.len)
if name.len >= 8 {
mut first := u64(0)
mut last := u64(0)
// Copying bounded words also permits unaligned source slices.
unsafe {
vmemcpy(&first, name.str, 8)
vmemcpy(&last, name.str + name.len - 8, 8)
}
key ^= first ^ (last * u64(0x9e3779b97f4a7c15))
} else {
key ^= (u64(name[0]) << 8) ^ (u64(name[name.len >> 1]) << 16) ^ (u64(name[name.len - 1]) << 24)
}
key = (key ^ (key >> 32)) * u64(0x9e3779b97f4a7c15)
return int((key >> 32) & 1023)
}
// c_name_shape classifies the identifier and dotted fast paths in one scan.
// Bit 0 is a plain spelling, bit 1 a double underscore, and bit 2 a dot.
@[direct_array_access; inline]
fn c_name_is_plain_dotted(name string) bool {
mut has_dot := false
fn c_name_shape(name string) u8 {
mut shape := u8(1)
mut previous_underscore := false
for i in 0 .. name.len {
c := name[i]
if c == `.` {
has_dot = true
shape |= 4
previous_underscore = false
continue
}
if (c < `a` || c > `z`) && (c < `A` || c > `Z`) && (c < `0` || c > `9`) && c != `_` {
return false
return 0
}
if c == `_` && previous_underscore {
shape |= 2
}
previous_underscore = c == `_`
}
return has_dot
return shape
}
@[inline]
fn c_name_is_pre_sanitized(name string) bool {
return c_name_shape(name) == 3
}
@[direct_array_access; inline]
@@ -88,12 +92,8 @@ fn c_name_is_string_literal_symbol(name string) bool {
@[direct_array_access; inline]
fn (c &CNameCache) recent(name string) ?string {
if name.len > 65535 {
return none
}
slot := c_name_recent_slot(name)
if c.recent_populated[slot] && c.recent_ptrs[slot] == voidptr(name.str)
&& c.recent_lens[slot] == u16(name.len) {
if c.recent_populated[slot] && c.recent_names[slot] == name {
return c.recent_values[slot]
}
return none
@@ -101,12 +101,8 @@ fn (c &CNameCache) recent(name string) ?string {
@[direct_array_access; inline]
fn (mut c CNameCache) remember(name string, value string) {
if name.len > 65535 {
return
}
slot := c_name_recent_slot(name)
c.recent_ptrs[slot] = voidptr(name.str)
c.recent_lens[slot] = u16(name.len)
c.recent_names[slot] = name
c.recent_values[slot] = value
c.recent_populated[slot] = true
}
@@ -556,22 +552,11 @@ fn (g &FlatGen) cname(name string) string {
cache.last_value = cached
return cached
}
needs_internal_namespace := naming.c_name_needs_internal_namespace(name)
if !needs_internal_namespace && c_name_is_pre_sanitized(name) {
cache.last_name = name
cache.last_value = name
cache.remember(name, name)
return name
}
if !needs_internal_namespace && naming.is_plain_identifier(name) && name != 'malloc'
&& name != 'int_str' && name != 'exit'
&& !c_name_is_string_literal_symbol(name) && !naming.is_reserved_word(name)
&& !naming.is_libc_collision(name) {
cache.last_name = name
cache.last_value = name
cache.remember(name, name)
return name
}
return g.cname_uncached(name)
}
fn (g &FlatGen) cname_uncached(name string) string {
mut cache := g.c_name_cache
if cached := cache.entries[name] {
cache.last_name = name
cache.last_value = cached
@@ -592,7 +577,29 @@ fn (g &FlatGen) cname(name string) string {
return cached
}
}
if !needs_internal_namespace && !name.starts_with('C.') && c_name_is_plain_dotted(name) {
shape := c_name_shape(name)
// A plain spelling without `__` cannot contain the internal prefix, a static
// marker, or an operator method. Preserve those rewrites for other names.
needs_internal_namespace := (shape == 0 || shape & 2 != 0)
&& naming.c_name_needs_internal_namespace(name)
if !needs_internal_namespace && shape == 3 {
cache.entries[name] = name
cache.last_name = name
cache.last_value = name
cache.remember(name, name)
return name
}
if !needs_internal_namespace && shape == 1 && name != 'malloc'
&& name != 'int_str' && name != 'exit'
&& !c_name_is_string_literal_symbol(name) && !naming.is_reserved_word(name)
&& !naming.is_libc_collision(name) {
cache.entries[name] = name
cache.last_name = name
cache.last_value = name
cache.remember(name, name)
return name
}
if !needs_internal_namespace && !name.starts_with('C.') && shape & 4 != 0 {
result := naming.sanitize(name)
cache.entries[name] = result
cache.last_name = name
+76
View File
@@ -190,6 +190,45 @@ fn test_cached_cname_fast_paths_match_canonical_naming() {
}
}
fn test_cached_cname_classifier_preserves_special_names_and_bytes() {
mut g := FlatGen.new()
for name in ['', '.', '9name', 'a..b', 'a__b.c', '__v3_internal_symbol_x',
'__v3_internal_symbol_x.y', 'x@static@y', 'x.[]=', 'x.+', '_str_123', 'C.name', 'C.SSL_CTX.str',
'complex', 'int_str', 'malloc'] {
for _ in 0 .. 2 {
assert g.cname(name) == c_name(name), name
}
}
for byte in 0 .. 256 {
name := 'prefix' + u8(byte).ascii_str() + 'suffix'
assert g.cname(name) == c_name(name), byte.str()
assert g.cname(name.clone()) == c_name(name), byte.str()
}
}
fn test_cname_recent_cache_checks_content_and_reuses_equal_allocations() {
mut cache := CNameCache{}
name := 'pkg.first'
cache.remember(name, 'pkg__first')
assert cache.recent(name.clone())? == 'pkg__first'
// Search for a real slot collision; a collision must never supply another name's value.
mut collision := ''
for i in 0 .. 100000 {
candidate := 'pkg.name_${i}'
if candidate != name && c_name_recent_slot(candidate) == c_name_recent_slot(name) {
collision = candidate
break
}
}
assert collision != ''
assert cache.recent(collision) == none
cache.remember(collision, 'pkg__farst')
assert cache.recent(name) == none
assert cache.recent(collision.clone())? == 'pkg__farst'
cache.remember('', '')
assert cache.recent('')? == ''
}
fn test_c_name_sanitizes_compound_generic_type_arguments() {
name :=
c_name('json2.StructKeyDecodeResult[fn(&mbedtls.SSLListener, string) !&mbedtls.SSLCerts]')
@@ -1197,3 +1236,40 @@ fn test_linker_object_inputs_keep_headerless_prototypes() {
assert !c_flag_links_c_source(flag), flag
}
}
fn test_fixed_storage_candidate_short_name_uses_last_dot_before_c_separator() {
g := FlatGen{}
cases := {
'': ''
'plain': 'plain'
'mod.name': 'name'
'mod__name': 'name'
'mod__deep__name': 'name'
'mod__x.name__tail': 'name__tail'
'mod.name__tail': 'name__tail'
'a___b': 'b'
'a.': ''
'a__': ''
'a_b': 'a_b'
}
for name, expected in cases {
assert g.fixed_storage_candidate_short_name(name) == expected
}
}
fn test_cname_caches_unchanged_names_in_private_overlay() {
mut base := &CNameCache{}
mut g := FlatGen{ c_name_cache: base }
for name in ['ordinary_identifier', 'pkg__already_sanitized'] {
assert g.cname(name) == name
assert base.entries[name]! == name
}
g.c_name_cache = &CNameCache{ base: base }
for name in ['ordinary_identifier', 'pkg__already_sanitized'] {
assert g.cname(name.clone()) == name
assert name !in g.c_name_cache.entries
}
assert g.cname('other_identifier') == 'other_identifier'
assert 'other_identifier' !in base.entries
assert g.c_name_cache.entries['other_identifier']! == 'other_identifier'
}
+24 -1
View File
@@ -744,12 +744,20 @@ fn (g &FlatGen) collect_top_level_prelude_scan(stmts []TopLevelStmt) FnPreludeSc
return scan
}
@[inline]
fn (g &FlatGen) collect_prelude_scan_from(id flat.NodeId, mut scan FnPreludeScan, collect_defers bool) {
if int(id) < 0 || int(id) >= g.a.nodes.len {
return
}
// This traversal only records prelude facts and never grows the AST.
node := unsafe { &g.a.nodes[int(id)] }
if node.children_count == 0 && node.kind !in [.defer_stmt, .lock_expr, .label_stmt] {
return
}
g.collect_prelude_scan_node(id, node, mut scan, collect_defers)
}
fn (g &FlatGen) collect_prelude_scan_node(id flat.NodeId, node &flat.Node, mut scan FnPreludeScan, collect_defers bool) {
mut child_collect_defers := collect_defers
if collect_defers
&& (node.kind == .fn_decl || node.kind == .c_fn_decl || node.kind == .fn_literal) {
@@ -6994,7 +7002,7 @@ fn (mut g FlatGen) gen_generated_variant_access_selector(node flat.Node, base_id
return true
}
@[direct_array_access]
@[direct_array_access; inline]
fn (g &FlatGen) usable_expr_type(id flat.NodeId) &types.Type {
idx := int(id)
if idx >= 0 && !isnil(g.usable_expr_type_memo) {
@@ -7004,6 +7012,18 @@ fn (g &FlatGen) usable_expr_type(id flat.NodeId) &types.Type {
if memo.gens[slot] == memo.generation && memo.ids[slot] == idx {
return memo.values[slot]
}
}
}
return g.usable_expr_type_memo_miss(id)
}
@[direct_array_access]
fn (g &FlatGen) usable_expr_type_memo_miss(id flat.NodeId) &types.Type {
idx := int(id)
if idx >= 0 && !isnil(g.usable_expr_type_memo) {
mut memo := g.usable_expr_type_memo
if memo.active {
slot := idx & usable_expr_type_memo_mask
result := g.tc.intern_type_reference(g.usable_expr_type_uncached(id))
memo.ids[slot] = idx
memo.gens[slot] = memo.generation
@@ -10397,6 +10417,9 @@ fn local_name_shadows_c_runtime(name string) bool {
}
fn (g &FlatGen) local_shadows_global(name string) bool {
if g.shadowed_global_locals.len == 0 {
return false
}
owner := g.local_storage_owner(name) or { return false }
key := owner.storage_key()
return key.len > 0 && key in g.shadowed_global_locals
+2 -5
View File
@@ -5741,7 +5741,6 @@ fn (g &FlatGen) struct_field_type(type_name_id u32, field_name string) ?types.Ty
@[direct_array_access]
fn (g &FlatGen) struct_field_type_cached(type_name_id u32, field_name string) ?types.Type {
type_name := flat.source_type_name(type_name_id)
mut cache := g.struct_decl_pref_cache
cache_enabled := g.cache_struct_fields && !isnil(cache)
mut slot := 0
@@ -5750,10 +5749,8 @@ fn (g &FlatGen) struct_field_type_cached(type_name_id u32, field_name string) ?t
cache.select_module(g.tc.cur_module)
type_id = type_name_id
// The complete owner/field comparison keeps sampled-key collisions harmless.
mut hash := u32(type_name.len * 31 + field_name.len)
if type_name.len > 0 {
hash = hash * 16777619 ^ u32(type_name[type_name.len - 1])
}
mut hash := (type_name_id * u32(0x9e3779b9)) ^ u32(field_name.len)
hash ^= hash >> 16
if field_name.len > 0 {
hash = hash * 16777619 ^ u32(field_name[0])
hash = hash * 16777619 ^ u32(field_name[field_name.len - 1])
+10 -10
View File
@@ -1537,17 +1537,17 @@ fn (g &FlatGen) type_contains_generic_placeholder(t &types.Type) bool {
// Inferred anonymous literals are materialized during transform. Their
// earlier checker entry has no nominal storage type; prefer the concrete
// lowered annotation instead of emitting `v_struct` for its container.
if c_short_name_view(t.name()) == 'struct' {
shape := flat.source_type_shape(t.name_id)
if shape.has(.anonymous_struct) {
return true
}
// A stale generic-call annotation can carry the concrete C function name
// as a nominal type. It is not a payload type and must not create an
// `__v_option_<function>` typedef in the program prefix.
if t.name().index_u8(`_`) >= 0 && t.name().contains('_T_')
&& !g.type_name_known(t.name()) {
if shape.has(.generic_c_name) && !g.type_name_known(t.name()) {
return true
}
if !g.skip_generics && flat.source_type_shape(t.name_id).has(.bracketed)
if !g.skip_generics && shape.has(.bracketed)
&& g.type_name_is_unbound_generic_decl(t.source_type_id(), g.struct_generic_params_for_name(t.source_type_id()), t.name() in g.tc.structs || g.tc.qualify_name(t.name()) in g.tc.structs) {
return true
}
@@ -1630,13 +1630,13 @@ fn (g &FlatGen) type_name_contains_generic_placeholder(name_id u32) bool {
if g.skip_generics && !g.placeholder_check_forced {
return false
}
name := flat.source_type_name(name_id)
if !flat.source_type_shape(name_id).has(.bracketed) {
// Without brackets only a bare placeholder (`T`) qualifies.
clean := trimmed_space(name)
return clean.len > 0 && g.is_bare_generic_placeholder_name(clean)
shape := flat.source_type_shape(name_id)
if !shape.has(.bracketed) {
// Only the spelling is cached; declarations still decide whether `T` is bound.
return shape.has(.bare_generic)
&& g.is_bare_generic_placeholder_name(trimmed_space(flat.source_type_name(name_id)))
}
return g.type_name_contains_generic_placeholder_bytes(name)
return g.type_name_contains_generic_placeholder_bytes(flat.source_type_name(name_id))
}
fn (g &FlatGen) type_name_contains_generic_placeholder_bytes(name string) bool {
+28
View File
@@ -1309,3 +1309,31 @@ fn test_import_type_ids_preserve_padded_wrapper_normalization() {
assert flat.source_type_name(g.canonical_import_alias_type_text_in_file(flat.source_type_id(' & ? Foo '), 'clean.v')) == '&?Foo'
assert flat.source_type_name(g.canonical_import_alias_type_text_in_file(flat.source_type_id(' & ! Foo '), 'clean.v')) == '&!Foo'
}
fn test_field_type_cache_distinguishes_many_owners_with_the_same_field() {
mut ast := flat.FlatAst.new()
mut tc := types.TypeChecker.new(&ast)
tc.cur_module = 'main'
mut ids := []u32{}
for i in 0 .. 320 {
name := 'main.CacheOwner_${i}'
ids << flat.source_type_id(name)
tc.structs[name] = [types.StructField{
name: 'value'
typ: if i % 2 == 0 { types.Type(types.int_) } else { types.Type(types.string_) }
}]
}
mut g := FlatGen.new()
g.a = &ast
g.tc = &tc
g.cache_struct_fields = true
g.struct_decl_pref_cache = &StructDeclPrefCache{}
for _ in 0 .. 2 {
for i, id in ids {
expected := if i % 2 == 0 { types.Type(types.int_) } else { types.Type(types.string_) }
assert g.struct_field_type(id, 'value')? == expected
assert g.struct_field_type(id, 'missing') == none
assert g.struct_field_type(id, 'value')? == expected
}
}
}
+25 -3
View File
@@ -719,7 +719,21 @@ fn (g &Parser) render_nested_array_access_expression(tokens []FastcExpressionTok
needle += '[${next_index}]'
access_end = next_close
}
replacement := g.render_array_access_expression(tokens[i..access_end + 1]) or { continue }
mut member_end := access_end + 1
for member_end + 1 < tokens.len && tokens[member_end].tok == .dot
&& tokens[member_end + 1].tok == .name {
if member_end + 2 < tokens.len && tokens[member_end + 2].tok == .lpar { break }
member_end += 2
}
mut replacement := g.render_array_access_expression(tokens[i..access_end + 1]) or { continue }
if member_end > access_end + 1 {
if member := g.render_indexed_member_receiver(tokens[i..member_end]) {
for part in tokens[access_end + 1..member_end] {
needle += if part.tok == .name { part.lit } else { part.tok.str() }
}
replacement = member
}
}
if fastc_contains(rendered, needle) {
// A method receiver may already contain the lowered direct access
// `((s).str[i])`; the raw `s[i]` suffix there is a struct member, not
@@ -804,14 +818,22 @@ fn (g &Parser) render_indexed_member_receiver(tokens []FastcExpressionToken) ?Fa
}
field := g.struct_field_metadata(current_type, tokens[i + 1].lit) or { return none }
for storage_name in field.storage_path {
separator := if flat.source_type_name(current_type).ends_with('*') { '->' } else { '.' }
separator := if fastc_is_pointer_type(g.underlying_alias_type(current_type)) {
'->'
} else {
'.'
}
source += separator + fastc_c_identifier(storage_name)
current_type = g.struct_direct_member_type(current_type, storage_name)
if flat.source_type_name(current_type) == '' {
return none
}
}
separator := if flat.source_type_name(current_type).ends_with('*') { '->' } else { '.' }
separator := if fastc_is_pointer_type(g.underlying_alias_type(current_type)) {
'->'
} else {
'.'
}
field_source := source + separator + fastc_c_identifier(field.name)
source = if field.is_shared_pointer { '*(${field_source})' } else { field_source }
current_type = field.typ
+67 -7
View File
@@ -1837,6 +1837,20 @@ fn fastc_generate_constant_declarations(candidates []FastcSourceFile, prefs &pre
if os.getenv('FASTC_BENCH_PHASES') != '' {
eprintln('fastc-phase constants.detail candidates=${candidates.len} parallel_us=${parallel_us} serial=${serial_count} merge_us=${sw.elapsed().microseconds() - parallel_us}')
}
// Lowering a struct literal inserts field defaults that are absent from the
// constant's source. Include their references before ordering initializers.
mut constant_keys := map[string]string{}
for value in values {
constant_keys[value.c_name] = value.key
}
for index, value in values {
mut dependencies := value.dependencies.clone()
fastc_rendered_constant_dependencies(value.value, constant_keys, mut dependencies)
values[index] = FastcConstantValue{
...value
dependencies: dependencies
}
}
mut runtime_constants := map[string]bool{}
for value in values {
if value.is_runtime {
@@ -1975,6 +1989,36 @@ fn (mut g Parser) parse_selected_comptime_constant_declarations(mut values []Fas
}
}
// fastc_rendered_constant_dependencies collects references introduced by defaults,
// ignoring literals and member names in the generated C expression.
fn fastc_rendered_constant_dependencies(source string, keys map[string]string, mut dependencies []string) {
mut offset := 0
for offset < source.len {
if source[offset] in [`'`, `"`] {
offset = fastc_c_literal_end(source, offset)
continue
}
if !fastc_c_identifier_start_byte(source[offset]) {
offset++
continue
}
start := offset
offset++
for offset < source.len && fastc_identifier_byte(source[offset]) {
offset++
}
if start > 0 && (source[start - 1] == `.`
|| (source[start - 1] == `>` && start > 1 && source[start - 2] == `-`)) {
continue
}
if key := keys[source[start..offset]] {
if key !in dependencies {
dependencies << key
}
}
}
}
fn fastc_append_runtime_constant(index int, values []FastcConstantValue, runtime_constants map[string]bool, mut visiting []int, mut visited []int, mut ordered []int) ! {
if index in visited {
return
@@ -2926,7 +2970,7 @@ fn fastc_emit_struct_declaration(mut scan scanner.Scanner, is_union bool, attrib
field_names << scan.lit
tok = scan.scan()
}
if tok == .semicolon || tok == .rcbr {
if tok in [.semicolon, .rcbr, .assign] {
embedded_key := if qualified_embed_key != '' {
qualified_embed_key
} else {
@@ -2934,17 +2978,33 @@ fn fastc_emit_struct_declaration(mut scan scanner.Scanner, is_union bool, attrib
return error('fastc parser does not support embedded field `${field_names[0]}` in ${source_file.path}')
}
}
mut default_source := ''
mut default_source_offset := 0
if tok == .assign {
first_default_token := scan.scan()
default_start := scan.pos
default_source_offset = source_file.source_offset + default_start
tok = fastc_skip_field_default_from_token(mut scan, first_default_token)!
default_source = source_file.source[default_start..scan.pos].trim_space().trim_right(';').trim_space()
if default_source == '' {
return error('fastc parser does not support empty default for struct `${name}` embedded field `${field_names[0]}` in ${source_file.path}')
}
}
if !is_c_struct {
out.writeln('\t${fastc_c_declared_type_name(embedded_key)} __embedded_${embedded_id};')
}
fields_by_name.set('__embedded_${embedded_id}', fastc_c_declared_type_name(embedded_key))
field_info << FastcStructField{
name: '__embedded_${embedded_id}'
typ: flat.source_type_id(fastc_c_declared_type_name(embedded_key))
is_public: true
module_name: source_file.header.module_name
path: source_file.path
imports: source_file.header.imports.clone()
name: '__embedded_${embedded_id}'
typ: flat.source_type_id(fastc_c_declared_type_name(embedded_key))
is_public: true
module_name: source_file.header.module_name
path: source_file.path
imports: source_file.header.imports.clone()
default_source: default_source
default_source_offset: default_source_offset
default_source_origins: source_file.source_origins
translated: source_file.header.translated
}
embedded_id++
fields++
+2 -1
View File
@@ -3580,7 +3580,8 @@ fn fastc_replace_c_name(source string, identifier string, replacement string, me
end := index + identifier.len
scanned = end
before_is_name := index > 0 && (source[index - 1].is_alnum() || source[index - 1] == `_`)
before_is_member := member_blocks && index > 0 && source[index - 1] in [`.`, `>`]
before_is_member := member_blocks && index > 0 && (source[index - 1] == `.`
|| (source[index - 1] == `>` && index > 1 && source[index - 2] == `-`))
after_is_name := end < source.len && (source[end].is_alnum() || source[end] == `_`)
if before_is_name || before_is_member || after_is_name {
continue
@@ -0,0 +1,177 @@
module fastc
import os
import v.cmdexec
import v.pref
import v.scanner
import v.token
fn test_fastc_self_qualified_types_use_the_canonical_module_path() {
prefs := pref.new_preferences()
source := '[]types.Type'
file := token.File.unindexed('type.v', source.len)
mut scan := scanner.new_scanner(prefs, .normal)
scan.init(file, source)
first := scan.scan()
typ, next := fastc_scan_type_bytes(mut scan, first, 'type.v', 'v.types',
map[string]string{}, {
'v.types.Type': true
}, false) or { panic(err) }
assert typ == fastc_array_c_type('v__types__Type')
assert next in [.eof, .semicolon]
}
// Promoted fields copy pointer embeds before applying overrides to shared defaults.
fn test_fastc_pointer_embeds_preserve_defaults_and_copy_on_override() {
$if !macos && !linux {
return
}
root := os.join_path(os.vtmp_dir(), 'fastc_pointer_embed_defaults_${os.getpid()}')
os.mkdir_all(root) or { panic(err) }
defer { os.rmdir_all(root) or {} }
os.write_file(os.join_path(root, 'main.v'), r'module main
struct Number { value int }
struct Payload {
count int = 7
label string = "default"
params []int = [1, 2]
link &Number = default_number
}
type PayloadRef = &Payload
const shared_payload = &Payload{count: 11}
const default_number = &Number{value: 41}
struct Wrapped { PayloadRef = shared_payload }
struct Nested { Wrapped }
struct Defaultless { PayloadRef }
struct FixedPayload {
values [2]int = [2, 3]!
count int = 19
}
type FixedRef = &FixedPayload
const shared_fixed = &FixedPayload{values: [3, 5]!}
struct FixedWrapped { FixedRef = shared_fixed }
struct FixedDefaultless { FixedRef }
struct Collection { items []main.Payload }
struct Direct { count int }
type Variant = Wrapped | Direct
fn (value Wrapped) read() int { return value.count }
fn (value Direct) read() int { return value.count }
fn (value Variant) read() int {
return match value {
Wrapped { value.count }
Direct { value.count }
}
}
fn grouped_read(value Variant) int {
return match value {
Wrapped, Direct { value.read() }
}
}
fn grouped_count(value Variant) int {
return match value {
Wrapped, Direct { value.count }
}
}
fn guarded_count(value Variant) int {
if value is Wrapped { return value.count }
return 0
}
fn guarded_loop(value Variant) int {
mut current := value
for current is Wrapped { return current.count }
return 0
}
fn larger(value Wrapped, values []int) bool {
return values.len > value.params.len || (values.len == value.params.len && value.label.len == 0)
}
fn main() {
assert larger(Wrapped{}, [1, 2, 3])
assert !larger(Wrapped{}, [1])
collection := Collection{items: [Payload{count: 19}]}
assert collection.items[0].count == 19
empty := Wrapped{}
assert empty.count == 11
assert empty.label == "default"
assert empty.link.value == 41
changed := Wrapped{count: 23, label: "changed"}
assert changed.count == 23
assert changed.label == "changed"
updated := Wrapped{...changed, count: 29}
assert updated.count == 29
assert updated.label == "changed"
assert changed.count == 23
defaultless := Defaultless{count: 43}
assert defaultless.count == 43
assert defaultless.label == "default"
fixed := FixedWrapped{values: [7, 11]!}
assert fixed.values[0] == 7
assert fixed.values[1] == 11
assert shared_fixed.values[0] == 3
replacement := [13, 17]!
copied := FixedWrapped{values: replacement}
assert copied.values[0] == 13
assert copied.values[1] == 17
copied_member := FixedWrapped{values: fixed.values}
assert copied_member.values[0] == 7
assert copied_member.values[1] == 11
fixed_defaultless := FixedDefaultless{count: 23}
assert fixed_defaultless.count == 23
assert fixed_defaultless.values[0] == 2
assert fixed_defaultless.values[1] == 3
assert empty.count == 11
assert empty.label == "default"
assert shared_payload.count == 11
nested := Nested{count: 31}
assert nested.count == 31
assert nested.label == "default"
assert shared_payload.count == 11
assert changed.PayloadRef.count == 23
assert changed == changed
other := Wrapped{count: 23, label: "changed"}
assert changed != other
assert guarded_count(changed) == 23
assert guarded_loop(changed) == 23
values := [changed]
assert values[0].count == 23
assert grouped_count(changed) == 23
assert grouped_count(Direct{count: 37}) == 37
assert grouped_read(changed) == 23
assert grouped_read(Direct{count: 37}) == 37
println("ok")
}
') or {
panic(err)
}
mut prefs := pref.new_preferences()
prefs.vroot = @VEXEROOT
prefs.building_v = true
prefs.backend = 'fastc'
prefs.ccompiler = 'clang'
prefs.user_defines = ['gc_none', 'no_backtrace', 'v3_backend']
prefs.exclude = [os.join_path(@VEXEROOT, 'vlib/builtin/backtraces_nix.c.v')]
c_source := generate_files([root], prefs) or { panic(err) }
c_file := os.join_path(root, 'main.c')
program := os.join_path(root, 'program')
os.write_file(c_file, c_source) or { panic(err) }
build := cmdexec.run('cc', ['-std=gnu11', '-w', '-o', program, c_file])
assert build.exit_code == 0, build.output
run := cmdexec.run(program, [])
assert run.exit_code == 0, run.output
assert run.output.trim_space() == 'ok', run.output
}
fn test_fastc_root_member_rewrite_distinguishes_comparison_from_arrow() {
assert fastc_replace_c_root_identifier('left>value.count', 'value.count', 'promoted') == 'left>promoted'
assert fastc_replace_c_root_identifier('left>>value.count', 'value.count', 'promoted') == 'left>>promoted'
assert fastc_replace_c_root_identifier('left->value.count', 'value.count', 'promoted') == 'left->value.count'
assert fastc_replace_c_root_identifier('left.value.count', 'value.count', 'promoted') == 'left.value.count'
}
fn test_fastc_rendered_constant_dependencies_ignore_literals_and_members() {
mut dependencies := ['main.direct']
fastc_rendered_constant_dependencies('({ box.value = (main__later); box.main__fake = "main__fake"; box->main__fake; })', {
'main__later': 'main.later'
'main__fake': 'main.fake'
}, mut dependencies)
assert dependencies == ['main.direct', 'main.later']
}
+64 -12
View File
@@ -973,7 +973,13 @@ fn (g &Parser) render_struct_literal_with_defaults(c_type_source_id u32, layout_
continue
}
if rendered_field := rendered_fields_by_name[field.name] {
initializers << rendered_field
value := rendered_field.all_after('=')
if value.starts_with('{') {
initializers << rendered_field
} else {
name := fastc_c_identifier(field.name)
assignments << 'memcpy(__vf_sd.${name}, (${value}).data, sizeof(__vf_sd.${name}));'
}
initialized_fields[rendered_field] = true
}
}
@@ -1039,7 +1045,7 @@ fn (g &Parser) struct_field_initializer_default(field FastcStructField) string {
// An option field a literal leaves out is `none`; a zeroed Option would be a
// present value with no payload.
value = '(Option){.state=2}'
} else if !field.typ_text().ends_with('*') && g.struct_type_has_initializer_defaults(field.typ) {
} else if !fastc_is_pointer_type(g.underlying_alias_type(field.typ)) && g.struct_type_has_initializer_defaults(field.typ) {
value = g.render_empty_struct_initializer(field.typ)
}
if value == '' {
@@ -1052,7 +1058,7 @@ fn (g &Parser) struct_field_initializer_default(field FastcStructField) string {
}
fn (g &Parser) struct_type_has_initializer_defaults(c_type u32) bool {
if flat.source_type_name(c_type).ends_with('*') {
if fastc_is_pointer_type(g.underlying_alias_type(c_type)) {
return false
}
layout_type := fastc_trim_pointer_suffix(flat.source_type_name(g.underlying_alias_type(c_type)))
@@ -2832,6 +2838,28 @@ fn (g &Parser) render_chained_array_access_expression(tokens []FastcExpressionTo
}
}
}
// Lower a trailing promoted field together with the indexed receiver. Replacing
// only `items[i]` would leave the source field name on the physical embed wrapper.
mut member_end := close + 1
for member_end + 1 < tokens.len && tokens[member_end].tok == .dot
&& tokens[member_end + 1].tok == .name {
if member_end + 2 < tokens.len && tokens[member_end + 2].tok == .lpar { break }
member_end += 2
}
if member_end > close + 1 {
if member := g.render_indexed_member_receiver(tokens[start..member_end]) {
mut member_needle := needle
for field_index := close + 1; field_index < member_end; field_index += 2 {
member_needle += '.' + fastc_c_identifier(tokens[field_index + 1].lit)
}
replaced := fastc_replace_c_root_identifier(rendered, member_needle, member.source)
if replaced != rendered {
rendered = replaced
changed = true
continue
}
}
}
// An element that is itself a pointer (`[]&TypeSymbol`) auto-dereferences on the
// following member access, so `arr[i].field` lowers to `(elem)->field`, not `.field`.
if flat.source_type_name(element_type).ends_with('*') && fastc_contains(rendered, '${needle}.') {
@@ -2974,7 +3002,7 @@ fn (g &Parser) render_logical_expression(tokens []FastcExpressionToken) ?FastcRe
}
fn (g &Parser) struct_equality_is_supported(typ u32, seen []u32) bool {
if fastc_is_pointer_type(typ) {
if fastc_is_pointer_type(g.underlying_alias_type(typ)) {
return true
}
layout_type := flat.source_type_id(fastc_trim_pointer_suffix(flat.source_type_name(g.underlying_alias_type(typ))))
@@ -3043,7 +3071,7 @@ fn (g &Parser) stored_equality_source(left string, right string, typ u32, seen [
// compared in a loop whose temporaries are named by `depth`, so the loop comparing its
// elements, nested in it, does not shadow them.
fn (g &Parser) struct_equality_source(left string, right string, typ u32, seen []u32, depth int) string {
if fastc_is_pointer_type(typ) {
if fastc_is_pointer_type(g.underlying_alias_type(typ)) {
return '((${left}) == (${right}))'
}
layout_type := flat.source_type_id(fastc_trim_pointer_suffix(flat.source_type_name(g.underlying_alias_type(typ))))
@@ -3396,14 +3424,22 @@ fn (g &Parser) render_as_cast_member_access(tokens []FastcExpressionToken) ?Fast
}
field := g.struct_field_metadata(current_type, tokens[i + 1].lit) or { return none }
for storage_name in field.storage_path {
separator := if flat.source_type_name(current_type).ends_with('*') { '->' } else { '.' }
separator := if fastc_is_pointer_type(g.underlying_alias_type(current_type)) {
'->'
} else {
'.'
}
source += separator + fastc_c_identifier(storage_name)
current_type = g.struct_direct_member_type(current_type, storage_name)
if current_type == 0 {
return none
}
}
separator := if flat.source_type_name(current_type).ends_with('*') { '->' } else { '.' }
separator := if fastc_is_pointer_type(g.underlying_alias_type(current_type)) {
'->'
} else {
'.'
}
field_source := source + separator + fastc_c_identifier(field.name)
source = if field.is_shared_pointer { '*(${field_source})' } else { field_source }
current_type = field.typ
@@ -6771,16 +6807,32 @@ fn (g &Parser) render_multi_variant_field_projection(variants []u32, tag_source
for i := variants.len - 1; i >= 0; i-- {
variant := variants[i]
field := g.struct_field_metadata(variant, field_name) or { return none }
if field.storage_path.len > 0 {
return none
}
if field_type == 0 {
field_type = field.typ
pointer_source = '((${flat.source_type_name(field_type)} *)0)'
pointer_source = '((${fastc_output_c_type(flat.source_type_name(field_type))} *)0)'
} else if field.typ != field_type {
return none
}
field_pointer := '&((${flat.source_type_name(variant)} *)(${object_source}))->${fastc_c_identifier(field.name)}'
mut receiver_type := flat.source_type_id(flat.source_type_name(variant) + '*')
mut member := '((${flat.source_type_name(variant)} *)(${object_source}))'
for storage_name in field.storage_path {
separator := if fastc_is_pointer_type(g.underlying_alias_type(receiver_type)) {
'->'
} else {
'.'
}
member += separator + fastc_c_identifier(storage_name)
receiver_type = g.struct_direct_member_type(receiver_type, storage_name)
if receiver_type == 0 {
return none
}
}
separator := if fastc_is_pointer_type(g.underlying_alias_type(receiver_type)) {
'->'
} else {
'.'
}
field_pointer := '&(${member}${separator}${fastc_c_identifier(field.name)})'
pointer_source = '((${tag_source}) == __v_typeid_${flat.source_type_name(variant)} ? ${field_pointer} : ${pointer_source})'
}
return FastcRenderedExpression{
+5 -4
View File
@@ -317,13 +317,14 @@ fn (g &Parser) render_method_call_expression(tokens []FastcExpressionToken, rend
continue
}
// A method defined on the whole sum type (`fn (e Expr) is_blank_ident()`) stays
// callable after the value is narrowed to a variant. When the narrowed type lacks the
// method but the receiver is a bare local whose declared (un-narrowed) type has it,
// dispatch on the un-narrowed value instead of the smart-cast variant pointer.
// callable after narrowing. A grouped arm must retain whole-sum dispatch even when
// its first variant defines the same method. Bare locals also retain whole-sum
// methods absent from a single narrowed variant.
mut unnarrowed_receiver := ''
if receiver_tokens.len == 1 && receiver_tokens[0].tok == .name && receiver_tokens[0].lit in g.member_smartcasts {
narrowed_key, _ := g.resolve_method(receiver_type, tokens[i].lit)
if narrowed_key !in g.functions && narrowed_key !in g.mono_functions {
grouped := g.member_smartcasts[receiver_tokens[0].lit].variants.ids.len > 1
if grouped || (narrowed_key !in g.functions && narrowed_key !in g.mono_functions) {
if local := g.locals[receiver_tokens[0].lit] {
original_key, _ := g.resolve_method(local.typ, tokens[i].lit)
if original_key in g.functions || original_key in g.mono_functions {
+93 -2
View File
@@ -222,6 +222,7 @@ fn (g &Parser) render_struct_literal_expression(tokens []FastcExpressionToken) ?
}
}
}
mut promoted_pointer_fields := map[string]FastcStructLiteralFieldValue{}
mut rendered_fields := []string{}
mut rendered_fields_by_name := map[string]string{}
mut field_values := map[string]string{}
@@ -326,6 +327,7 @@ fn (g &Parser) render_struct_literal_expression(tokens []FastcExpressionToken) ?
has_array_init = true
continue
}
mut promoted_pointer_path := ''
mut field_is_shared_pointer := false
mut expected_type := if flat.source_type_name(layout_type) == 'array' && field_name == 'init' {
g.array_element_type(c_type) or { u32(0) }
@@ -348,6 +350,14 @@ fn (g &Parser) render_struct_literal_expression(tokens []FastcExpressionToken) ?
c_storage_path << fastc_c_identifier(storage_name)
}
c_field_name = c_storage_path.join('.')
mut owner := c_type
for storage_name in field.storage_path {
owner = g.struct_direct_member_type(owner, storage_name)
if fastc_is_pointer_type(g.underlying_alias_type(owner)) {
promoted_pointer_path = c_field_name
break
}
}
} else {
// Initializing an embedded field by its type name:
// `Derived{ Base: Base{...} }` sets the `__embedded_N` field.
@@ -371,18 +381,47 @@ fn (g &Parser) render_struct_literal_expression(tokens []FastcExpressionToken) ?
], expected_type, c_field_name, explicit_initializers.len, field_is_shared_pointer) or { return none }
}
explicit_initializers << field_value.explicit_initializers
if field_value.rendered_field != '' {
if promoted_pointer_path != '' {
promoted_pointer_fields[promoted_pointer_path] = FastcStructLiteralFieldValue{
...field_value
fixed_array_copy: field_value.fixed_array_copy.replace('__vf_struct_fixed.${c_field_name}', '__vf_promoted_field')
}
} else if field_value.rendered_field != '' {
rendered_fields << field_value.rendered_field
rendered_fields_by_name[field_name] = field_value.rendered_field
}
field_values[field_name] = field_value.field_value
if field_value.fixed_array_copy != '' {
if promoted_pointer_path == '' && field_value.fixed_array_copy != '' {
fixed_array_copies << field_value.fixed_array_copy
}
if field_value.is_fixed_array {
continue
}
}
if promoted_pointer_fields.len > 0 {
for field in g.struct_field_info[flat.source_type_name(layout_type)] {
prefix := field.name + '.'
mut nested := map[string]FastcStructLiteralFieldValue{}
for path, value in promoted_pointer_fields {
if path.starts_with(prefix) {
nested[path[prefix.len..]] = value
}
}
if nested.len == 0 { continue }
base := if update_source != '' {
'__vf_struct_update.${fastc_c_identifier(field.name)}'
} else {
g.struct_field_initializer_default(field)
}
value := g.render_promoted_pointer_literal(field.typ, nested, base, 0) or { return none }
temporary := '__vf_sf_${explicit_initializers.len}'
explicit_initializers << '${field.typ_text()} ${temporary}; ${temporary} = (${value});'
rendered_field := '.${fastc_c_identifier(field.name)}=(${temporary})'
rendered_fields << rendered_field
rendered_fields_by_name[field.name] = rendered_field
field_values[field.name] = temporary
}
}
if update_source == '' {
// The struct's rendered defaults are consulted; the constants phase
// re-parses a file that did so after they are ready.
@@ -534,3 +573,55 @@ fn (g &Parser) render_struct_literal_expression(tokens []FastcExpressionToken) ?
typ: c_type
}
}
// Copy pointer embeds before overriding promoted fields so shared defaults stay immutable.
fn (g &Parser) render_promoted_pointer_literal(typ u32, values map[string]FastcStructLiteralFieldValue, default_value string, depth int) ?string {
underlying := g.underlying_alias_type(typ)
is_pointer := fastc_is_pointer_type(underlying)
layout := fastc_trim_pointer_suffix(flat.source_type_name(underlying))
fields := g.struct_field_info[layout] or { return none }
variable := '__vf_promoted_${depth}'
mut assignments := []string{}
for field in fields {
name := fastc_c_identifier(field.name)
if value := values[name] {
if value.is_fixed_array {
if value.fixed_array_copy != '' {
assignments << value.fixed_array_copy.replace('__vf_promoted_field', '${variable}.${name}')
} else {
assignments << 'memcpy(${variable}.${name}, ((${field.typ_text()}){.data=${value.field_value}}).data, sizeof(${variable}.${name}));'
}
} else {
assignments << '${variable}.${name} = (${value.field_value});'
}
continue
}
prefix := name + '.'
mut nested := map[string]FastcStructLiteralFieldValue{}
for path, value in values {
if path.starts_with(prefix) { nested[path[prefix.len..]] = value }
}
if nested.len > 0 {
nested_default := '${variable}.${name}'
value := g.render_promoted_pointer_literal(field.typ, nested, nested_default, depth + 1) or { return none }
assignments << '${variable}.${name} = (${value});'
}
}
mut setup := ''
fallback := g.render_empty_struct_initializer(flat.source_type_id(layout))
initial := if default_value != '' && is_pointer {
pointer := '__vf_promoted_default_${depth}'
setup = '${flat.source_type_name(typ)} ${pointer} = (${default_value});'
'${pointer} ? *${pointer} : (${fallback})'
} else if default_value != '' {
default_value
} else {
fallback
}
result := if is_pointer {
'(${flat.source_type_name(typ)})v_fastc_interface_box(&${variable}, sizeof(${layout}))'
} else {
variable
}
return '({ ${setup} ${layout} ${variable} = (${initial}); ${assignments.join(' ')} ${result}; })'
}
+2
View File
@@ -1727,6 +1727,8 @@ fn fastc_scan_type_bytes(mut scan scanner.Scanner, first token.Token, path strin
}
if raw_type == 'C' {
type_module = 'C'
} else if raw_type == module_name.all_after_last('.') {
type_module = module_name
} else {
type_module = imports[raw_type] or {
return error('fastc parser does not support unknown module qualifier `${raw_type}` in ${path}')
+9 -5
View File
@@ -1166,7 +1166,7 @@ fn (g &Parser) struct_direct_member_type(receiver_type_id u32, field_name string
return field.typ
}
}
mut layout_type := fastc_trim_pointer_suffix(flat.source_type_name(receiver_type_id))
mut layout_type := fastc_trim_pointer_suffix(flat.source_type_name(g.underlying_alias_type(receiver_type_id)))
if layout_type.starts_with('Array_') {
layout_type = 'array'
} else if layout_type.starts_with('Map_') {
@@ -1243,7 +1243,7 @@ fn (g &Parser) struct_field_metadata(receiver_type u32, field_name string) ?&Fas
}
fn (g &Parser) direct_struct_field_metadata(receiver_type u32, field_name string) ?&FastcStructField {
mut layout_type := fastc_trim_pointer_suffix(flat.source_type_name(receiver_type))
mut layout_type := fastc_trim_pointer_suffix(flat.source_type_name(g.underlying_alias_type(receiver_type)))
if layout_type.starts_with('Array_') {
layout_type = 'array'
} else if layout_type.starts_with('Map_') {
@@ -1258,7 +1258,7 @@ fn (g &Parser) direct_struct_field_metadata(receiver_type u32, field_name string
}
fn (g &Parser) struct_field_metadata_impl(receiver_type u32, field_name string) ?FastcStructField {
mut layout_type := fastc_trim_pointer_suffix(flat.source_type_name(receiver_type))
mut layout_type := fastc_trim_pointer_suffix(flat.source_type_name(g.underlying_alias_type(receiver_type)))
if layout_type.starts_with('Array_') {
layout_type = 'array'
} else if layout_type.starts_with('Map_') {
@@ -1344,12 +1344,16 @@ fn (g &Parser) embedded_field_dot_piece_for_type(receiver_type u32, field_name s
mut current_type := receiver_type
mut piece := ''
for storage_name in field.storage_path {
separator := if flat.source_type_name(current_type).ends_with('*') { '->' } else { '.' }
separator := if fastc_is_pointer_type(g.underlying_alias_type(current_type)) {
'->'
} else {
'.'
}
piece += separator + fastc_c_identifier(storage_name)
current_type = g.struct_direct_member_type(current_type, storage_name)
if current_type == 0 { return '' }
}
piece += if flat.source_type_name(current_type).ends_with('*') { '->' } else { '.' }
piece += if fastc_is_pointer_type(g.underlying_alias_type(current_type)) { '->' } else { '.' }
return piece
}
+53 -2
View File
@@ -98,6 +98,7 @@ mut:
struct_types map[string]TypeID
struct_field_types flat.TypeTextBindings
struct_field_defaults map[string]NativeFieldDefault
struct_embeds map[TypeID][]int
default_struct_types map[TypeID]bool
option_types map[u32]TypeID
sum_type_variants flat.TypeTextListBindings
@@ -390,10 +391,17 @@ fn (mut b Builder) register_types() {
if node.kind == .struct_decl {
mut field_types := []TypeID{}
mut field_names := []string{}
mut embeds := []int{}
for i in 0 .. node.children_count {
f := b.a.child_node(&node, i)
metadata := f.generic_params()
if f.typ == 0 || (metadata.len > 0 && metadata[0].contains('e')) {
embeds << i
}
// Embedded fields with defaults carry their type in the field name.
declared_type := if f.typ == 0 { flat.source_type_id(f.value) } else { f.typ }
field_type_name_id := native_c_struct_field_type(node.value, f.value) or {
qualify_type_ref_name(f.typ, cur_module)
qualify_type_ref_name(declared_type, cur_module)
}
field_type := b.resolve_struct_storage_type(field_type_name_id, cur_module)
field_types << field_type
@@ -423,6 +431,7 @@ fn (mut b Builder) register_types() {
b.struct_field_types.set('tm.tm_zone', '&char')
}
typ_id := b.struct_type_id_for_decl(node.value, cur_module)
b.struct_embeds[typ_id] = embeds
b.m.type_store.types[typ_id] = Type{
kind: .struct_t
fields: field_types
@@ -13247,7 +13256,7 @@ fn (mut b Builder) smartcast_sum_selector_addr(sum_addr ValueID, base_id flat.No
return none
}
if field_name !in variant_layout.field_names {
return none
_ := b.embedded_field_path(variant_type, field_name, []TypeID{}) or { return none }
}
payload_ptr := b.get_field_ptr(sum_addr, sum_field_name(variant))
return b.get_field_ptr(payload_ptr, field_name)
@@ -14078,12 +14087,44 @@ fn (mut b Builder) get_field_ptr(base_addr ValueID, field_name string) ValueID {
pointer := b.m.type_store.get_ptr(typ.fields[index])
return b.emit2(.get_element_ptr, pointer, address, constant)
}
if path := b.embedded_field_path(owner, storage_field, []TypeID{}) {
mut field := address
for name in path {
field = b.get_field_ptr(field, name)
}
return field
}
}
zero := b.m.get_or_add_const(b.i64_type, '0')
pointer := b.m.type_store.get_ptr(b.i64_type)
return b.emit2(.get_element_ptr, pointer, address, zero)
}
// embedded_field_path retains each embed so pointer fields are loaded before their members.
fn (b &Builder) embedded_field_path(owner TypeID, name string, seen []TypeID) ?[]string {
if owner <= 0 || owner >= b.m.type_store.types.len || owner in seen {
return none
}
typ := b.m.type_store.types[owner]
if name in typ.field_names {
return [name]
}
mut visited := seen.clone()
visited << owner
for index in b.struct_embeds[owner] {
mut child := typ.fields[index]
for b.is_pointer_type(child) {
child = b.m.type_store.types[child].elem_type
}
if nested := b.embedded_field_path(child, name, visited) {
mut path := [typ.field_names[index]]
path << nested
return path
}
}
return none
}
fn (mut b Builder) resolve_type(name_id u32) TypeID {
return b.resolve_type_uncached(flat.source_type_name(name_id))
}
@@ -14531,6 +14572,16 @@ fn (b &Builder) infer_v_type(id flat.NodeId) u32 {
}
node := b.a.nodes[int(id)]
match node.kind {
.struct_init {
return flat.source_type_id(node.value)
}
.prefix {
if node.op == .amp && node.children_count > 0 {
inner := b.infer_v_type(b.a.child(&node, 0))
return flat.source_type_id('&' + flat.source_type_name(inner))
}
return flat.source_type_int
}
.string_literal, .string_interp {
return flat.source_type_string
}
+20
View File
@@ -49,6 +49,26 @@ fn test_native_module_initialization_orders_dependencies_once() {
assert order == ['child', 'parent', 'main']
}
fn test_native_inferred_global_pointer_keeps_its_pointee_type() {
path := os.join_path(os.vtmp_dir(), 'native_pointer_init_${os.getpid()}.v')
defer {
os.rm(path) or {}
}
os.write_file(path, 'struct Payload { value i64 }
__global payload = &Payload{value: 42}
fn main() {}
')!
mut p := parser.Parser.new(pref.new_preferences())
a := p.parse_file(path)
assert p.diagnostics.len == 0, p.diagnostics.str()
m := build(a)
payload := m.globals.filter(it.name == 'payload')[0]
typ := m.type_store.types[payload.typ]
assert typ.kind == .ptr_t
assert m.type_store.types[typ.elem_type].kind == .struct_t
assert m.type_store.types[typ.elem_type].field_names == ['value']
}
fn test_native_static_mutex_initialization_calls_pthread_init() {
path := os.join_path(os.vtmp_dir(), 'native_mutex_init_${os.getpid()}.v')
defer {
+27 -1
View File
@@ -114,6 +114,8 @@ pub:
mut:
// One entry per source line, so keep it compact: offsets are file-local.
line_offsets []i32 = [i32(0)]
// Upper bounds at 256-byte boundaries narrow repeated position lookups.
line_pages []i32
// Set once the line table holds the line starts of the source.
has_line_index bool
// Keep the digest inline: stored files can outlive parser-worker preallocation scopes.
@@ -168,6 +170,7 @@ pub fn File.unindexed(name string, size int) &File {
@[inline]
pub fn (mut f File) add_line(offset int) {
f.line_offsets << i32(offset)
f.line_pages = []i32{}
}
// clone_index returns a copy of this file index that owns its storage: the name,
@@ -178,6 +181,7 @@ pub fn (f &File) clone_index() &File {
name: f.name.clone()
size: f.size
line_offsets: f.line_offsets.clone()
line_pages: f.line_pages.clone()
has_line_index: f.has_line_index
source_digest: f.source_digest
has_source_digest: f.has_source_digest
@@ -258,6 +262,18 @@ fn (mut f File) index_line_offsets(src string) {
i++
}
f.line_offsets = offsets
f.line_pages = []i32{}
if lines >= 32 && src.len <= 256 * 1024 * 1024 {
mut pages := []i32{len: (src.len >> 8) + 2}
mut next_line := 0
for page in 0 .. pages.len {
for next_line < offsets.len && offsets[next_line] <= page << 8 {
next_line++
}
pages[page] = i32(next_line)
}
f.line_pages = pages
}
f.has_line_index = true
}
@@ -278,6 +294,7 @@ pub fn quick_sum(data &u8, len int) u64 {
pub fn (mut f File) index_lines_without_digest(src string) {
_ := src
f.line_offsets = [i32(0)]
f.line_pages = []i32{}
f.has_line_index = false
f.has_source_digest = false
}
@@ -365,9 +382,18 @@ pub fn (f &File) find_line_and_column(pos int) (int, int) {
return line, pos - f.line_offsets[line - 1] + 1
}
// find_line resolves find line information for token.
// find_line resolves a byte offset using the immutable page index when available.
@[direct_array_access]
pub fn (f &File) find_line(pos int) int {
mut min, mut max := 0, f.line_offsets.len
if pos >= 0 && f.line_pages.len > 0 {
page := pos >> 8
if page + 1 >= f.line_pages.len {
return max
}
min = f.line_pages[page]
max = f.line_pages[page + 1]
}
for min < max {
mid := (min + max) / 2
if f.line_offsets[mid] <= pos {
+49
View File
@@ -0,0 +1,49 @@
module token
fn assert_file_lines_match_scan(f &File, source string) {
mut expected := 1
assert f.find_line(-1) == 0
for pos in 0 .. source.len + 513 {
if pos > 0 && pos <= source.len && source[pos - 1] == `\n` {
expected++
}
assert f.find_line(pos) == expected, 'offset ${pos}'
}
}
fn test_line_pages_preserve_positions_at_page_and_line_boundaries() {
for width in [1, 2, 7, 31, 255, 256, 257, 600] {
source := ('x'.repeat(width - 1) + '\n').repeat(40) + 'tail'
mut f := File{
name: 'lines.v'
size: source.len
}
f.index_line_starts(source)
assert f.line_pages.len > 0
assert_file_lines_match_scan(f, source)
cloned := f.clone_index()
assert_file_lines_match_scan(cloned, source)
f.index_line_starts('short\nfile')
assert f.line_pages.len == 0
assert_file_lines_match_scan(f, 'short\nfile')
assert_file_lines_match_scan(cloned, source)
}
}
fn test_line_pages_are_invalidated_when_lines_are_appended_or_reset() {
source := 'x\n'.repeat(40)
mut f := File{
name: 'growing.v'
size: source.len + 20
}
f.index_line_starts(source)
f.add_line(source.len + 10)
assert f.line_pages.len == 0
assert f.find_line(source.len + 9) == 41
assert f.find_line(source.len + 10) == 42
f.index_line_starts(source)
f.index_lines_without_digest(source)
assert f.line_pages.len == 0
assert f.find_line(0) == 1
assert f.find_line(source.len) == 1
}
+82
View File
@@ -0,0 +1,82 @@
module transform
import v.types
fn test_imported_type_payloads_keep_implicit_references() {
fixed := types.ArrayFixed{ elem_type: types.Type(types.string_), len: 2 }
channel := types.Channel{ elem_type: types.Type(types.int_), is_mut: true }
mapping := types.Map{
key_type: types.Type(types.string_)
value_type: types.Type(types.int_)
}
signature := types.FnType{ return_type: types.Type(types.string_) }
alias := types.Alias{ name_id: 123, base_type: types.Type(types.int_) }
assert fixed.elem_type is types.String
assert fixed.len == 2
assert channel.elem_type is types.Primitive
assert mapping.key_type is types.String
assert mapping.value_type is types.Primitive
assert signature.return_type is types.String
assert alias.base_type is types.Primitive
assert types.ArrayFixed{}.elem_type is types.Void
assert types.Map{}.key_type is types.Void
assert types.FnType{}.return_type is types.Void
}
fn test_callback_normalization_keeps_shared_type_payloads_immutable() {
callback := types.Type(types.FnType{
params: [types.FnParam{ typ: &types.Type(types.Array{ elem_type: &types.builtin_int_type }), is_mut: true }]
return_type: &types.builtin_string_type
is_variadic: true
})
inputs := [callback, types.Type(types.ArrayFixed{
elem_type: types.new_type_reference(callback)
len: 3
len_expr: 'count'
}), types.Type(types.Channel{
elem_type: types.new_type_reference(callback)
is_mut: true
}), types.Type(types.Map{
key_type: &types.builtin_string_type
value_type: types.new_type_reference(callback)
}), types.Type(types.MultiReturn{
types: [callback, types.builtin_int_type]
})]
for input in inputs {
before := types.clone_owned_type(input)
normalized := callback_type_without_variadic_modes(input)
assert types.semantic_types_equal(input, before)
assert !types.semantic_types_equal(input, normalized)
match normalized {
types.FnType {
assert !normalized.is_variadic
assert normalized.params[0].is_mut
}
types.ArrayFixed {
assert normalized.len == 3
assert normalized.len_expr == 'count'
assert normalized.elem_type is types.FnType
assert !normalized.elem_type.is_variadic
}
types.Channel {
assert normalized.is_mut
assert normalized.elem_type is types.FnType
assert !normalized.elem_type.is_variadic
}
types.Map {
assert normalized.key_type is types.String
assert normalized.value_type is types.FnType
assert !normalized.value_type.is_variadic
}
types.MultiReturn {
first := normalized.types[0]
assert first is types.FnType
assert !first.is_variadic
assert normalized.types[1] is types.Primitive
}
else {
assert false
}
}
}
}
+10 -7
View File
@@ -2723,8 +2723,12 @@ fn (t &Transformer) call_name_for_node(id flat.NodeId, node flat.Node) string {
if node.children_count > 0 && t.cur_module.len > 0
&& t.cur_module !in ['main', 'builtin'] {
fn_node := t.a.child_node(&node, 0)
short_name := short_name_view(name)
if fn_node.kind == .ident && fn_node.value == short_name {
if fn_node.kind == .ident && fn_node.value == short_name_view(name) {
short_name := fn_node.value
if name.len == t.cur_module.len + 1 + short_name.len
&& name.starts_with(t.cur_module) && name[t.cur_module.len] == `.` {
return name
}
qname := '${t.cur_module}.${short_name}'
if t.fn_ret_types.contains(qname) || qname in t.tc.fn_ret_types {
return qname
@@ -16933,7 +16937,6 @@ fn callback_type_without_variadic_modes(typ types.Type) types.Type {
params << types.FnParam{ typ: types.new_type_reference(types.Type(callback_type_without_variadic_modes(param.typ))), is_mut: param.is_mut }
}
types.Type(types.FnType{
...typ
params: params
return_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.return_type)))
is_variadic: false
@@ -16943,13 +16946,13 @@ fn callback_type_without_variadic_modes(typ types.Type) types.Type {
types.Type(types.Array{ ...typ, elem_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.elem_type))) })
}
types.ArrayFixed {
types.Type(types.ArrayFixed{ ...typ, elem_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.elem_type))) })
types.Type(types.ArrayFixed{ len: typ.len, len_expr: typ.len_expr, elem_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.elem_type))) })
}
types.Channel {
types.Type(types.Channel{ ...typ, elem_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.elem_type))) })
types.Type(types.Channel{ is_mut: typ.is_mut, elem_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.elem_type))) })
}
types.Map {
types.Type(types.Map{ ...typ, key_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.key_type))), value_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.value_type))) })
types.Type(types.Map{ key_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.key_type))), value_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.value_type))) })
}
types.Pointer {
types.Type(types.Pointer{ ...typ, base_type: types.new_type_reference(types.Type(callback_type_without_variadic_modes(typ.base_type))) })
@@ -16963,7 +16966,7 @@ fn callback_type_without_variadic_modes(typ types.Type) types.Type {
types.MultiReturn {
mut members := []types.Type{cap: typ.types.len}
for member in typ.types { members << callback_type_without_variadic_modes(member) }
types.Type(types.MultiReturn{ ...typ, types: members })
types.Type(types.MultiReturn{ types: members })
}
else { typ }
}
@@ -3,6 +3,31 @@ module transform
import v.flat
import v.types
fn test_call_name_keeps_qualified_current_module_and_checks_foreign_names() {
mut a := flat.FlatAst.new()
callee := a.add_val(.ident, 'run')
start := a.begin_children()
a.add_child(callee)
call := flat.Node{
kind: .call
children_start: start
children_count: 1
}
id := a.add_node(call)
mut tc := types.TypeChecker.new(&a)
tc.begin_sparse_transform_node_caches(0)
mut t := new_transformer(mut a, &tc, map[string]bool{})
t.cur_module = 'worker'
tc.sparse_resolved_call_names[int(id)] = 'worker.run'
assert t.call_name_for_node(id, call) == 'worker.run'
tc.sparse_resolved_call_names[int(id)] = 'foreign.run'
assert t.call_name_for_node(id, call) == 'foreign.run'
t.fn_ret_types.set('worker.run', 'int')
assert t.call_name_for_node(id, call) == 'worker.run'
t.cur_module = 'worker_more'
assert t.call_name_for_node(id, call) == 'foreign.run'
}
fn test_rewritten_generic_call_keeps_caller_type_in_foreign_scan() {
mut a := flat.FlatAst.new()
callee := a.add_val(.ident, 'callee.run_T_Context')
+17
View File
@@ -82,6 +82,23 @@ fn test_node_type_memo_epoch_wrap_does_not_restore_old_results() {
assert t.node_type(value) == flat.source_type_bool
}
fn test_binding_setter_indexes_hand_built_bindings_and_keeps_undo() {
mut t := Transformer{
var_types: [VarTypeBinding{ name: 'value', typ: flat.source_type_int }]
}
t.set_var_type('value', flat.source_type_string)
assert t.var_type_indices['value'] == 0
t.var_type_undo_active = true
mark := t.mark_var_type_scope()
t.set_var_type('value', flat.source_type_bool)
t.set_var_type('inner', flat.source_type_u8)
assert t.var_types[t.var_type_index('value')].typ == flat.source_type_bool
t.restore_var_type_scope(mark)
assert t.var_type_indices.len == 1
assert t.var_types[t.var_type_index('value')].typ == flat.source_type_string
assert t.var_type_index('inner') == -1
}
fn test_struct_guess_comparison_follows_checked_ids_and_call_annotation_precedence() {
mut a := flat.FlatAst.new()
value := a.add_val(.ident, 'value')
@@ -3,6 +3,25 @@ module transform
import v.flat
import v.types
fn test_scoped_text_scan_finds_borrowed_text_in_unscoped_payloads() {
$if prealloc {
mut node := flat.Node{ kind: .fn_decl, value: 'unscoped' }
scope := transform_worker_scope_begin(true)
params := ['scoped parameter'.clone()]
transform_worker_scope_leave(scope)
node.set_generic_params(params)
lo, hi := scopes_address_range([scope])
assert hi > lo
assert !transform_scope_owns(scope, node.value.str)
assert !transform_scope_owns(scope, node.payload_ptr())
assert node_has_any_scoped_text(&node, [scope], lo, hi)
plain := flat.Node{ kind: .ident, value: 'unscoped' }
assert !node_has_any_scoped_text(&plain, [scope], lo, hi)
assert !text_pointer_in_scopes(unsafe { nil }, [scope], lo, hi)
transform_worker_scope_free(scope)
}
}
fn test_generic_batch_publishes_indirect_base_writes_and_new_nodes() {
mut a := flat.FlatAst.new()
a.add_node(flat.Node{ kind: .ident, value: 'outside_current_body' })
+36
View File
@@ -92,3 +92,39 @@ fn test_expression_smartcast_lookup_preserves_shadowing_and_unnamed_keys() {
t.smartcast_stack << SmartcastContext{ expr_name: '', variant_name: flat.source_type_bool }
assert (t.find_smartcast_for_expr(literal) or { panic('missing unnamed context') }).variant_name == flat.source_type_bool
}
fn test_selector_type_keeps_field_and_receiver_smartcasts() {
mut a := flat.FlatAst.new()
object := a.add_val(.ident, 'object')
start := a.begin_children()
a.add_child(object)
member := a.add_node(flat.Node{
kind: .selector
value: 'member'
children_start: i32(start)
children_count: 1
})
mut t := Transformer{
a: &a
structs: {
'Box': StructInfo{
name: flat.source_type_id('Box')
fields: [FieldInfo{ name: 'member', typ: flat.source_type_int }]
}
'Other': StructInfo{
name: flat.source_type_id('Other')
fields: [FieldInfo{ name: 'member', typ: flat.source_type_string }]
}
}
}
t.set_var_type('object', flat.source_type_id('Box'))
assert t.resolve_selector_type(a.nodes[int(member)]) == flat.source_type_int
t.push_smartcast('object', flat.source_type_id('Other'), flat.source_type_id(option_unwrap_marker))
assert t.resolve_selector_type(a.nodes[int(member)]) == flat.source_type_string
t.push_smartcast('object.member', flat.source_type_bool, flat.source_type_id(option_unwrap_marker))
assert t.resolve_selector_type(a.nodes[int(member)]) == flat.source_type_bool
t.pop_smartcast()
assert t.resolve_selector_type(a.nodes[int(member)]) == flat.source_type_string
t.pop_smartcast()
assert t.resolve_selector_type(a.nodes[int(member)]) == flat.source_type_int
}
+1 -1
View File
@@ -125,7 +125,7 @@ fn (mut t Transformer) transform_struct_fields(id flat.NodeId, node flat.Node) f
}
}
}
field_type = t.lookup_struct_field_raw_type(embedded_owner_source_id, target_field_name) or {
field_type = t.lookup_struct_field_type(embedded_owner_source_id, target_field_name) or {
t.checker_struct_field_type_name(struct_type, field_name) or { u32(0) }
}
}
+15 -13
View File
@@ -26,21 +26,15 @@ fn same_transform_text(a string, b string) bool {
// short_name_view returns the suffix after the final dot without allocating.
@[direct_array_access; inline]
fn short_name_view(name string) string {
for i := name.len - 1; i >= 0; i-- {
if name[i] == `.` {
return unsafe { name.substr_unsafe(i + 1, name.len) }
}
}
i := name.last_index_u8(`.`)
if i >= 0 { return unsafe { name.substr_unsafe(i + 1, name.len) } }
return name
}
@[direct_array_access; inline]
fn owner_name_view(name string) string {
for i := name.len - 1; i >= 0; i-- {
if name[i] == `.` {
return unsafe { name.substr_unsafe(0, i) }
}
}
i := name.last_index_u8(`.`)
if i >= 0 { return unsafe { name.substr_unsafe(0, i) } }
return name
}
@@ -3396,8 +3390,11 @@ fn (mut t Transformer) set_var_type_binding(name string, typ u32, raw_typ u32, i
binding: t.var_types[i]
}
}
t.var_type_indices[name] = i
t.clear_node_type_memo()
// Production setters keep the index complete. Only a hand-built view can
// find an existing binding through the linear fallback without indexing it.
if t.var_type_indices.len != t.var_types.len {
t.var_type_indices[name] = i
}
t.var_types[i] = VarTypeBinding{
name: name
typ: typ
@@ -10519,7 +10516,7 @@ fn (t &Transformer) escape_fn_literal_capture_names(id flat.NodeId) []string {
// `method_value_receivers`, (d) captured names in `cb := fn [p]` into
// `closure_capture_aliases`, and (e) every ident name appearing inside a return
// statement, map value assignment, or nonlocal field store into `returned`.
@[direct_array_access]
@[direct_array_access; inline]
fn (mut t Transformer) scan_escape_pass(id flat.NodeId, mut amp_ptrs map[string]bool, mut amp_sources map[string][]string, mut ptr_aliases map[string]string, mut method_value_receivers map[string]string, mut closure_capture_aliases map[string][]string, mut interface_boxes map[string]bool, mut returned map[string]bool, mut local_stack_names map[string]bool, mut local_stack_added []string, mut reference_backing_sources map[string][]string, can_clear_interface_boxes bool) {
if int(id) < 0 || int(id) >= t.a.nodes.len {
return
@@ -10528,6 +10525,11 @@ fn (mut t Transformer) scan_escape_pass(id flat.NodeId, mut amp_ptrs map[string]
if t.a.nodes[int(id)].children_count == 0 && t.a.nodes[int(id)].kind != .comptime_for {
return
}
t.scan_escape_pass_nonempty(id, mut amp_ptrs, mut amp_sources, mut ptr_aliases, mut method_value_receivers, mut closure_capture_aliases, mut interface_boxes, mut returned, mut local_stack_names, mut local_stack_added, mut reference_backing_sources, can_clear_interface_boxes)
}
@[direct_array_access]
fn (mut t Transformer) scan_escape_pass_nonempty(id flat.NodeId, mut amp_ptrs map[string]bool, mut amp_sources map[string][]string, mut ptr_aliases map[string]string, mut method_value_receivers map[string]string, mut closure_capture_aliases map[string][]string, mut interface_boxes map[string]bool, mut returned map[string]bool, mut local_stack_names map[string]bool, mut local_stack_added []string, mut reference_backing_sources map[string][]string, can_clear_interface_boxes bool) {
// Resolve reflected method bodies before borrowing a node header: cloning
// their analysis view can grow the AST. The source loop is lowered normally later.
if t.a.nodes[int(id)].kind == .comptime_for {
@@ -544,20 +544,54 @@ struct WorkerScopeFreeArgs {
end int
}
// node_has_scoped_text reports whether any text payload of `node` lives in
// `scope`'s arena, i.e. whether canonicalization/promotion would rewrite it.
// node_has_any_scoped_text reports whether any node text is owned by one of
// `scopes`. `lo`/`hi` bound the union of the arenas' address ranges: nearly
// every text lives outside them, so the per-arena search is rarely needed.
@[inline]
fn node_has_any_scoped_text(node &flat.Node, scopes []voidptr, lo usize, hi usize) bool {
value_addr := usize(voidptr(node.value.str))
if (node.value.len == 0 || value_addr < lo || value_addr >= hi) && node.payload == 0 {
if node.value.len > 0 && text_pointer_in_scopes(node.value.str, scopes, lo, hi) {
return true
}
if node.payload == 0 {
return false
}
payload := node.payload_ptr()
if text_pointer_in_scopes(payload, scopes, lo, hi) {
return true
}
debugger_types := payload.debugger_value_types.ids
if node.kind == .debugger_stmt && debugger_types.len > 0
&& text_pointer_in_scopes(debugger_types.data, scopes, lo, hi) {
return true
}
constraints := payload.generic_constraints.ids
if constraints.len > 0 && text_pointer_in_scopes(constraints.data, scopes, lo, hi) {
return true
}
params := payload.generic_params
if params.len == 0 {
return false
}
if text_pointer_in_scopes(params.data, scopes, lo, hi) {
return true
}
for param in params {
if param.len > 0 && text_pointer_in_scopes(param.str, scopes, lo, hi) {
return true
}
}
return false
}
// Reject pointers outside the union once, before consulting individual arenas.
@[inline]
fn text_pointer_in_scopes(ptr voidptr, scopes []voidptr, lo usize, hi usize) bool {
addr := usize(ptr)
if addr < lo || addr >= hi {
return false
}
for scope in scopes {
if node_has_scoped_text(node, scope) {
if transform_scope_owns(scope, ptr) {
return true
}
}
@@ -585,37 +619,6 @@ fn scopes_address_range(scopes []voidptr) (usize, usize) {
return lo, hi
}
fn node_has_scoped_text(node &flat.Node, scope voidptr) bool {
if node.value.len > 0 && transform_scope_owns(scope, node.value.str) {
return true
}
if node.payload == 0 {
return false
}
if transform_scope_owns(scope, node.payload_ptr()) {
return true
}
debugger_types := node.debugger_value_type_ids()
if debugger_types.len > 0 && transform_scope_owns(scope, debugger_types.data) { return true }
constraints := node.payload_ptr().generic_constraints.ids
if constraints.len > 0 && transform_scope_owns(scope, constraints.data) {
return true
}
params := node.generic_params()
if params.len == 0 {
return false
}
if transform_scope_owns(scope, params.data) {
return true
}
for param in params {
if param.len > 0 && transform_scope_owns(scope, param.str) {
return true
}
}
return false
}
// scoped_text_scan_thread flags this worker's node range. Pure reads plus
// byte writes into a disjoint flag range: no allocations, so it is safe to
// run on pool threads regardless of their arena state.
+42 -28
View File
@@ -796,22 +796,21 @@ fn (t &Transformer) resolve_selector_type_uncached(node flat.Node) u32 {
if builtin_type_source_id := t.builtin_selector_type(base_type_source_id, field_name) {
return builtin_type_source_id
}
base_key := t.expr_key(base_id)
if base_key.len > 0 {
full_key := '${base_key}.${field_name}'
if sc := t.find_smartcast(full_key) {
return t.smartcast_target_type(sc)
}
}
if base_key.len > 0 {
if sc := t.find_smartcast(base_key) {
variant_type_source_id := t.qualify_variant(sc.variant_name, sc.sum_type_name)
if ftyp_source_id := t.lookup_struct_field_type(variant_type_source_id, field_name) {
return ftyp_source_id
if t.smartcast_stack.len > 0 {
base_key := t.expr_key(base_id)
if base_key.len > 0 {
full_key := '${base_key}.${field_name}'
if sc := t.find_smartcast(full_key) {
return t.smartcast_target_type(sc)
}
if ftyp_source_id := t.lookup_struct_field_type(sc.variant_name, field_name) {
return ftyp_source_id
if sc := t.find_smartcast(base_key) {
variant_type_source_id := t.qualify_variant(sc.variant_name, sc.sum_type_name)
if ftyp_source_id := t.lookup_struct_field_type(variant_type_source_id, field_name) {
return ftyp_source_id
}
if ftyp_source_id := t.lookup_struct_field_type(sc.variant_name, field_name) {
return ftyp_source_id
}
}
}
}
@@ -1398,8 +1397,17 @@ fn (t &Transformer) type_authority_has(name string) bool {
}
// normalize_type_alias resolves an interned spelling while retaining its numeric identity.
@[direct_array_access; inline]
fn (t &Transformer) normalize_type_alias(type_id u32) u32 {
if type_id == 0 || isnil(t.tc) { return type_id }
if !isnil(t.alias_cache) && t.alias_cache.texts == t.var_type_texts
&& t.alias_cache.canonical_types[int(type_id & 1023)] == type_id {
return type_id
}
return t.normalize_type_alias_cached(type_id)
}
fn (t &Transformer) normalize_type_alias_cached(type_id u32) u32 {
mut texts := t.var_type_texts
if isnil(t.alias_cache) {
typ := texts.text(type_id)
@@ -2069,7 +2077,7 @@ fn (t &Transformer) normalized_node_type(id flat.NodeId) u32 {
// transform) and falling back to the transformer's local heuristics. This is the
// bridge that makes the transformer type-aware, so type-dependent lowering lives
// here rather than in the backend.
@[direct_array_access]
@[direct_array_access; inline]
fn (t &Transformer) node_type(id flat.NodeId) u32 {
idx := int(id)
if !isnil(t.node_type_memo) {
@@ -2098,23 +2106,29 @@ fn (t &Transformer) node_type(id flat.NodeId) u32 {
if memo.filled[slot] == memo.generation {
return memo.values[slot]
}
generation := memo.generation
result_id := t.node_type_uncached(id)
// Resolution can request a nested specialization and change the type
// environment. Publish only results from an unchanged context, and let
// provisional answers resolve again after declarations are registered.
if memo.generation == generation && result_id != 0 && result_id != flat.source_type_unknown
&& !t.generic_arg_is_unresolved(result_id) {
memo.values[slot] = result_id
memo.filled[slot] = generation
}
return result_id
return t.node_type_memo_miss(id, slot)
}
}
return t.node_type_uncached(id)
}
@[direct_array_access]
fn (t &Transformer) node_type_memo_miss(id flat.NodeId, slot int) u32 {
mut memo := t.node_type_memo
generation := memo.generation
result_id := t.node_type_uncached(id)
// Resolution can request a nested specialization and change the type
// environment. Publish only results from an unchanged context, and let
// provisional answers resolve again after declarations are registered.
if memo.generation == generation && result_id != 0 && result_id != flat.source_type_unknown
&& !t.generic_arg_is_unresolved(result_id) {
memo.values[slot] = result_id
memo.filled[slot] = generation
}
return result_id
}
@[direct_array_access]
fn (t &Transformer) node_type_uncached(id flat.NodeId) u32 {
if int(id) < 0 {
+7 -1
View File
@@ -35,13 +35,19 @@ spelling at an integration boundary. Equality, hashing, and owned copies retain
identity. This spelling identity is shared across workers; semantic `TypeId` values still belong
to a particular checker's interner.
`Type` keeps a 16-byte descriptor on 64-bit targets. Small variants stay inline; `Unknown`,
`ArrayFixed`, `Channel`, `Map`, `FnType`, `Alias`, and `MultiReturn` embed references to immutable
payloads. Promoted fields and their constructor syntax remain available. Descriptor copies share
these payloads; value equality and semantic hashing inspect their fields. Do not mutate a shared
payload. Use `clone_owned_type` when it must outlive the arena that owns any part of its graph.
`new_type_reference(value)` retains a shallow descriptor copy in stable reference storage.
Its nested metadata stays borrowed. Use `clone_owned_type(value)` first when that metadata
belongs to an arena that will end. Checker parsing and interning retain canonical recursive
descriptor edges; use those APIs when a reference should share the checker's type identity.
Pass-2 worker publication checks the descriptor's referenced storage before releasing its arena.
Lists, strings and recursive descriptor references are copied when they depend on that arena;
Indirect payloads, lists, strings and recursive descriptors are copied when they depend on it;
graphs already outside it retain their references.
A `mut value ?T` parameter keeps the `&?&T` calling convention while its body reads `?T`,
+54 -27
View File
@@ -15,21 +15,15 @@ import v.util
// short_name_view returns the suffix after the final dot without allocating.
@[direct_array_access; inline]
fn short_name_view(name string) string {
for i := name.len - 1; i >= 0; i-- {
if name[i] == `.` {
return unsafe { name.substr_unsafe(i + 1, name.len) }
}
}
i := name.last_index_u8(`.`)
if i >= 0 { return unsafe { name.substr_unsafe(i + 1, name.len) } }
return name
}
@[direct_array_access; inline]
fn owner_name_view(name string) string {
for i := name.len - 1; i >= 0; i-- {
if name[i] == `.` {
return unsafe { name.substr_unsafe(0, i) }
}
}
i := name.last_index_u8(`.`)
if i >= 0 { return unsafe { name.substr_unsafe(0, i) } }
return name
}
@@ -382,7 +376,7 @@ mut:
owner &TypeInterner = unsafe { nil }
texts [128]u32
ids [128]TypeId
values []Type
values []&Type
}
struct IndexedTypeCache {
@@ -6404,6 +6398,7 @@ pub fn (tc &TypeChecker) ownership_time_spent_us() i64 {
}
// qualify_name resolves a name to its module-qualified form in the current checker context.
@[inline]
pub fn (tc &TypeChecker) qualify_name(name string) string {
// Builtin spellings cannot depend on imports, declarations or the current
// module. Avoid the contextual cache and wrapper walk for these leaves.
@@ -6451,6 +6446,7 @@ pub fn (tc &TypeChecker) qualify_type_name(name_id u32) u32 {
return tc.qualify_type_name_cached(name_id, '')
}
@[inline]
fn (tc &TypeChecker) qualify_type_name_cached(name_id u32, provided_name string) u32 {
mut cache := tc.qualify_name_cache
fingerprint := tc.qualify_table_fingerprint()
@@ -6473,6 +6469,11 @@ fn (tc &TypeChecker) qualify_type_name_cached(name_id u32, provided_name string)
cache.last_valid = true
return cached
}
return tc.qualify_type_name_miss(name_id, provided_name)
}
fn (tc &TypeChecker) qualify_type_name_miss(name_id u32, provided_name string) u32 {
mut cache := tc.qualify_name_cache
// Only a miss needs spelling bytes. String callers already have those bytes.
name := if provided_name.len > 0 { provided_name } else { flat.source_type_name(name_id) }
result := tc.qualify_name_uncached(name)
@@ -10577,8 +10578,17 @@ fn for_in_ref_binding_type(typ Type, yields_ref bool) Type {
}
// expr_type returns the resolved type recorded for a node during annotate_types.
@[direct_array_access]
@[direct_array_access; inline]
pub fn (tc &TypeChecker) expr_type(id flat.NodeId) ?Type {
idx := int(id)
if idx < 0 || tc.a.nodes[idx].kind != .call {
return tc.cached_expr_type(id)
}
return tc.call_expr_type(id)
}
@[direct_array_access]
fn (tc &TypeChecker) call_expr_type(id flat.NodeId) ?Type {
if t := tc.resolved_call_type(id) {
return t
}
@@ -10588,8 +10598,9 @@ pub fn (tc &TypeChecker) expr_type(id flat.NodeId) ?Type {
return *tc.parse_type(node.typ)
}
}
if t := tc.cached_expr_type_ref(id) {
return *t
cached_type_ref0 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref0) {
return *cached_type_ref0
}
return none
}
@@ -10615,9 +10626,10 @@ fn (tc &TypeChecker) resolved_call_type(id flat.NodeId) ?Type {
}
}
}
if t := tc.cached_expr_type_ref(id) {
if t !is Void {
return *t
cached_type_ref1 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref1) {
if cached_type_ref1 !is Void {
return *cached_type_ref1
}
}
if name := tc.cached_resolved_call(id) {
@@ -10667,38 +10679,52 @@ pub fn (tc &TypeChecker) invalidate_checked_expr_type(idx int) {
@[inline]
fn (tc &TypeChecker) cached_expr_type(id flat.NodeId) ?Type {
if typ := tc.cached_expr_type_ref(id) {
return *typ
cached_type_ref2 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref2) {
return *cached_type_ref2
}
return none
}
// Dense slots point at immutable canonical types. Sparse map values need stable
// copies because later insertions can move the map backing storage.
// copies because later insertions can move the map backing storage. A nil pointer
// represents a cache miss without an optional-result wrapper on this hot path.
@[direct_array_access; inline]
fn (tc &TypeChecker) cached_expr_type_ref(id flat.NodeId) &Type {
idx := int(id)
if isnil(tc.storage_query_probe) && (!tc.parallel_check_sparse || tc.in_check_range(idx)) {
if idx >= 0 && idx < tc.expr_type_set.len && tc.expr_type_set[idx] {
return tc.expr_type_values[idx]
}
return unsafe { nil }
}
return tc.cached_expr_type_ref_sparse(id)
}
@[direct_array_access]
fn (tc &TypeChecker) cached_expr_type_ref(id flat.NodeId) ?&Type {
fn (tc &TypeChecker) cached_expr_type_ref_sparse(id flat.NodeId) &Type {
idx := int(id)
if !isnil(tc.storage_query_probe) {
if typ := tc.sparse_expr_type_values[idx] { return new_type_reference(typ) }
if !isnil(tc.storage_query_probe.read_base) {
return tc.storage_query_probe.read_base.cached_expr_type_ref(id)
}
return none
return unsafe { nil }
}
if tc.parallel_check_sparse {
if tc.in_check_range(idx) {
if idx < tc.expr_type_set.len && tc.expr_type_set[idx] {
return tc.expr_type_values[idx]
}
return none
return unsafe { nil }
}
if typ := tc.sparse_expr_type_values[idx] { return new_type_reference(typ) }
return none
return unsafe { nil }
}
if idx >= 0 && idx < tc.expr_type_set.len && tc.expr_type_set[idx] {
return tc.expr_type_values[idx]
}
return none
return unsafe { nil }
}
// cached_resolved_call supports cached resolved call handling for TypeChecker.
@@ -14041,8 +14067,7 @@ fn is_c_backend_test_file(path string) bool {
}
fn is_regular_v_test_file(path string) bool {
file := path_leaf_view(path)
return file.ends_with('_test.v') || file.ends_with('_test.vv')
return path.ends_with('_test.v') || path.ends_with('_test.vv')
}
fn export_qualified_fn_name(module_name string, name string) string {
@@ -20530,6 +20555,7 @@ fn (tc &TypeChecker) match_without_else_exhaustive_sumtype_returns(node flat.Nod
// unalias_type follows an alias chain to its underlying type, unwrapping each
// Alias to its base type until a non-alias type is reached and returned.
@[inline]
pub fn unalias_type(t &Type) &Type {
mut current := t
for current is Alias {
@@ -20538,6 +20564,7 @@ pub fn unalias_type(t &Type) &Type {
return current
}
@[inline]
fn unalias_and_unwrap_pointer_type(t &Type) Type {
mut cur := t
for {
+76 -58
View File
@@ -2117,7 +2117,7 @@ fn (tc &TypeChecker) const_decl_is_in_top_level_comptime(id flat.NodeId) bool {
}
// check_node validates check node state for types.
@[direct_array_access]
@[direct_array_access; inline]
fn (mut tc TypeChecker) check_node(id flat.NodeId) {
idx := int(id)
if idx < 0 {
@@ -2131,10 +2131,7 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {
// and its re-entry bookkeeping; their diagnostics still run on every visit.
node := tc.a.nodes[idx]
if node.kind == .string_literal {
literal_pos := tc.string_literal_diagnostic_pos(node)
if !tc.string_literal_source_is_valid_utf8(literal_pos) {
tc.record_notice_at(.unknown_ident, 'invalid utf8 byte sequence in string literal', id, literal_pos)
}
tc.check_string_literal_node(id, node)
return
}
kind_id := node_kind_id(node)
@@ -2149,6 +2146,13 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {
tc.check_nonliteral_node(id)
}
fn (mut tc TypeChecker) check_string_literal_node(id flat.NodeId, node flat.Node) {
literal_pos := tc.string_literal_diagnostic_pos(node)
if !tc.string_literal_source_is_valid_utf8(literal_pos) {
tc.record_notice_at(.unknown_ident, 'invalid utf8 byte sequence in string literal', id, literal_pos)
}
}
fn (mut tc TypeChecker) check_nonliteral_node(id flat.NodeId) {
idx := int(id)
if tc.parallel_check_sparse {
@@ -2419,7 +2423,7 @@ fn (mut tc TypeChecker) check_nonliteral_node(id flat.NodeId) {
}
unsafe_alias_state := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_state :=
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
$if ownership ? {
tc.ownership_check_defer_stmt(id, node)
} $else {
@@ -2427,7 +2431,7 @@ fn (mut tc TypeChecker) check_nonliteral_node(id flat.NodeId) {
}
tc.fn_context.unsafe_reference_alias_owners = unsafe_alias_state.clone()
tc.fn_context.pointer_binding_value_keys =
clone_pointer_binding_value_keys(pointer_alias_state)
snapshot_pointer_binding_value_keys(pointer_alias_state)
return
}
if node.kind == .spawn_expr {
@@ -2577,7 +2581,7 @@ fn (mut tc TypeChecker) check_nonliteral_node(id flat.NodeId) {
tc.check_node(lhs_id)
unsafe_alias_skipped_rhs := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_skipped_rhs :=
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
saved_smartcasts := clone_smartcasts(tc.smartcasts)
for sc in smartcasts {
if valid_string_data(sc.name) {
@@ -2638,7 +2642,7 @@ fn (mut tc TypeChecker) check_nonliteral_node(id flat.NodeId) {
if node.kind == .infix && node.op in [.logical_and, .logical_or] && i == 1 {
unsafe_alias_skipped_rhs = tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_skipped_rhs =
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
previous_channel_send_or_expr_id := tc.channel_send_or_expr_id
if node.kind == .infix && node.op == .arrow && i == 1 {
@@ -10226,12 +10230,12 @@ fn (mut tc TypeChecker) check_select_stmt(node flat.Node) {
mut invalidated_smartcasts := map[string]bool{}
unsafe_alias_base := tc.fn_context.unsafe_reference_alias_owners.clone()
mut unsafe_alias_paths := []map[string]bool{}
pointer_alias_base := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_base := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
mut pointer_alias_paths := []map[string][]string{}
for i in 0 .. node.children_count {
tc.fn_context.unsafe_reference_alias_owners = unsafe_alias_base.clone()
tc.fn_context.pointer_binding_value_keys =
clone_pointer_binding_value_keys(pointer_alias_base)
snapshot_pointer_binding_value_keys(pointer_alias_base)
if base_smartcasts.len > 0 {
tc.smartcasts = clone_smartcasts(base_smartcasts)
}
@@ -10341,7 +10345,7 @@ fn (mut tc TypeChecker) check_select_stmt(node flat.Node) {
}
if !tc.stmt_sequence_definitely_returns(&branch, body_start) {
unsafe_alias_paths << tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
for key, _ in base_smartcasts {
if key !in tc.smartcasts {
@@ -10354,7 +10358,7 @@ fn (mut tc TypeChecker) check_select_stmt(node flat.Node) {
tc.smartcasts.delete(key)
}
tc.fn_context.unsafe_reference_alias_owners = intersect_unsafe_reference_alias_states(unsafe_alias_paths, unsafe_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(pointer_alias_paths, pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(pointer_alias_paths, pointer_alias_base)
$if ownership ? {
tc.ownership_end_branch_group()
}
@@ -10944,7 +10948,7 @@ fn (mut tc TypeChecker) check_or_expr(id flat.NodeId, node flat.Node) {
}
unsafe_alias_success := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_success :=
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
$if ownership ? {
tc.ownership_begin_value_branch_group()
tc.ownership_begin_branch()
@@ -10973,14 +10977,14 @@ fn (mut tc TypeChecker) check_or_expr(id flat.NodeId, node flat.Node) {
tc.pop_scope()
mut unsafe_alias_paths := [unsafe_alias_success]
mut pointer_alias_paths := [
clone_pointer_binding_value_keys(pointer_alias_success),
snapshot_pointer_binding_value_keys(pointer_alias_success),
]
if !tc.stmt_definitely_returns(fallback_id) {
unsafe_alias_paths << tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
tc.fn_context.unsafe_reference_alias_owners = intersect_unsafe_reference_alias_states(unsafe_alias_paths, unsafe_alias_success)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(pointer_alias_paths, pointer_alias_success)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(pointer_alias_paths, pointer_alias_success)
if require_fallback_value {
tail_id := tc.branch_tail_expr_id(fallback_id)
if tc.valid_node_id(tail_id) {
@@ -14233,7 +14237,7 @@ fn (mut tc TypeChecker) check_fn_literal(id flat.NodeId, node flat.Node) {
tc.fn_context.method_value_stack_mut_owners =
saved_fn_context.method_value_stack_mut_owners.clone()
tc.fn_context.pointer_binding_value_keys =
clone_pointer_binding_value_keys(captured_pointer_values)
snapshot_pointer_binding_value_keys(captured_pointer_values)
$if ownership ? {
tc.ownership_begin_fn_literal(id, node)
}
@@ -14865,7 +14869,7 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
}
}
unsafe_alias_base := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_base := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_base := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
loop_may_skip_body := node.children_count > 1 && tc.a.child_node(&node, 1).kind != .empty
$if ownership ? {
if node.children_count > 2 {
@@ -14902,9 +14906,9 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
}
}
unsafe_alias_post := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_post := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_post := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
tc.fn_context.unsafe_reference_alias_owners = unsafe_alias_base.clone()
tc.fn_context.pointer_binding_value_keys = clone_pointer_binding_value_keys(pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = snapshot_pointer_binding_value_keys(pointer_alias_base)
tc.fn_context.unsafe_alias_break_states << []map[string]bool{}
tc.fn_context.pointer_alias_break_states << []map[string][]string{}
tc.fn_context.pointer_alias_continue_states << []map[string][]string{}
@@ -14930,7 +14934,7 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
pointer_alias_break_states := tc.take_pointer_binding_loop_break_states()
pointer_alias_continue_states := tc.take_pointer_binding_loop_continue_states()
unsafe_alias_body := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_body := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_body := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
if tc.valid_node_id(unreachable_id) && tc.should_diagnose(unreachable_id) {
tc.record_error_at(.return_mismatch, 'unreachable code', unreachable_id, tc.unreachable_statement_diagnostic_pos(unreachable_id))
}
@@ -14964,7 +14968,7 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
mut pointer_alias_paths := []map[string][]string{}
if loop_may_skip_body {
unsafe_alias_paths << unsafe_alias_base
pointer_alias_paths << clone_pointer_binding_value_keys(pointer_alias_base)
pointer_alias_paths << snapshot_pointer_binding_value_keys(pointer_alias_base)
}
if unsafe_alias_break_states.len > 0 {
for state in unsafe_alias_break_states {
@@ -14975,10 +14979,10 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
}
if pointer_alias_break_states.len > 0 {
for state in pointer_alias_break_states {
pointer_alias_paths << clone_pointer_binding_value_keys(state)
pointer_alias_paths << snapshot_pointer_binding_value_keys(state)
}
} else if body_may_break {
pointer_alias_paths << clone_pointer_binding_value_keys(pointer_alias_body)
pointer_alias_paths << snapshot_pointer_binding_value_keys(pointer_alias_body)
}
if loop_may_skip_body || body_may_break {
for state in pointer_alias_continue_states {
@@ -14993,10 +14997,10 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
unsafe_alias_paths << unsafe_alias_base
}
if pointer_alias_paths.len == 0 {
pointer_alias_paths << clone_pointer_binding_value_keys(pointer_alias_base)
pointer_alias_paths << snapshot_pointer_binding_value_keys(pointer_alias_base)
}
tc.fn_context.unsafe_reference_alias_owners = intersect_unsafe_reference_alias_states(unsafe_alias_paths, unsafe_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(pointer_alias_paths, pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(pointer_alias_paths, pointer_alias_base)
if has_cond_smartcasts {
tc.smartcasts = clone_smartcasts(saved_smartcasts)
}
@@ -15037,7 +15041,7 @@ fn (mut tc TypeChecker) record_pointer_binding_loop_break_state_at_depth(depth i
if index < 0 {
return
}
tc.fn_context.pointer_alias_break_states[index] << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
tc.fn_context.pointer_alias_break_states[index] << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
fn (mut tc TypeChecker) take_pointer_binding_loop_break_states() []map[string][]string {
@@ -15047,7 +15051,7 @@ fn (mut tc TypeChecker) take_pointer_binding_loop_break_states() []map[string][]
index := tc.fn_context.pointer_alias_break_states.len - 1
mut result := []map[string][]string{cap: tc.fn_context.pointer_alias_break_states[index].len}
for state in tc.fn_context.pointer_alias_break_states[index] {
result << clone_pointer_binding_value_keys(state)
result << snapshot_pointer_binding_value_keys(state)
}
tc.fn_context.pointer_alias_break_states.delete_last()
return result
@@ -15062,7 +15066,7 @@ fn (mut tc TypeChecker) record_pointer_binding_loop_continue_state_at_depth(dept
if index < 0 {
return
}
tc.fn_context.pointer_alias_continue_states[index] << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
tc.fn_context.pointer_alias_continue_states[index] << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
fn (mut tc TypeChecker) take_pointer_binding_loop_continue_states() []map[string][]string {
@@ -15072,7 +15076,7 @@ fn (mut tc TypeChecker) take_pointer_binding_loop_continue_states() []map[string
index := tc.fn_context.pointer_alias_continue_states.len - 1
mut result := []map[string][]string{cap: tc.fn_context.pointer_alias_continue_states[index].len}
for state in tc.fn_context.pointer_alias_continue_states[index] {
result << clone_pointer_binding_value_keys(state)
result << snapshot_pointer_binding_value_keys(state)
}
tc.fn_context.pointer_alias_continue_states.delete_last()
return result
@@ -15883,7 +15887,7 @@ fn (mut tc TypeChecker) check_for_in_stmt(node flat.Node) {
}
}
unsafe_alias_base := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_base := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_base := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
tc.fn_context.unsafe_alias_break_states << []map[string]bool{}
tc.fn_context.pointer_alias_break_states << []map[string][]string{}
tc.fn_context.pointer_alias_continue_states << []map[string][]string{}
@@ -15927,22 +15931,22 @@ fn (mut tc TypeChecker) check_for_in_stmt(node flat.Node) {
tc.ownership_end_branch_group()
}
mut unsafe_alias_paths := [unsafe_alias_base]
mut pointer_alias_paths := [clone_pointer_binding_value_keys(pointer_alias_base)]
mut pointer_alias_paths := [snapshot_pointer_binding_value_keys(pointer_alias_base)]
for state in unsafe_alias_break_states {
unsafe_alias_paths << state.clone()
}
for state in pointer_alias_break_states {
pointer_alias_paths << clone_pointer_binding_value_keys(state)
pointer_alias_paths << snapshot_pointer_binding_value_keys(state)
}
for state in pointer_alias_continue_states {
pointer_alias_paths << clone_pointer_binding_value_keys(state)
pointer_alias_paths << snapshot_pointer_binding_value_keys(state)
}
if !tc.stmt_sequence_definitely_returns(&node, header) {
unsafe_alias_paths << tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
tc.fn_context.unsafe_reference_alias_owners = intersect_unsafe_reference_alias_states(unsafe_alias_paths, unsafe_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(pointer_alias_paths, pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(pointer_alias_paths, pointer_alias_base)
tc.pop_scope()
if mut_binding_name.len > 0 {
if had_mut_base {
@@ -17737,9 +17741,10 @@ fn (mut tc TypeChecker) restore_return_alias_for_in_bindings(node flat.Node) {
fn (mut tc TypeChecker) return_alias_decl_binding_type(node flat.Node, index int) Type {
lhs_id := tc.multi_assign_lhs_id(node, index)
if typ := tc.cached_expr_type_ref(lhs_id) {
if typ !is Unknown && typ !is Void {
return *typ
cached_type_ref0 := tc.cached_expr_type_ref(lhs_id)
if !isnil(cached_type_ref0) {
if cached_type_ref0 !is Unknown && cached_type_ref0 !is Void {
return *cached_type_ref0
}
}
lhs_count := tc.multi_assign_lhs_count(node)
@@ -18202,9 +18207,9 @@ fn (tc &TypeChecker) cur_scope_depth() int {
}
fn clone_scope_binding_owner_map(src map[string][]ScopeBindingOwner) map[string][]ScopeBindingOwner {
mut out := map[string][]ScopeBindingOwner{}
for name, owners in src {
out[name] = owners.clone()
mut out := src.clone()
for _, mut owners in out {
owners = owners.clone()
}
return out
}
@@ -20128,7 +20133,7 @@ fn (mut tc TypeChecker) check_assign(id flat.NodeId, node flat.Node) {
assignment_is_translated := tc.node_is_from_translated_file(node)
is_cross_assignment := node.op == .assign && node.children_count > 2
pointer_alias_assignment_rhs := if is_cross_assignment {
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
} else {
map[string][]string{}
}
@@ -20817,10 +20822,17 @@ fn (tc &TypeChecker) pointer_binding_ident_values(owner ScopeBindingOwner, state
return (state[key] or { [tc.pointer_binding_storage_value(owner)] }).clone()
}
// snapshot_pointer_binding_value_keys copies a branch's map while sharing its immutable value lists.
// Checker updates replace a whole list; they must not append to or modify a stored list in place.
@[inline]
fn snapshot_pointer_binding_value_keys(src map[string][]string) map[string][]string {
return src.clone()
}
fn clone_pointer_binding_value_keys(src map[string][]string) map[string][]string {
mut result := map[string][]string{}
for key, values in src {
result[key] = values.clone()
mut result := src.clone()
for _, mut values in result {
values = values.clone()
}
return result
}
@@ -20851,19 +20863,24 @@ fn pointer_binding_states_same(a map[string][]string, b map[string][]string) boo
return true
}
// merge_pointer_binding_value_states returns an owning copy for callers that may edit value lists.
fn merge_pointer_binding_value_states(states []map[string][]string, fallback map[string][]string) map[string][]string {
return clone_pointer_binding_value_keys(merge_pointer_binding_snapshots(states, fallback))
}
fn merge_pointer_binding_snapshots(states []map[string][]string, fallback map[string][]string) map[string][]string {
if states.len == 0 {
return clone_pointer_binding_value_keys(fallback)
return snapshot_pointer_binding_value_keys(fallback)
}
// Branches that left every binding as it was merge into the fallback itself.
if states.all(pointer_binding_states_same(fallback, it)) {
return clone_pointer_binding_value_keys(fallback)
return snapshot_pointer_binding_value_keys(fallback)
}
// Merge each key once, in first-seen order: the fallback's keys, then the new
// keys of each state in turn.
mut result := map[string][]string{}
for key, _ in fallback {
result[key] = merge_pointer_binding_key_values(states, fallback, key)
mut result := fallback.clone()
for key, mut values in result {
values = merge_pointer_binding_key_values(states, fallback, key)
}
for state in states {
for key, _ in state {
@@ -20906,10 +20923,11 @@ fn (mut tc TypeChecker) pointer_binding_state_after_loop_post(loop_node flat.Nod
if post.kind == .postfix && post.op in [.inc, .dec] && post.children_count == 1 {
target_id := tc.a.child(post, 0)
if tc.a.node(target_id).kind == .ident {
if target_type := tc.cached_expr_type_ref(target_id) {
cached_type_ref1 := tc.cached_expr_type_ref(target_id)
if !isnil(cached_type_ref1) {
// Rechecking a scalar counter cannot change pointer identities. The
// post has already been checked for diagnostics above the loop body.
if unalias_type(target_type) is Primitive && precomputed == base {
if unalias_type(cached_type_ref1) is Primitive && precomputed == base {
return clone_pointer_binding_value_keys(incoming)
}
}
@@ -20920,7 +20938,7 @@ fn (mut tc TypeChecker) pointer_binding_state_after_loop_post(loop_node flat.Nod
error_count := tc.errors.len
notice_count := tc.notices.len
pending_ierror_count := tc.pending_ierror_errors.len
tc.fn_context.pointer_binding_value_keys = clone_pointer_binding_value_keys(incoming)
tc.fn_context.pointer_binding_value_keys = snapshot_pointer_binding_value_keys(incoming)
if has_cond_smartcasts {
tc.smartcasts = clone_smartcasts(precondition_smartcasts)
}
@@ -21013,17 +21031,17 @@ fn (mut tc TypeChecker) merge_pointer_binding_short_circuit_state(op flat.Op, lh
if pointer_binding_states_same(skipped_rhs, tc.fn_context.pointer_binding_value_keys) {
return
}
executed_rhs := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
executed_rhs := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
if lhs := tc.constant_bool_value(lhs_id) {
rhs_executes := (op == .logical_and && lhs) || (op == .logical_or && !lhs)
tc.fn_context.pointer_binding_value_keys = if rhs_executes {
executed_rhs
} else {
clone_pointer_binding_value_keys(skipped_rhs)
snapshot_pointer_binding_value_keys(skipped_rhs)
}
return
}
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states([
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots([
skipped_rhs,
executed_rhs,
], skipped_rhs)
+41 -1
View File
@@ -37,12 +37,26 @@ fn (tc &TypeChecker) invalidate_lexical_parent_memo() {
// preceding-write checks still use the same node as the ordinary parent walk.
// Keep the full distance: match narrowing has a 64-parent limit, while if/for
// narrowing remains available beyond that limit.
@[direct_array_access]
@[direct_array_access; inline]
fn (tc &TypeChecker) lexical_parent_link(id flat.NodeId) (flat.NodeId, flat.NodeId, int) {
initial_idx := int(id)
if initial_idx < 0 || initial_idx >= tc.direct_parent_ids.len {
return flat.empty_node, id, 1
}
memo := tc.lexical_parent_memo
if !isnil(memo) && memo.nodes_len == tc.a.nodes.len
&& memo.parents_len == tc.direct_parent_ids.len {
slot := initial_idx & 2047
if memo.generations[slot] == memo.generation && memo.ids[slot] == initial_idx {
return memo.parents[slot], memo.children[slot], memo.depths[slot]
}
}
return tc.lexical_parent_link_miss(id)
}
@[direct_array_access]
fn (tc &TypeChecker) lexical_parent_link_miss(id flat.NodeId) (flat.NodeId, flat.NodeId, int) {
initial_idx := int(id)
mut wtc := unsafe { tc }
if isnil(wtc.lexical_parent_memo) {
wtc.lexical_parent_memo = &LexicalParentMemo{
@@ -213,6 +227,7 @@ fn (tc &TypeChecker) node_outside_unsafe_blocks(idx int) bool {
// lexical_smartcast_key_unused reports that no lexical smartcast of the
// function being checked can narrow `key` at the node `idx`.
@[inline]
fn (tc &TypeChecker) lexical_smartcast_key_unused(idx int, key string) bool {
if isnil(tc.smartcast_key_filter) {
return false
@@ -226,3 +241,28 @@ fn (tc &TypeChecker) lexical_smartcast_key_unused(idx int, key string) bool {
}
return !filter.has(key)
}
// lexical_smartcasts_unused rules out lexical narrowing of this expression
// without allocating its composite key when the function has few smartcasts.
@[inline]
fn (tc &TypeChecker) lexical_smartcasts_unused(idx int) bool {
if isnil(tc.smartcast_key_filter) {
return false
}
mut filter := tc.smartcast_key_filter
if idx < filter.lo || idx > filter.hi || filter.lo < 0 {
return false
}
if !filter.ready {
tc.collect_smartcast_keys(mut filter)
}
if filter.index.len > 0 {
return false
}
for key in filter.keys {
if tc.expr_key_matches(flat.NodeId(idx), key) {
return false
}
}
return true
}
+24 -7
View File
@@ -703,6 +703,7 @@ fn (mut p Pass2TypePromotion) promote(scope voidptr, typ Type) Type {
// Bootstrap compilers can still box Type variants. Taking the address after
// narrowing finds that box as well as the payload of an inline tagged union.
// Large immutable variants keep their payload behind an embedded reference.
fn type_payload_refers_to_scope(scope voidptr, typ &Type) bool {
if scope == unsafe { nil } {
return false
@@ -734,7 +735,20 @@ fn type_payload_refers_to_scope(scope voidptr, typ &Type) bool {
MultiReturn { voidptr(&typ) }
}
// The allocator only checks whether this storage belongs to the live scope.
return unsafe { prealloc_scope_owns(scope, payload) }
if unsafe { prealloc_scope_owns(scope, payload) } {
return true
}
indirect := match typ {
Unknown { voidptr(typ.UnknownDataRef) }
ArrayFixed { voidptr(typ.ArrayFixedDataRef) }
Channel { voidptr(typ.ChannelDataRef) }
Map { voidptr(typ.MapDataRef) }
FnType { voidptr(typ.FnTypeDataRef) }
Alias { voidptr(typ.AliasDataRef) }
MultiReturn { voidptr(typ.MultiReturnDataRef) }
else { return false }
}
return unsafe { prealloc_scope_owns(scope, indirect) }
}
// type_refers_to_scope inspects actual descriptor references and their metadata storage.
@@ -5259,8 +5273,11 @@ fn promote_cached_name_value(name &CachedName, mut cache CheckNamePromotionCache
return promoted
}
fn (tc &TypeChecker) cached_check_type_promotion(typ Type, mut cache CheckTypePromotionCache, promote_missing bool, frozen_interner bool) ?&Type {
slot := int(semantic_type_hash(typ) & u64(cache.set.len - 1))
@[direct_array_access; inline]
fn (tc &TypeChecker) cached_check_type_promotion(typ &Type, mut cache CheckTypePromotionCache, promote_missing bool, frozen_interner bool) ?&Type {
// Slot selection stays shallow; structural verification below guards collisions
// and borrowed metadata that changed since a previous lookup.
slot := int(canonical_intern_cache_key(typ) & u64(cache.set.len - 1))
if cache.set[slot] && semantic_types_equal(cache.values[slot], typ) {
return cache.values[slot]
}
@@ -5268,7 +5285,7 @@ fn (tc &TypeChecker) cached_check_type_promotion(typ Type, mut cache CheckTypePr
if !promote_missing {
return none
}
tc.promote_check_type(typ)
tc.promote_check_type(*typ)
}
cache.values[slot] = canonical
cache.set[slot] = true
@@ -5303,7 +5320,7 @@ fn check_clone_chunk_thread(arg voidptr) voidptr {
tc.resolved_call_names[idx] = promote_cached_name_value(tc.resolved_call_names[idx], mut names)
}
if idx < tc.expr_type_set.len && tc.expr_type_set[idx] {
if canonical := tc.cached_check_type_promotion(*tc.expr_type_values[idx], mut cache, false, true) {
if canonical := tc.cached_check_type_promotion(tc.expr_type_values[idx], mut cache, false, true) {
tc.expr_type_values[idx] = canonical
} else {
a.miss << idx
@@ -5317,7 +5334,7 @@ fn check_clone_chunk_thread(arg voidptr) voidptr {
fn (mut tc TypeChecker) intern_expr_type_misses(indexes []int) {
mut cache := CheckTypePromotionCache{}
for idx in indexes {
tc.expr_type_values[idx] = tc.cached_check_type_promotion(*tc.expr_type_values[idx], mut cache, true, false) or {
tc.expr_type_values[idx] = tc.cached_check_type_promotion(tc.expr_type_values[idx], mut cache, true, false) or {
tc.promote_check_type(*tc.expr_type_values[idx])
}
}
@@ -5351,7 +5368,7 @@ fn (mut tc TypeChecker) clone_parallel_worker_node_caches(items []CheckWorkItem)
tc.resolved_call_names[idx] = promote_cached_name_value(tc.resolved_call_names[idx], mut names)
}
if idx < tc.expr_type_set.len && tc.expr_type_set[idx] {
tc.expr_type_values[idx] = tc.cached_check_type_promotion(*tc.expr_type_values[idx], mut cache, true, false) or {
tc.expr_type_values[idx] = tc.cached_check_type_promotion(tc.expr_type_values[idx], mut cache, true, false) or {
tc.promote_check_type(*tc.expr_type_values[idx])
}
}
+22
View File
@@ -191,6 +191,28 @@ fn test_type_promotion_cache_preserves_misses_and_distinct_live_payloads() {
assert tc.type_count() == initial_count + 600
}
fn test_type_promotion_cache_verifies_borrowed_metadata_after_reuse() {
a := flat.FlatAst.new()
mut tc := TypeChecker.new(&a)
mut cache := CheckTypePromotionCache{}
mut params := [FnParam{ typ: &builtin_int_type }]
typ := Type(FnType{ params: params, return_type: &builtin_int_type })
first := tc.cached_check_type_promotion(typ, mut cache, true, false)?
assert first.name() == 'fn(int) int'
// The wrapper, list address and length stay the same. The interner must own
// the first payload, and a frozen probe must not mistake the new one for it.
params[0] = FnParam{ typ: &builtin_string_type, is_mut: true }
assert tc.cached_check_type_promotion(typ, mut cache, false, true) == none
second := tc.cached_check_type_promotion(typ, mut cache, true, false)?
assert !semantic_types_equal(first, second)
assert first.name() == 'fn(int) int'
assert second is FnType
assert second.params[0].is_mut
assert second.params[0].typ is String
assert voidptr(tc.cached_check_type_promotion(typ, mut cache, false, true)?) == voidptr(second)
}
fn test_check_heap_partitions_match_linear_load_selection() {
for count in [0, 1, 2, 33, 256, 1500] {
mut items := []CheckWorkItem{}
+40 -34
View File
@@ -1137,10 +1137,10 @@ fn (tc &TypeChecker) expr_is_mut_arg_for_key(id flat.NodeId, key string) bool {
}
node := tc.a.nodes[int(id)]
if node.kind in [.paren, .expr_stmt] && node.children_count > 0 {
return (node.is_mut && tc.expr_key(id) == key)
return (node.is_mut && tc.expr_key_matches(id, key))
|| tc.expr_is_mut_arg_for_key(tc.a.child(&node, 0), key)
}
return node.is_mut && tc.expr_key(id) == key
return node.is_mut && tc.expr_key_matches(id, key)
}
// expr_mut_arg_key returns the resolved expression key of a `mut` argument
@@ -3805,7 +3805,7 @@ fn (tc &TypeChecker) interface_smartcast_uses_value_pattern_for_key(id flat.Node
}
}
} else if parent.kind == .match_stmt && parent.children_count > 0
&& tc.expr_key(tc.a.child(parent, 0)) == key {
&& tc.expr_key_matches(tc.a.child(parent, 0), key) {
branch := tc.a.node(current)
if branch.kind == .match_branch && branch.value == '1' && branch.children_count > 0 {
if pattern_source_id := tc.match_type_pattern(tc.a.child_node(branch, 0)) {
@@ -3834,7 +3834,7 @@ fn (tc &TypeChecker) condition_uses_value_smartcast_pattern(id flat.NodeId, key
return none
}
node := tc.a.node(id)
if node.kind == .is_expr && node.children_count > 0 && tc.expr_key(tc.a.child(node, 0)) == key {
if node.kind == .is_expr && node.children_count > 0 && tc.expr_key_matches(tc.a.child(node, 0), key) {
return unalias_type(tc.parse_type(flat.source_type_id(node.value))) !is Pointer
}
if node.kind == .ident {
@@ -4492,7 +4492,7 @@ fn (mut tc TypeChecker) invalid_ierror_return_expr_type_name(id flat.NodeId, exp
continue
}
tail := tc.branch_tail_expr_id(branch_id)
if subject_key.len > 0 && tc.expr_key(tail) == subject_key && branch.value != 'else'
if subject_key.len > 0 && tc.expr_key_matches(tail, subject_key) && branch.value != 'else'
&& branch.value.int() == 1 && subject_type is SumType {
cond := tc.a.node(tc.a.child(&branch, 0))
if pattern_source_id := tc.match_type_pattern(cond) {
@@ -9000,13 +9000,16 @@ fn module_import_lines(source string) []int {
if at < 0 {
break
}
for counted < at {
if source[counted] == `\n` {
line_number++
line_start = counted + 1
for {
newline := source.index_after_('\n', counted)
if newline < 0 || newline >= at {
break
}
counted++
line_number++
line_start = newline + 1
counted = newline + 1
}
counted = at
mut indented := true
for i in line_start .. at {
if source[i] != ` ` && source[i] != `\t` {
@@ -12293,7 +12296,7 @@ fn (tc &TypeChecker) visible_mutation_fn_decl(name string, fallback_mod string)
}
mut cur_mod := ''
for i in tc.top_level_idx {
node := tc.a.nodes[i]
node := tc.a.node(flat.NodeId(i))
match node.kind {
.file {
cur_mod = tc.file_modules[node.value] or { '' }
@@ -12342,10 +12345,10 @@ fn (tc &TypeChecker) visible_mutation_fn_param(decl VisibleMutationFnDecl, param
if decl.idx < 0 || decl.idx >= tc.a.nodes.len || param_idx < 0 {
return none
}
fn_node := tc.a.nodes[decl.idx]
fn_node := tc.a.node(flat.NodeId(decl.idx))
mut idx := 0
for i in 0 .. fn_node.children_count {
child := tc.a.child_node(&fn_node, i)
child := tc.a.child_node(fn_node, i)
if child.kind != .param {
break
}
@@ -15347,9 +15350,10 @@ fn (mut tc TypeChecker) check_valid_call_arg_types(id flat.NodeId, node flat.Nod
} else {
tc.check_node(check_arg_id)
}
if multi := tc.cached_expr_type_ref(check_arg_id) {
if !info.is_variadic && multi is MultiReturn {
expanded_arg_offset += multi.types.len - 1
cached_type_ref0 := tc.cached_expr_type_ref(check_arg_id)
if !isnil(cached_type_ref0) {
if !info.is_variadic && cached_type_ref0 is MultiReturn {
expanded_arg_offset += cached_type_ref0.types.len - 1
}
}
if has_dsl_scope {
@@ -15525,7 +15529,7 @@ fn (tc &TypeChecker) subtree_can_rebind_mut_parameter(id flat.NodeId, name strin
if node.kind == .assign {
mut i := 0
for i + 1 < node.children_count {
if tc.expr_key(tc.a.child(node, i)) == name {
if tc.expr_key_matches(tc.a.child(node, i), name) {
return true
}
i += 2
@@ -15536,13 +15540,13 @@ fn (tc &TypeChecker) subtree_can_rebind_mut_parameter(id flat.NodeId, name strin
if node.kind == .call && node.children_count > 0 {
callee := tc.a.child_node(node, 0)
if callee.kind == .selector && callee.children_count > 0
&& tc.expr_key(tc.a.child(callee, 0)) == name
&& tc.expr_key_matches(tc.a.child(callee, 0), name)
&& tc.sum_type_has_mut_receiver_method(typ, callee.value) {
return true
}
for i in 1 .. node.children_count {
arg_id := tc.call_arg_value(tc.a.child(node, i))
if tc.a.node(arg_id).is_mut && tc.expr_key(arg_id) == name {
if tc.a.node(arg_id).is_mut && tc.expr_key_matches(arg_id, name) {
return true
}
}
@@ -15681,14 +15685,15 @@ fn (mut tc TypeChecker) check_call_arg_types(id flat.NodeId, node flat.Node, inf
mut logical_arg_count := recv_extra
for i in 1 + info.arg_offset .. node.children_count {
arg_id := tc.call_arg_value(tc.a.child(&node, i))
arg_type := tc.cached_expr_type(arg_id) or { tc.resolve_type(arg_id) }
logical_arg_count++
arg := tc.a.node(arg_id)
if !info.is_variadic && arg.kind == .call && arg_type is MultiReturn
&& !is_print_style_fn_name(info.name) {
actual_count += arg_type.types.len - 1
logical_arg_count += arg_type.types.len - 1
expanded_multi_return_arg = true
if !info.is_variadic && arg.kind == .call && !is_print_style_fn_name(info.name) {
arg_type := tc.cached_expr_type(arg_id) or { tc.resolve_type(arg_id) }
if arg_type is MultiReturn {
actual_count += arg_type.types.len - 1
logical_arg_count += arg_type.types.len - 1
expanded_multi_return_arg = true
}
}
if int(expanded_overflow_arg) < 0 && logical_arg_count > info.params.len {
expanded_overflow_arg = arg_id
@@ -18192,7 +18197,7 @@ fn (mut tc TypeChecker) check_array_sort_call(id flat.NodeId, node flat.Node, ca
lhs_id := tc.a.child(&arg, 0)
rhs_id := tc.a.child(&arg, 1)
lhs_key := tc.expr_key(lhs_id)
if lhs_key.len > 0 && lhs_key == tc.expr_key(rhs_id) {
if lhs_key.len > 0 && tc.expr_key_matches(rhs_id, lhs_key) {
tc.record_error_at(.call_arg_mismatch, '`.sort()` cannot use same argument', id, method_pos)
}
if invalid_id := tc.sort_first_invalid_ident(arg_id) {
@@ -21216,8 +21221,9 @@ fn (tc &TypeChecker) selector_fn_base_type(base_id flat.NodeId) ?Type {
}
}
}
if typ := tc.cached_expr_type_ref(base_id) {
return *typ
cached_type_ref1 := tc.cached_expr_type_ref(base_id)
if !isnil(cached_type_ref1) {
return *cached_type_ref1
}
if int(base_id) < 0 {
return none
@@ -22235,7 +22241,7 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
tc.record_constant_condition_diagnostics(cond_id)
unsafe_alias_base := clone_unsafe_alias_owners(tc.fn_context.unsafe_reference_alias_owners)
mut unsafe_alias_paths := []map[string]bool{}
pointer_alias_base := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_base := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
mut pointer_alias_paths := []map[string][]string{}
mut condition_is_true := false
mut condition_is_false := false
@@ -22288,12 +22294,12 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
}
if !condition_is_false && !tc.stmt_definitely_returns(then_id) {
unsafe_alias_paths << clone_unsafe_alias_owners(tc.fn_context.unsafe_reference_alias_owners)
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
tc.fn_context.unsafe_reference_alias_owners = clone_unsafe_alias_owners(unsafe_alias_base)
// A then branch that changed no pointer binding already left the base state.
if !pointer_binding_states_same(pointer_alias_base, tc.fn_context.pointer_binding_value_keys) {
tc.fn_context.pointer_binding_value_keys = clone_pointer_binding_value_keys(pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = snapshot_pointer_binding_value_keys(pointer_alias_base)
}
if had_saved_smartcasts {
tc.smartcasts = clone_smartcasts(saved_smartcasts)
@@ -22320,7 +22326,7 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
}
if !condition_is_true && !tc.stmt_definitely_returns(else_id) {
unsafe_alias_paths << clone_unsafe_alias_owners(tc.fn_context.unsafe_reference_alias_owners)
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
if else_smartcasts.len > 0 {
if had_saved_smartcasts {
@@ -22332,14 +22338,14 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
} else {
if !condition_is_true {
unsafe_alias_paths << clone_unsafe_alias_owners(unsafe_alias_base)
pointer_alias_paths << clone_pointer_binding_value_keys(pointer_alias_base)
pointer_alias_paths << snapshot_pointer_binding_value_keys(pointer_alias_base)
}
$if ownership ? {
tc.ownership_add_branch_group_base()
}
}
tc.fn_context.unsafe_reference_alias_owners = intersect_unsafe_reference_alias_states(unsafe_alias_paths, unsafe_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(pointer_alias_paths, pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(pointer_alias_paths, pointer_alias_base)
$if ownership ? {
tc.ownership_end_branch_group()
}
+345 -236
View File
@@ -262,7 +262,7 @@ fn (mut tc TypeChecker) update_pointer_alias_goto_state(id flat.NodeId) {
mut states := tc.fn_context.pointer_alias_goto_states[node.value] or {
[]map[string][]string{}
}
states << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
states << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
tc.fn_context.pointer_alias_goto_states[node.value] = states
return
}
@@ -279,12 +279,12 @@ fn (mut tc TypeChecker) update_pointer_alias_goto_state(id flat.NodeId) {
}
incoming := tc.fn_context.pointer_alias_goto_states[node.value] or { return }
mut paths := [
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys),
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys),
]
for state in incoming {
paths << clone_pointer_binding_value_keys(state)
paths << snapshot_pointer_binding_value_keys(state)
}
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(paths, tc.fn_context.pointer_binding_value_keys)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(paths, tc.fn_context.pointer_binding_value_keys)
tc.fn_context.pointer_alias_goto_states.delete(node.value)
}
@@ -1044,7 +1044,7 @@ fn (mut tc TypeChecker) check_condition(cond_id flat.NodeId) []LocalBinding {
}
unsafe_alias_skipped_rhs := tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_skipped_rhs :=
clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
saved_smartcasts := clone_smartcasts(tc.smartcasts)
for sc in lhs_smartcasts {
if valid_string_data(sc.name) {
@@ -1638,7 +1638,7 @@ fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) {
mut seen_match_patterns := map[string]int{}
unsafe_alias_base := tc.fn_context.unsafe_reference_alias_owners.clone()
mut unsafe_alias_paths := []map[string]bool{}
pointer_alias_base := clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_base := snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
mut pointer_alias_paths := []map[string][]string{}
// One in-place scan for the whole match: the old per-condition
// source_text_for_node(...).contains(...) copied the entire match span
@@ -1656,7 +1656,7 @@ fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) {
branch := tc.a.child_node(&node, i)
tc.fn_context.unsafe_reference_alias_owners = unsafe_alias_base.clone()
tc.fn_context.pointer_binding_value_keys =
clone_pointer_binding_value_keys(pointer_alias_base)
snapshot_pointer_binding_value_keys(pointer_alias_base)
if branch.kind != .match_branch {
tc.check_node(branch_id)
continue
@@ -1806,7 +1806,7 @@ fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) {
for j in 0 .. n_conds {
tc.fn_context.unsafe_reference_alias_owners = unsafe_alias_base.clone()
tc.fn_context.pointer_binding_value_keys =
clone_pointer_binding_value_keys(pointer_alias_base)
snapshot_pointer_binding_value_keys(pointer_alias_base)
cond := tc.a.node(tc.a.child(branch, j))
pattern_source_id_6 := tc.match_type_pattern(cond) or { continue }
@@ -1826,7 +1826,7 @@ fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) {
}
if !tc.match_branch_definitely_returns(branch) {
unsafe_alias_paths << tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
continue
}
@@ -1884,15 +1884,15 @@ fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) {
}
if !tc.match_branch_definitely_returns(branch) {
unsafe_alias_paths << tc.fn_context.unsafe_reference_alias_owners.clone()
pointer_alias_paths << clone_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
pointer_alias_paths << snapshot_pointer_binding_value_keys(tc.fn_context.pointer_binding_value_keys)
}
}
if !tc.match_has_else_or_exhaustive_coverage(node) {
unsafe_alias_paths << unsafe_alias_base.clone()
pointer_alias_paths << clone_pointer_binding_value_keys(pointer_alias_base)
pointer_alias_paths << snapshot_pointer_binding_value_keys(pointer_alias_base)
}
tc.fn_context.unsafe_reference_alias_owners = intersect_unsafe_reference_alias_states(unsafe_alias_paths, unsafe_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_value_states(pointer_alias_paths, pointer_alias_base)
tc.fn_context.pointer_binding_value_keys = merge_pointer_binding_snapshots(pointer_alias_paths, pointer_alias_base)
if missing_non_else {
tc.record_match_requires_non_else(node)
}
@@ -2487,12 +2487,12 @@ fn (tc &TypeChecker) match_branch_tail_diagnostic_type(subject_key string, subje
smartcast_type := tc.parse_type(flat.source_type_id(smartcast_name))
tail := tc.a.node(tail_id)
// `match key.handle { Variant { key.handle } }` names the subject like an ident.
if tail.kind in [.ident, .selector] && tc.expr_key(tail_id) == subject_key {
if tail.kind in [.ident, .selector] && tc.expr_key_matches(tail_id, subject_key) {
return *smartcast_type
}
if tail.kind == .prefix && tail.op == .amp && tail.children_count > 0 {
child_id := tc.a.child(tail, 0)
if tc.expr_key(child_id) == subject_key {
if tc.expr_key_matches(child_id, subject_key) {
if subject_type is Interface {
return *smartcast_type
}
@@ -4992,10 +4992,13 @@ fn (tc &TypeChecker) struct_init_has_related_bare_generic_return_error(id flat.N
}
fn (tc &TypeChecker) fn_types_match_ignoring_module_qualification(expected FnType, actual &Type) bool {
if actual !is FnType || expected.params.len != actual.params.len {
if actual !is FnType {
return false
}
actual_fn := actual as FnType
if expected.params.len != actual_fn.params.len {
return false
}
for i in 0 .. expected.params.len {
if !fn_param_modes_compatible(actual_fn, expected, i)
|| !tc.types_match_ignoring_module_qualification(fn_compatible_param_type(expected, i), fn_compatible_param_type(actual_fn, i)) {
@@ -10506,14 +10509,19 @@ fn qualified_type_modifiers(name string) string {
return name[..start]
}
@[inline]
fn (tc &TypeChecker) type_compatible(actual &Type, expected &Type) bool {
actual_raw := actual
expected_raw := expected
// Equal semantic values have equal spellings even when their immutable
// payloads were allocated separately. Compare them before interning names.
if semantic_types_equal(actual, expected) {
return true
}
return tc.type_compatible_unequal(actual, expected)
}
fn (tc &TypeChecker) type_compatible_unequal(actual &Type, expected &Type) bool {
actual_raw := actual
expected_raw := expected
// One memoized spelling per side: the former per-comparison name() calls
// composed fresh strings up to eight times per invocation (1.3M+ calls per
// self-host build), and equal compound types now compare via the shared
@@ -11016,56 +11024,40 @@ fn canonical_thread_handle_type_name(name string) string {
@[inline]
fn type_contains_unknown(typ &Type) bool {
// These dominate checked expression queries and cannot contain another type.
if typ is Primitive || typ is Struct {
return false
match typ {
Primitive, Struct, Void, String, Char, Rune, ISize, USize, Nil, None, Interface, Enum, SumType {
return false
}
else { return type_contains_unknown_nested(typ) }
}
return type_contains_unknown_nested(typ)
}
fn type_contains_unknown_nested(typ Type) bool {
if typ is Unknown {
return true
}
if typ is Alias {
return type_contains_unknown(typ.base_type)
}
if typ is Array {
return type_contains_unknown(typ.elem_type)
}
if typ is ArrayFixed {
return type_contains_unknown(typ.elem_type)
}
if typ is Map {
return type_contains_unknown(typ.key_type) || type_contains_unknown(typ.value_type)
}
if typ is Channel {
return type_contains_unknown(typ.elem_type)
}
if typ is Pointer {
return type_contains_unknown(typ.base_type)
}
if typ is OptionType {
return type_contains_unknown(typ.base_type)
}
if typ is ResultType {
return type_contains_unknown(typ.base_type)
}
if typ is FnType {
for i in 0 .. typ.params.len {
if type_contains_unknown(typ.params[i].typ) {
return true
@[direct_array_access]
fn type_contains_unknown_nested(typ &Type) bool {
match typ {
Unknown { return true }
Alias { return type_contains_unknown(typ.base_type) }
Array { return type_contains_unknown(typ.elem_type) }
ArrayFixed { return type_contains_unknown(typ.elem_type) }
Map { return type_contains_unknown(typ.key_type) || type_contains_unknown(typ.value_type) }
Channel { return type_contains_unknown(typ.elem_type) }
Pointer { return type_contains_unknown(typ.base_type) }
OptionType { return type_contains_unknown(typ.base_type) }
ResultType { return type_contains_unknown(typ.base_type) }
FnType {
for i in 0 .. typ.params.len {
if type_contains_unknown(typ.params[i].typ) { return true }
}
return type_contains_unknown(typ.return_type)
}
return type_contains_unknown(typ.return_type)
}
if typ is MultiReturn {
for i in 0 .. typ.types.len {
if type_contains_unknown(&typ.types[i]) {
return true
MultiReturn {
for i in 0 .. typ.types.len {
if type_contains_unknown(&typ.types[i]) { return true }
}
return false
}
else { return false }
}
return false
}
// fn_param_compatible compares parameter representations, preserving source-level integer distinctions.
@@ -14229,7 +14221,7 @@ pub fn (tc &TypeChecker) struct_fields_for_type(struct_name_id u32) []StructFiel
return tc.struct_fields_for_init(struct_name_id)
}
@[direct_array_access]
@[direct_array_access; inline]
fn (tc &TypeChecker) struct_field_type(struct_name_id u32, field_name string) ?Type {
struct_name := flat.source_type_name(struct_name_id)
if struct_name.starts_with('C.') {
@@ -14261,6 +14253,10 @@ fn (tc &TypeChecker) struct_field_type(struct_name_id u32, field_name string) ?T
return none
}
}
return tc.struct_field_type_uncached(struct_name_id, field_name)
}
fn (tc &TypeChecker) struct_field_type_uncached(struct_name_id u32, field_name string) ?Type {
if !isnil(tc.type_cache) {
mut fallback := tc.type_cache.base
for !isnil(fallback) {
@@ -16035,10 +16031,19 @@ fn (mut tc TypeChecker) type_mismatch(kind TypeErrorKind, msg string, node flat.
}
// expr_key supports expr key handling for TypeChecker.
@[inline]
fn (tc &TypeChecker) expr_key(id flat.NodeId) string {
if int(id) < 0 {
return ''
}
node := tc.a.node(id)
if node.kind == .ident {
return if valid_string_data(node.value) { node.value } else { '' }
}
return tc.expr_key_composite(id)
}
fn (tc &TypeChecker) expr_key_composite(id flat.NodeId) string {
node := tc.a.nodes[int(id)]
if node.kind in [.as_expr, .paren] && node.children_count > 0 {
return tc.expr_key(tc.a.child(&node, 0))
@@ -16099,6 +16104,15 @@ fn (tc &TypeChecker) expr_key_matches_prefix(id flat.NodeId, key string, end int
return tc.expr_key_matches_prefix(tc.a.child(node, 0), key, base_end)
}
if node.kind == .index && node.children_count >= 2 {
part := tc.a.child_node(node, 1)
if part.kind in [.ident, .int_literal, .string_literal, .char_literal, .enum_val] {
base_end := end - part.value.len - 2
if part.value.len == 0 || base_end <= 0 || key[base_end] != `[` || key[end - 1] != `]`
|| !string_bytes_equal_at(key, base_end + 1, part.value) {
return false
}
return tc.expr_key_matches_prefix(tc.a.child(node, 0), key, base_end)
}
full := tc.expr_key(id)
return full.len == end && string_bytes_equal_at(key, 0, full)
}
@@ -16138,7 +16152,7 @@ fn (tc &TypeChecker) expr_key_part(id flat.NodeId) string {
}
// smartcast_type supports smartcast type handling for TypeChecker.
@[direct_array_access]
@[direct_array_access; inline]
fn (tc &TypeChecker) smartcast_type(id flat.NodeId) ?Type {
idx := int(id)
if idx < 0 || idx >= tc.a.nodes.len || tc.a.nodes[idx].kind !in [.ident, .selector, .index] {
@@ -16166,6 +16180,17 @@ fn (tc &TypeChecker) smartcast_type(id flat.NodeId) ?Type {
}
return *cache.lexical_smartcast_types[int(state) - 2]
}
return tc.smartcast_type_miss(id, parsed, state)
}
@[direct_array_access]
fn (tc &TypeChecker) smartcast_type_miss(id flat.NodeId, parsed bool, state u32) ?Type {
idx := int(id)
mut cache := tc.type_cache
if tc.smartcasts.len == 0 && parsed && tc.lexical_smartcasts_unused(idx) {
tc.remember_lexical_smartcast_miss(idx)
return none
}
key := tc.expr_key(id)
if key.len == 0 {
return none
@@ -16193,14 +16218,7 @@ fn (tc &TypeChecker) smartcast_type(id flat.NodeId) ?Type {
found = tc.lexical_smartcast_type(id, key)
}
result := found or {
if isnil(tc.storage_query_probe) && !tc.resolution_type_mode && idx < tc.lexical_smartcast_misses.len
&& (!tc.parallel_check_sparse || (idx >= tc.check_range_lo && idx <= tc.check_range_hi)) {
mut writable := unsafe { tc }
writable.lexical_smartcast_misses[idx] = true
}
if !isnil(cache) {
cache.set_lexical_smartcast_state(idx, 1)
}
tc.remember_lexical_smartcast_miss(idx)
return none
}
if !isnil(cache) {
@@ -16210,6 +16228,18 @@ fn (tc &TypeChecker) smartcast_type(id flat.NodeId) ?Type {
return result
}
fn (tc &TypeChecker) remember_lexical_smartcast_miss(idx int) {
if isnil(tc.storage_query_probe) && !tc.resolution_type_mode && idx < tc.lexical_smartcast_misses.len
&& (!tc.parallel_check_sparse || (idx >= tc.check_range_lo && idx <= tc.check_range_hi)) {
mut writable := unsafe { tc }
writable.lexical_smartcast_misses[idx] = true
}
if !isnil(tc.type_cache) {
mut cache := tc.type_cache
cache.set_lexical_smartcast_state(idx, 1)
}
}
fn (tc &TypeChecker) lexical_smartcast_type(id flat.NodeId, key string) ?Type {
return tc.lexical_smartcast_type_in_parents(id, key, false)
}
@@ -16682,6 +16712,7 @@ pub fn type_text_contains_typeof(s string) bool {
}
// parse_type resolves an interned source spelling in the current checker context.
@[inline]
pub fn (tc &TypeChecker) parse_type(source_id u32) &Type {
if tc.type_param_texts.ids.len > 0 {
return tc.intern_type_reference(tc.parse_type_as_instance(source_id))
@@ -16696,22 +16727,29 @@ pub fn (tc &TypeChecker) parse_type(source_id u32) &Type {
mut cache := unsafe { tc.type_cache }
if scalar := tc.parsed_scalar_type(source_id) {
cache.parse_hits++
return tc.intern_type_reference(scalar)
return scalar
}
if cached := parse_type_cache_get_mode(mut cache, tc.cur_file, tc.cur_module, source_id, tc.fn_context.generic_params, tc.resolution_type_mode, tc.fast_parse_recent) {
cache.parse_hits++
return cached
}
}
return tc.parse_type_miss(source_id)
}
fn (tc &TypeChecker) parse_type_miss(source_id u32) &Type {
if tc.type_cache != unsafe { nil } && tc.type_cache.parse_enabled {
mut cache := unsafe { tc.type_cache }
// Only a miss needs source spelling bytes. typeof depends on expression scope.
typ := flat.source_type_name(source_id)
if type_text_contains_typeof(typ) {
if flat.source_type_shape(source_id).has(.typeof_expr) {
return tc.intern_type_reference(tc.parse_type_uncached(typ))
}
cache.parse_misses++
result := tc.intern_type_reference(tc.parse_type_uncached(typ))
if scalar_builtin_type_name(typ) {
id, _ := tc.intern_type(*result)
tc.remember_parsed_scalar_type(source_id, id, *result)
tc.remember_parsed_scalar_type(source_id, id, result)
}
if tc.fast_parse_recent {
parse_type_cache_put_recent(mut cache, tc.cur_file, tc.cur_module, source_id, tc.fn_context.generic_params, tc.resolution_type_mode, result)
@@ -16721,7 +16759,7 @@ pub fn (tc &TypeChecker) parse_type(source_id u32) &Type {
return result
}
typ := flat.source_type_name(source_id)
if type_text_contains_typeof(typ) {
if flat.source_type_shape(source_id).has(.typeof_expr) {
return tc.intern_type_reference(tc.parse_type_uncached(typ))
}
result := tc.intern_type_reference(tc.parse_type_uncached(typ))
@@ -16731,6 +16769,7 @@ pub fn (tc &TypeChecker) parse_type(source_id u32) &Type {
// parse_type_ref resolves a node annotation through its exact canonical text
// identity. Context remains part of the key because an unqualified spelling
// can denote different types in different files/modules.
@[inline]
pub fn (tc &TypeChecker) parse_type_ref(text_id u32) &Type {
if text_id == 0 || !tc.fast_type_text_refs || tc.type_cache == unsafe { nil }
|| !tc.type_cache.parse_enabled {
@@ -16743,6 +16782,19 @@ pub fn (tc &TypeChecker) parse_type_ref(text_id u32) &Type {
return tc.intern_type_reference(recursive_alias)
}
}
mut cache := unsafe { tc.type_cache }
if cache.parse_text_ids.len > 0 {
slot := int(text_id) & 4095
context_hash := parse_type_cache_context_hash(mut cache, tc.cur_file, tc.cur_module, tc.fn_context.generic_params, tc.resolution_type_mode)
if cache.parse_text_ids[slot] == text_id && cache.parse_text_id_context[slot] == context_hash {
cache.parse_hits++
return cache.parse_text_id_values[slot]
}
}
return tc.parse_type_ref_miss(text_id)
}
fn (tc &TypeChecker) parse_type_ref_miss(text_id u32) &Type {
mut cache := unsafe { tc.type_cache }
if cache.parse_text_ids.len == 0 {
cache.parse_text_id_context = []u64{len: 4096}
@@ -16751,10 +16803,6 @@ pub fn (tc &TypeChecker) parse_type_ref(text_id u32) &Type {
}
slot := int(text_id) & 4095
context_hash := parse_type_cache_context_hash(mut cache, tc.cur_file, tc.cur_module, tc.fn_context.generic_params, tc.resolution_type_mode)
if cache.parse_text_ids[slot] == text_id && cache.parse_text_id_context[slot] == context_hash {
cache.parse_hits++
return cache.parse_text_id_values[slot]
}
result := tc.parse_type(text_id)
cache.parse_text_id_context[slot] = context_hash
cache.parse_text_id_values[slot] = result
@@ -16994,6 +17042,7 @@ pub fn (tc &TypeChecker) parse_canonical_type(source_id u32) Type {
return *tc.parse_type(flat.source_type_id(clean))
}
@[inline]
fn (tc &TypeChecker) probe_intern_type(t &Type) ?&Type {
if isnil(tc.type_interner) {
return none
@@ -17001,11 +17050,14 @@ fn (tc &TypeChecker) probe_intern_type(t &Type) ?&Type {
return tc.type_interner.probe(t)
}
@[inline]
fn (tc &TypeChecker) intern_type(t &Type) (TypeId, &Type) {
mut interner := unsafe { tc.type_interner }
return interner.canonicalize(t)
}
// intern_type_reference returns the owned canonical reference for a semantic type.
@[inline]
pub fn (tc &TypeChecker) intern_type_reference(t &Type) &Type {
mut interner := unsafe { tc.type_interner }
_, value := interner.canonicalize(t)
@@ -17024,31 +17076,18 @@ pub fn (tc &TypeChecker) type_count() int {
// type_name returns the interned canonical spelling of a semantic type.
// Composite spellings are constructed once and memoized by semantic identity.
@[inline]
pub fn (tc &TypeChecker) type_name(t &Type) u32 {
has_literal_name := match t {
Primitive, Void, Unknown, Nil, None, String, Char, Rune, ISize, USize { true }
else { false }
}
if has_literal_name {
return t.source_type_id()
}
// Stored-name variants are free; interning them would only add hashing on
// top of returning the field.
if t is Struct {
return t.source_type_id()
}
if t is Alias {
return t.source_type_id()
}
if t is Enum {
return t.source_type_id()
}
if t is SumType {
return t.source_type_id()
}
if t is Interface {
return t.source_type_id()
match t {
Primitive, Void, Unknown, Nil, None, String, Char, Rune, ISize, USize,
Struct, Alias, Enum, SumType, Interface {
return t.source_type_id()
}
else { return tc.composite_type_name(t) }
}
}
fn (tc &TypeChecker) composite_type_name(t &Type) u32 {
if isnil(tc.type_interner) {
return t.source_type_id()
}
@@ -17093,13 +17132,22 @@ pub fn (tc &TypeChecker) type_cache_stats() TypeCacheStats {
}
}
@[inline]
fn parse_type_cache_context_hash(mut cache TypeCache, file string, module_name string, generic_params []string, resolution bool) u64 {
if cache.parse_context_valid && cache.parse_context_resolution == resolution
&& parse_type_cache_string_matches(cache.parse_context_file, file)
&& parse_type_cache_string_matches(cache.parse_context_module, module_name)
&& parse_type_cache_strings_match(cache.parse_context_generics, generic_params) {
&& (if generic_params.len == 0 {
cache.parse_context_generics.len == 0
} else {
parse_type_cache_strings_match(cache.parse_context_generics, generic_params)
}) {
return cache.parse_context_hash
}
return parse_type_cache_context_hash_miss(mut cache, file, module_name, generic_params, resolution)
}
fn parse_type_cache_context_hash_miss(mut cache TypeCache, file string, module_name string, generic_params []string, resolution bool) u64 {
mut hash := u64(14_695_981_039_346_656_037)
if resolution {
hash = (hash ^ u64(1)) * u64(1_099_511_628_211)
@@ -17164,7 +17212,8 @@ fn parse_type_cache_strings_match(a []string, b []string) bool {
return true
}
fn parse_type_cache_entry_matches(entry ParseTypeCacheEntry, file string, module_name string, text u32, generic_params []string, resolution bool) bool {
@[inline]
fn parse_type_cache_entry_matches(entry &ParseTypeCacheEntry, file string, module_name string, text u32, generic_params []string, resolution bool) bool {
return entry.resolution == resolution && entry.text == text
&& parse_type_cache_string_matches(entry.file, file)
&& parse_type_cache_string_matches(entry.module, module_name)
@@ -18801,14 +18850,9 @@ fn (mut memo BodyResolveMemo) begin(lo int, hi int) {
}
// resolve_type returns the type of an expression in the current checker context.
@[direct_array_access]
@[direct_array_access; inline]
pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
if tc.trust_checked_expr_types {
if !isnil(tc.storage_query_probe) {
if typ := tc.cached_expr_type_ref(id) {
if !type_contains_unknown(typ) { return tc.widen_mixed_integer_expr_type(id, typ) }
}
}
if tc.trust_checked_expr_types && isnil(tc.storage_query_probe) {
// Post-check phases re-resolve mostly unchanged subtrees the checker
// already typed. Serve those straight from the dense per-node cache:
// dense in-range entries are checker-authored, transform's node-write
@@ -18816,7 +18860,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
// beyond the dense range so they resolve normally below. Cached
// unknowns stay excluded — a later registration may resolve them.
tidx := int(id)
if isnil(tc.storage_query_probe) && tidx >= 0 && tidx < tc.expr_type_set.len && tc.expr_type_set[tidx]
if tidx >= 0 && tidx < tc.expr_type_set.len && tc.expr_type_set[tidx]
&& (!tc.parallel_check_sparse || tc.in_check_range(tidx)) {
typ := tc.expr_type_values[tidx]
if !type_contains_unknown(typ) {
@@ -18824,6 +18868,19 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
}
}
}
return tc.resolve_type_slow(id)
}
@[direct_array_access]
fn (tc &TypeChecker) resolve_type_slow(id flat.NodeId) Type {
if tc.trust_checked_expr_types && !isnil(tc.storage_query_probe) {
cached_type_ref0 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref0) {
if !type_contains_unknown(cached_type_ref0) {
return tc.widen_mixed_integer_expr_type(id, cached_type_ref0)
}
}
}
if !isnil(tc.storage_query_probe) {
if typ := tc.storage_query_probe_checked_type(id) {
return tc.widen_mixed_integer_expr_type(id, typ)
@@ -18881,6 +18938,7 @@ fn wide_integer_type_for_widening(typ Type) bool {
// narrower operand's, while the same expression assigned to a variable gets the
// 128-bit type from the promotion ladder. Printing, `typeof` and interpolation all
// read the recorded type, so they cut a mixed expression to 64 bits without this.
@[direct_array_access; inline]
fn (tc &TypeChecker) widen_mixed_integer_expr_type(id flat.NodeId, typ &Type) Type {
narrow := match typ {
Primitive { typ.props.has(.integer) && typ.size < 128 }
@@ -18894,6 +18952,10 @@ fn (tc &TypeChecker) widen_mixed_integer_expr_type(id flat.NodeId, typ &Type) Ty
if tidx < 0 || tidx >= tc.a.nodes.len || tc.a.nodes[tidx].kind != .infix {
return *typ
}
return tc.widen_mixed_integer_infix_type(id, typ)
}
fn (tc &TypeChecker) widen_mixed_integer_infix_type(id flat.NodeId, typ &Type) Type {
node := tc.a.node(id)
if !narrow_integer_type_for_widening(*typ) {
return *typ
@@ -18915,150 +18977,195 @@ fn (tc &TypeChecker) widen_mixed_integer_expr_type(id flat.NodeId, typ &Type) Ty
return *typ
}
@[direct_array_access]
@[direct_array_access; inline]
fn (tc &TypeChecker) resolve_type_uncached(id flat.NodeId) Type {
if int(id) < 0 || int(id) >= tc.a.nodes.len {
return unknown_type('missing node')
}
node := tc.a.node(id)
if node.kind == .int_literal && tc.node_is_in_translated_file(id) {
if typ := tc.translated_integer_literal_type(id) {
return typ
match node.kind {
.int_literal {
if tc.translated_files.len == 0 { return builtin_int_type }
}
}
if node.kind == .ident && tc.errors.len > 0 {
if typ := tc.ident_error_type(id) {
return typ
.float_literal { return builtin_f64_type }
.bool_literal { return builtin_bool_type }
.char_literal {
if !node.value.starts_with('c:') { return builtin_rune_type }
}
}
if node.kind == .selector && tc.errors.any(it.node == id
&& it.msg.contains(' does not return a value')) {
return builtin_void_type
}
if node.kind == .call && tc.errors.any(it.node == id && it.msg.starts_with('unknown enum `')) {
return builtin_void_type
}
if node.kind == .infix
&& tc.errors.any(it.node == id && it.msg.starts_with('expected type is not an enum (')) {
return builtin_void_type
}
if node.kind == .directive && node.value == '\$res' {
return_type := tc.fn_context.return_type
if return_type is ResultType || return_type is Void {
return builtin_void_type
}
if return_type is MultiReturn {
if node.children_count == 0 {
return builtin_void_type
.string_literal {
if node.children_count != 1
|| node.value !in ['__v3_comptime_zero', '__v3_comptime_new'] {
return builtin_string_type
}
index_node := tc.a.child_node(node, 0)
if index_node.kind != .int_literal {
return builtin_void_type
}
index := index_node.value.int()
if index < 0 || index >= return_type.types.len {
return builtin_void_type
}
return return_type.types[index]
}
return return_type
.string_interp { return builtin_string_type }
.sizeof_expr, .offsetof_expr { return builtin_u32_type }
.nil_literal { return builtin_voidptr_type }
.none_expr { return Type(OptionType{}) }
else {}
}
if node.kind == .string_literal && node.children_count == 1
&& node.value in ['__v3_comptime_zero', '__v3_comptime_new'] {
if type_name_source_id := tc.comptime_static_type_expr_name(tc.a.child(node, 0)) {
target := tc.parse_type(type_name_source_id)
if target !is Unknown {
return if node.value == '__v3_comptime_new' {
Type(Pointer{
base_type: tc.intern_type_reference(target)
})
} else {
*target
return tc.resolve_type_nonliteral(id)
}
@[direct_array_access]
fn (tc &TypeChecker) resolve_type_nonliteral(id flat.NodeId) Type {
node := tc.a.node(id)
match node.kind {
.int_literal {
if tc.node_is_in_translated_file(id) {
if typ := tc.translated_integer_literal_type(id) {
return typ
}
}
return builtin_int_type
}
.ident {
if tc.errors.len > 0 {
if typ := tc.ident_error_type(id) {
return typ
}
}
}
}
if node.kind == .paren && node.children_count > 0 {
return tc.resolve_type(tc.a.child(node, 0))
}
if node.kind == .expr_stmt {
if node.children_count == 0 {
return builtin_void_type
.selector {
if tc.errors.len > 0 && tc.errors.any(it.node == id
&& it.msg.contains(' does not return a value')) {
return builtin_void_type
}
}
if node.children_count == 1 {
return tc.resolve_type(tc.a.child(node, 0))
.infix {
if tc.errors.len > 0
&& tc.errors.any(it.node == id && it.msg.starts_with('expected type is not an enum (')) {
return builtin_void_type
}
}
mut expr_types := []Type{cap: node.children_count}
for i in 0 .. node.children_count {
value_id := tc.a.child(node, i)
typ := tc.expr_type(value_id) or { tc.resolve_type(value_id) }
expr_types << typ
.call {
if tc.errors.len > 0 && tc.errors.any(it.node == id && it.msg.starts_with('unknown enum `')) {
return builtin_void_type
}
if node.typ != 0 && node.typ_text() !in ['int', 'array', 'map', 'unknown'] {
return *tc.parse_type(node.typ)
}
if t := tc.resolved_call_type(id) {
return t
}
}
return Type(MultiReturn{
types: expr_types
})
}
kind_id := int(node.kind)
if kind_id == 1 {
return builtin_int_type
}
if kind_id == 2 {
return builtin_f64_type
}
if kind_id == 3 {
return builtin_bool_type
}
if kind_id == 4 {
if node.value.starts_with('c:') {
return Type(Pointer{
base_type: tc.intern_type_reference(Type(u8_))
.directive {
if node.value == '\$res' {
return_type := tc.fn_context.return_type
if return_type is ResultType || return_type is Void {
return builtin_void_type
}
if return_type is MultiReturn {
if node.children_count == 0 {
return builtin_void_type
}
index_node := tc.a.child_node(node, 0)
if index_node.kind != .int_literal {
return builtin_void_type
}
index := index_node.value.int()
if index < 0 || index >= return_type.types.len {
return builtin_void_type
}
return return_type.types[index]
}
return return_type
}
}
.string_literal {
if node.children_count == 1
&& node.value in ['__v3_comptime_zero', '__v3_comptime_new'] {
if type_name_source_id := tc.comptime_static_type_expr_name(tc.a.child(node, 0)) {
target := tc.parse_type(type_name_source_id)
if target !is Unknown {
return if node.value == '__v3_comptime_new' {
Type(Pointer{
base_type: tc.intern_type_reference(target)
})
} else {
*target
}
}
}
}
return builtin_string_type
}
.paren {
if node.children_count > 0 {
return tc.resolve_type(tc.a.child(node, 0))
}
}
.expr_stmt {
if node.children_count == 0 {
return builtin_void_type
}
if node.children_count == 1 {
return tc.resolve_type(tc.a.child(node, 0))
}
mut expr_types := []Type{cap: node.children_count}
for i in 0 .. node.children_count {
value_id := tc.a.child(node, i)
typ := tc.expr_type(value_id) or { tc.resolve_type(value_id) }
expr_types << typ
}
return Type(MultiReturn{
types: expr_types
})
}
return builtin_rune_type
}
if kind_id == 5 || kind_id == 6 {
return builtin_string_type
}
if node.kind in [.sizeof_expr, .offsetof_expr] {
return builtin_u32_type
}
if kind_id == 28 {
return builtin_voidptr_type
}
if kind_id == 29 {
return Type(OptionType{})
}
if kind_id == 21 {
return tc.fn_literal_type(*node)
}
if kind_id == 32 {
return tc.lambda_expr_type(*node)
}
if kind_id == 12 && node.typ != 0 && node.typ_text() !in ['int', 'array', 'map', 'unknown'] {
return *tc.parse_type(node.typ)
}
if t := tc.resolved_call_type(id) {
return t
}
if kind_id == 22 {
if aggregate_type := tc.sql_aggregate_or_expr_type(*node) {
return aggregate_type
.float_literal {
return builtin_f64_type
}
if value_type := tc.match_trailing_or_value_type(tc.a.child(node, 0)) {
return value_type
.bool_literal {
return builtin_bool_type
}
if payload := tc.or_expr_payload_type(tc.a.child(node, 0)) {
return payload
.string_interp {
return builtin_string_type
}
inner := tc.resolve_type(tc.a.child(node, 0))
if inner is OptionType {
return *inner.base_type
.sizeof_expr, .offsetof_expr {
return builtin_u32_type
}
if inner is ResultType {
return *inner.base_type
.nil_literal {
return builtin_voidptr_type
}
return inner
.none_expr {
return Type(OptionType{})
}
.fn_literal {
return tc.fn_literal_type(*node)
}
.lambda_expr {
return tc.lambda_expr_type(*node)
}
.char_literal {
if node.value.starts_with('c:') {
return Type(Pointer{
base_type: tc.intern_type_reference(Type(u8_))
})
}
return builtin_rune_type
}
.or_expr {
if aggregate_type := tc.sql_aggregate_or_expr_type(*node) {
return aggregate_type
}
if value_type := tc.match_trailing_or_value_type(tc.a.child(node, 0)) {
return value_type
}
if payload := tc.or_expr_payload_type(tc.a.child(node, 0)) {
return payload
}
inner := tc.resolve_type(tc.a.child(node, 0))
if inner is OptionType {
return *inner.base_type
}
if inner is ResultType {
return *inner.base_type
}
return inner
}
else {}
}
kind_id := int(node.kind)
if smart_type := tc.smartcast_type(id) {
return smart_type
}
@@ -19067,9 +19174,10 @@ fn (tc &TypeChecker) resolve_type_uncached(id flat.NodeId) Type {
// type its check gave it (see check_generic_method_value), which the
// resolution of an index cannot tell from an index of the method value.
if node.children_count > 0 && tc.a.child_node(node, 0).kind == .selector {
if typ := tc.cached_expr_type_ref(id) {
typ_raw := *typ
if typ is FnType {
cached_type_ref1 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref1) {
typ_raw := *cached_type_ref1
if cached_type_ref1 is FnType {
return typ_raw
}
}
@@ -19080,12 +19188,13 @@ fn (tc &TypeChecker) resolve_type_uncached(id flat.NodeId) Type {
return builtin_bool_type
}
if !(tc.smartcasts.len > 0 && (kind_id == 7 || kind_id == 13)) {
if typ := tc.cached_expr_type_ref(id) {
cached_type_ref2 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref2) {
// A resolution-only annotation can cache `void` for a method whose
// receiver is a local alias that is only known during body checking.
// Re-resolve calls in that case instead of hiding the real return type.
if node.kind != .call || typ !is Void {
return *typ
if node.kind != .call || cached_type_ref2 !is Void {
return *cached_type_ref2
}
}
}
+3 -2
View File
@@ -1013,8 +1013,9 @@ fn (tc &TypeChecker) incremental_touches_generics(f IncrementalFunction, generic
return true
}
}
if typ := tc.cached_expr_type_ref(id) {
if type_mentions_generic_application(typ) {
cached_type_ref0 := tc.cached_expr_type_ref(id)
if !isnil(cached_type_ref0) {
if type_mentions_generic_application(cached_type_ref0) {
return true
}
}
+5 -3
View File
@@ -147,7 +147,7 @@ fn parsed_scalar_slot(source_id u32) int {
}
@[direct_array_access; inline]
fn (tc &TypeChecker) parsed_scalar_type(text u32) ?Type {
fn (tc &TypeChecker) parsed_scalar_type(text u32) ?&Type {
cache := tc.type_cache.parsed_scalars
if isnil(cache) || cache.owner != tc.type_interner || text == 0 {
return none
@@ -161,7 +161,9 @@ fn (tc &TypeChecker) parsed_scalar_type(text u32) ?Type {
// Only exact builtin spellings enter this cache. Their meanings do not depend
// on imports, the current module, resolution mode or function-local scopes.
fn (tc &TypeChecker) remember_parsed_scalar_type(text u32, id TypeId, value Type) {
// Values are canonical pointers; reset and promotion clear the cache before
// replacing their interner storage.
fn (tc &TypeChecker) remember_parsed_scalar_type(text u32, id TypeId, value &Type) {
if isnil(tc.type_interner) {
return
}
@@ -170,7 +172,7 @@ fn (tc &TypeChecker) remember_parsed_scalar_type(text u32, id TypeId, value Type
type_cache.parsed_scalars = &ParsedScalarTypeCache{
owner: tc.type_interner
// Unfilled slots are guarded by their nonempty text key.
values: unsafe { []Type{len: 128} }
values: unsafe { []&Type{len: 128} }
}
}
mut cache := type_cache.parsed_scalars
+4
View File
@@ -118,6 +118,8 @@ fn test_parsed_ids_follow_generic_recursive_alias_and_worker_precedence() {
mut tc := TypeChecker.new(&a)
tc.set_fresh_type_cache(true)
integer := tc.parse_type_id(flat.source_type_u64) or { panic('missing integer identity') }
integer_reference := tc.parse_type(flat.source_type_u64)
assert voidptr(tc.parse_type(flat.source_type_u64)) == voidptr(integer_reference)
tc.cur_module = 'other'
tc.cur_file = 'other.v'
assert tc.parse_type_id(flat.source_type_u64) or { panic('missing contextual identity') } == integer
@@ -144,4 +146,6 @@ fn test_parsed_ids_follow_generic_recursive_alias_and_worker_precedence() {
replacement := tc.parse_type_id(flat.source_type_bool) or { panic('missing replacement identity') }
assert replacement == integer
assert tc.type_name_by_id(replacement) == flat.source_type_bool
assert tc.parse_type(flat.source_type_u64) is Primitive
assert voidptr(tc.parse_type(flat.source_type_u64)) != voidptr(integer_reference)
}
@@ -12,6 +12,7 @@ fn test_repeated_scalar_interning_after_scope_promotion() {
assert unsafe { prealloc_scope_owns(scope, scoped) }
unsafe { prealloc_scope_leave(scope) }
interner.promote_from(0, scope)
assert isnil(interner.canonical_identity(scoped))
promoted_id, promoted := interner.canonicalize(input)
assert promoted_id == id
assert voidptr(promoted) == voidptr(interner.types[int(id)])
+19
View File
@@ -3,6 +3,19 @@ module types
import v.flat
fn promotion_test_indirect_payload(typ &Type) voidptr {
return match typ {
Unknown { voidptr(typ.UnknownDataRef) }
ArrayFixed { voidptr(typ.ArrayFixedDataRef) }
Channel { voidptr(typ.ChannelDataRef) }
Map { voidptr(typ.MapDataRef) }
FnType { voidptr(typ.FnTypeDataRef) }
Alias { voidptr(typ.AliasDataRef) }
MultiReturn { voidptr(typ.MultiReturnDataRef) }
else { unsafe { nil } }
}
}
fn test_pass2_promotion_retains_scoped_variant_payloads() {
$if prealloc {
name_id := flat.source_type_id('ScopedType')
@@ -38,8 +51,14 @@ fn test_pass2_promotion_retains_scoped_variant_payloads() {
mut promotion := Pass2TypePromotion{}
for value in values {
expected << value.name()
payload := promotion_test_indirect_payload(value)
if payload != unsafe { nil } {
assert unsafe { prealloc_scope_owns(scope, payload) }
assert type_refers_to_scope(scope, value)
}
owned := promotion.promote(scope, value)
assert !type_refers_to_scope(scope, owned)
assert !unsafe { prealloc_scope_owns(scope, promotion_test_indirect_payload(owned)) }
// Check storage directly too: the old scope query missed boxed payloads.
if owned is Primitive {
assert !unsafe { prealloc_scope_owns(scope, &owned.props) }
+39
View File
@@ -218,3 +218,42 @@ fn test_infix_primitive_keeps_generic_alias_redirect_metadata() {
}
}
}
fn unknown_query_wrappers(typ &Type) []Type {
return [
Type(Alias{ base_type: typ }),
Type(Array{ elem_type: typ }),
Type(ArrayFixed{ elem_type: typ, len: 2 }),
Type(Map{ key_type: typ, value_type: builtin_int_type }),
Type(Map{ key_type: builtin_int_type, value_type: typ }),
Type(Channel{ elem_type: typ }),
Type(Pointer{ base_type: typ }),
Type(OptionType{ base_type: typ }),
Type(ResultType{ base_type: typ }),
Type(FnType{ params: [FnParam{ typ: typ }], return_type: builtin_int_type }),
Type(FnType{ return_type: typ }),
Type(MultiReturn{ types: [Type(int_), *typ] }),
]
}
fn test_unknown_query_recurses_through_all_composite_types() {
known := Type(int_)
unknown := Type(Unknown{ reason: 'provisional' })
assert !type_contains_unknown(known)
assert type_contains_unknown(unknown)
for wrapped in unknown_query_wrappers(known) {
assert !type_contains_unknown(wrapped)
}
for wrapped in unknown_query_wrappers(unknown) {
assert type_contains_unknown(wrapped)
for nested in unknown_query_wrappers(wrapped) {
assert type_contains_unknown(nested)
}
}
assert !type_contains_unknown(Type(FnType{}))
assert !type_contains_unknown(Type(MultiReturn{}))
for leaf in [Type(void_), Type(string_), Type(char_), Type(rune_), Type(isize_), Type(usize_),
Type(Nil{}), Type(None{}), Type(Struct{}), Type(Interface{}), Type(Enum{}), Type(SumType{})] {
assert !type_contains_unknown(leaf)
}
}
+20 -18
View File
@@ -39,8 +39,11 @@ pub struct ScopeBindingOwner {
@[inline]
fn scope_name_bit(name string) u64 {
first := if name.len > 0 { u32(name[0]) } else { 0 }
return u64(1) << ((first * 33 + u32(name.len)) & 63)
if name.len == 0 {
return u64(1)
}
// Distinguish equal-length identifiers sharing a prefix before probing a scope.
return u64(1) << ((u32(name[0]) * 33 + u32(name[name.len - 1]) * 7 + u32(name.len)) & 63)
}
@[inline]
@@ -59,7 +62,7 @@ fn scope_fast_slot(name string) int {
const scope_linear_names = 8
@[direct_array_access; inline]
fn (s &Scope) own_binding_index(name string) ?int {
fn (s &Scope) own_binding_index(name string) int {
if s.fast_lookup {
slot := scope_fast_slot(name)
if s.fast_generations[slot] == s.fast_generation
@@ -77,20 +80,20 @@ fn (s &Scope) own_binding_index(name string) ?int {
if i := s.name_indexes[name] {
return i
}
return none
return -1
}
// scan_own_binding_index finds `name` among the names of a scope that has not
// indexed them (see scope_linear_names).
@[direct_array_access]
fn (s &Scope) scan_own_binding_index(name string) ?int {
fn (s &Scope) scan_own_binding_index(name string) int {
for i := s.names.len - 1; i >= 0; i-- {
bound := s.names[i]
if bound.len == name.len && (bound.str == name.str || bound == name) {
return i
}
}
return none
return -1
}
@[direct_array_access; inline]
@@ -164,7 +167,7 @@ pub fn (s &Scope) contains(name string) bool {
mut scope := unsafe { &Scope(s) }
for scope != unsafe { nil } {
if scope.name_mask & bit != 0 {
if _ := scope.own_binding_index(name) {
if scope.own_binding_index(name) >= 0 {
return true
}
}
@@ -191,7 +194,8 @@ pub fn (s &Scope) lookup(name string) ?Type {
mut scope := unsafe { &Scope(s) }
for scope != unsafe { nil } {
if scope.name_mask & bit != 0 {
if i := scope.own_binding_index(name) {
i := scope.own_binding_index(name)
if i >= 0 {
return scope.types[i]
}
}
@@ -222,7 +226,8 @@ pub fn (s &Scope) lookup_before(name string, stop &Scope) ?Type {
bit := scope_name_bit(name)
for scope != unsafe { nil } && voidptr(scope) != voidptr(stop) {
if scope.name_mask & bit != 0 {
if i := scope.own_binding_index(name) {
i := scope.own_binding_index(name)
if i >= 0 {
return scope.types[i]
}
}
@@ -248,10 +253,7 @@ pub fn (s &Scope) binds_own(name string) bool {
if s.name_mask & scope_name_bit(name) == 0 {
return false
}
if _ := s.own_binding_index(name) {
return true
}
return false
return s.own_binding_index(name) >= 0
}
return name in s.names
}
@@ -270,7 +272,8 @@ pub fn (s &Scope) lookup_owner(name string) ?ScopeBindingOwner {
scope = scope.parent
continue
}
if i := scope.own_binding_index(name) {
i := scope.own_binding_index(name)
if i >= 0 {
return ScopeBindingOwner{
scope: scope
index: i
@@ -413,7 +416,8 @@ pub fn (s &Scope) nearest_binding_owned_by(name string, owner ScopeBindingOwner)
mut scope := unsafe { &Scope(s) }
for scope != unsafe { nil } {
if scope.name_mask & bit != 0 {
if i := scope.own_binding_index(name) {
i := scope.own_binding_index(name)
if i >= 0 {
return voidptr(scope) == voidptr(owner.scope)
&& scope.lifetime == owner.lifetime && i == owner.index
}
@@ -452,9 +456,7 @@ pub fn (mut s Scope) insert_with_owner(name string, typ Type) ScopeBindingOwner
$if !ownership ? {
mut existing := -1
if s.names.len <= scope_linear_names {
if i := s.scan_own_binding_index(name) {
existing = i
}
existing = s.scan_own_binding_index(name)
} else if i := s.name_indexes[name] {
existing = i
}
+214 -5
View File
@@ -248,12 +248,12 @@ fn test_import_alias_negative_filter_keeps_collisions_rebindings_and_file_contex
tc.cur_file = 'first.v'
tc.register_file_import('os', 'os')
assert (tc.resolve_import_alias('os') or { '' }) == 'os'
// Equal first bytes and lengths collide in the conservative name mask.
assert scope_name_bit('os') == scope_name_bit('ox')
assert tc.resolve_import_alias('ox') == none
// Last bytes differing by 64 still collide in the conservative name mask.
assert scope_name_bit('os') == scope_name_bit('o3')
assert tc.resolve_import_alias('o3') == none
assert tc.resolve_import_alias('absent') == none
tc.register_file_import('ox', 'other.module')
assert (tc.resolve_import_alias('ox') or { '' }) == 'other.module'
tc.register_file_import('o3', 'other.module')
assert (tc.resolve_import_alias('o3') or { '' }) == 'other.module'
tc.register_file_import('os', 'replacement')
assert (tc.resolve_import_alias('os') or { '' }) == 'replacement'
tc.cur_file = 'second.v'
@@ -280,3 +280,212 @@ fn test_import_alias_filter_accepts_unindexed_hand_built_views() {
tc.register_file_import('new_alias', 'new.module')
assert (tc.resolve_import_alias('new_alias') or { '' }) == 'new.module'
}
fn test_branch_snapshots_keep_map_and_array_storage_independent() {
mut source := map[string][]string{}
for i in 0 .. 200 {
source['key${i}'] = ['value${i}']
}
for i in 0 .. 180 {
source.delete('key${i}')
}
mut snapshot := clone_pointer_binding_value_keys(source)
assert snapshot == source
assert snapshot.keys() == source.keys()
snapshot['key180'][0] = 'changed'
snapshot['key181'] << 'appended'
snapshot.delete('key182')
snapshot['new'] = ['new']
assert source['key180'] == ['value180']
assert source['key181'] == ['value181']
assert 'key182' in source
assert 'new' !in source
source.clear()
mut empty_snapshot := clone_pointer_binding_value_keys(source)
empty_snapshot['new'] = ['new']
assert source.len == 0
owner := ScopeBindingOwner{ index: 1, name: 'original' }
mut owners := {
'key': [owner]
}
mut owner_snapshot := clone_scope_binding_owner_map(owners)
owner_snapshot['key'][0] = ScopeBindingOwner{ index: 2, name: 'changed' }
owner_snapshot['key'] << owner
assert owners['key'] == [owner]
}
fn test_pointer_binding_merge_preserves_order_fallbacks_and_independent_values() {
mut base := {
'a': ['one']
'b': ['two']
}
mut left := {
'a': ['left', 'left']
'c': ['three']
}
mut right := {
'a': ['right', 'left']
'd': ['four']
}
mut merged := merge_pointer_binding_value_states([left, right], base)
assert merged.keys() == ['a', 'b', 'c', 'd']
assert merged['a'] == ['left', 'right']
assert merged['b'] == ['two']
assert merged['c'] == ['three', 'c']
assert merged['d'] == ['d', 'four']
merged['a'][0] = 'changed'
merged['b'] << 'new'
assert base['b'] == ['two']
assert left['a'] == ['left', 'left']
assert right['a'] == ['right', 'left']
}
fn test_pointer_binding_branch_snapshots_isolate_replacement_and_invalidation() {
mut source := {
'left': ['first', 'second']
'right': ['third']
}
mut snapshot := snapshot_pointer_binding_value_keys(source)
source['left'] = ['changed']
source.delete('right')
source['new'] = ['new']
assert snapshot['left'] == ['first', 'second']
assert snapshot['right'] == ['third']
assert 'new' !in snapshot
snapshot['left'] = ['other']
assert source['left'] == ['changed']
mut tc := TypeChecker{}
tc.fn_context.pointer_binding_value_keys = snapshot
tc.invalidate_all_pointer_binding_aliases()
assert source['left'] == ['changed']
}
fn selfhost_smartcast_index(mut a flat.FlatAst) flat.NodeId {
base := a.add_val(.ident, 'object')
field_start := a.begin_children()
a.add_child(base)
field := a.add_node(flat.Node{
kind: .selector
value: 'member'
children_start: field_start
children_count: 1
})
index := a.add_val(.int_literal, '7')
index_start := a.begin_children()
a.add_child(field)
a.add_child(index)
return a.add_node(flat.Node{
kind: .index
children_start: index_start
children_count: 2
})
}
fn test_empty_lexical_smartcast_filter_preserves_dynamic_types_and_range() {
mut a := flat.FlatAst.new()
id := selfhost_smartcast_index(mut a)
mut tc := TypeChecker.new(&a)
tc.build_direct_parent_index(&a)
tc.smartcast_key_filter = &SmartcastKeyFilter{}
tc.smartcast_key_filter.arm(0, a.nodes.len - 1)
assert tc.smartcast_type(id) == none
assert tc.smartcast_key_filter.ready
assert tc.type_cache.lexical_smartcast_miss(int(id))
tc.smartcasts['object.member[7]'] = Type(int_)
assert tc.smartcast_type(id)? == Type(int_)
tc.smartcasts.delete('object.member[7]')
assert tc.smartcast_type(id) == none
assert !tc.lexical_smartcasts_unused(-1)
assert !tc.lexical_smartcasts_unused(a.nodes.len)
// Reusing the filter for another body must discard its earlier empty state.
type_id := a.add_val(.ident, 'int')
condition_start := a.begin_children()
a.add_child(id)
a.add_child(type_id)
a.add_node(flat.Node{
kind: .is_expr
children_start: condition_start
children_count: 2
})
tc.smartcast_key_filter.arm(0, a.nodes.len - 1)
assert !tc.lexical_smartcasts_unused(int(id))
assert !tc.lexical_smartcast_key_unused(int(id), 'object.member[7]')
tc.smartcast_key_filter = unsafe { nil }
assert !tc.lexical_smartcasts_unused(int(id))
}
fn test_empty_lexical_smartcast_filter_avoids_composite_key_allocations() {
$if prealloc && prealloc_stats ? {
mut a := flat.FlatAst.new()
id := selfhost_smartcast_index(mut a)
mut tc := TypeChecker.new(&a)
tc.build_direct_parent_index(&a)
tc.lexical_smartcast_misses = []bool{len: a.nodes.len}
tc.smartcast_key_filter = &SmartcastKeyFilter{}
tc.smartcast_key_filter.arm(0, a.nodes.len - 1)
assert tc.smartcast_type(id) == none
before := prealloc_stats_snapshot()
assert before.enabled
for _ in 0 .. 4096 {
// Force a cold per-node lookup without reallocating its cache page.
tc.type_cache.set_lexical_smartcast_state(int(id), 0)
tc.lexical_smartcast_misses[int(id)] = false
assert tc.smartcast_type(id) == none
}
after := prealloc_stats_snapshot()
assert after.allocation_count == before.allocation_count
assert after.allocated_bytes == before.allocated_bytes
}
}
fn test_small_lexical_smartcast_filter_matches_indexed_and_selector_keys() {
mut a := flat.FlatAst.new()
id := selfhost_smartcast_index(mut a)
mut tc := TypeChecker.new(&a)
tc.smartcast_key_filter = &SmartcastKeyFilter{
lo: 0
hi: a.nodes.len - 1
ready: true
keys: ['other.member[7]', 'object.member[70]', 'object.member']
}
assert tc.lexical_smartcasts_unused(int(id))
assert !tc.expr_key_matches(id, 'object.member[7x]')
assert !tc.expr_key_matches(id, 'object.member[7')
assert !tc.expr_key_matches(id, '[7]')
assert !tc.expr_key_matches(id, '')
tc.smartcast_key_filter.add('object.member[7]')
assert !tc.lexical_smartcasts_unused(int(id))
for i in 0 .. smartcast_key_filter_linear + 1 {
tc.smartcast_key_filter.add('another${i}')
}
assert tc.smartcast_key_filter.index.len > 0
assert !tc.lexical_smartcasts_unused(int(id))
}
fn test_small_lexical_smartcast_filter_avoids_unrelated_index_key_allocations() {
$if prealloc && prealloc_stats ? {
mut a := flat.FlatAst.new()
id := selfhost_smartcast_index(mut a)
mut tc := TypeChecker.new(&a)
tc.build_direct_parent_index(&a)
tc.lexical_smartcast_misses = []bool{len: a.nodes.len}
tc.smartcast_key_filter = &SmartcastKeyFilter{
lo: 0
hi: a.nodes.len - 1
ready: true
keys: ['different', 'object.other[7]', 'object.member[70]']
}
assert tc.smartcast_type(id) == none
before := prealloc_stats_snapshot()
assert before.enabled
for _ in 0 .. 4096 {
tc.type_cache.set_lexical_smartcast_state(int(id), 0)
tc.lexical_smartcast_misses[int(id)] = false
assert tc.smartcast_type(id) == none
}
after := prealloc_stats_snapshot()
assert after.allocation_count == before.allocation_count
assert after.allocated_bytes == before.allocated_bytes
}
}
+150 -54
View File
@@ -1,5 +1,6 @@
module types
import strings
import v.flat
// Type aliases type values used by types.
@@ -35,6 +36,23 @@ pub struct Void {
// Unknown represents unknown data used by types.
pub struct Unknown {
UnknownDataRef = empty_unknown_data
}
// == compares Unknown payloads by value.
// Cast payload references to compare addresses before inspecting their fields.
@[inline]
pub fn (a Unknown) == (b Unknown) bool {
return voidptr(a.UnknownDataRef) == voidptr(b.UnknownDataRef) || *a.UnknownDataRef == *b.UnknownDataRef
}
const empty_unknown_data = &UnknownData{}
// UnknownDataRef references the immutable payload of Unknown.
pub type UnknownDataRef = &UnknownData
// UnknownData stores the immutable fields of Unknown.
pub struct UnknownData {
pub:
reason string
}
@@ -99,6 +117,22 @@ pub:
// ArrayFixed represents array fixed data used by types.
pub struct ArrayFixed {
ArrayFixedDataRef = empty_array_fixed_data
}
// == compares ArrayFixed payloads by value.
@[inline]
pub fn (a ArrayFixed) == (b ArrayFixed) bool {
return voidptr(a.ArrayFixedDataRef) == voidptr(b.ArrayFixedDataRef) || *a.ArrayFixedDataRef == *b.ArrayFixedDataRef
}
const empty_array_fixed_data = &ArrayFixedData{}
// ArrayFixedDataRef references the immutable payload of ArrayFixed.
pub type ArrayFixedDataRef = &ArrayFixedData
// ArrayFixedData stores the immutable fields of ArrayFixed.
pub struct ArrayFixedData {
pub:
elem_type &Type = empty_type
len int
@@ -107,6 +141,22 @@ pub:
// Channel represents channel data used by types.
pub struct Channel {
ChannelDataRef = empty_channel_data
}
// == compares Channel payloads by value.
@[inline]
pub fn (a Channel) == (b Channel) bool {
return voidptr(a.ChannelDataRef) == voidptr(b.ChannelDataRef) || *a.ChannelDataRef == *b.ChannelDataRef
}
const empty_channel_data = &ChannelData{}
// ChannelDataRef references the immutable payload of Channel.
pub type ChannelDataRef = &ChannelData
// ChannelData stores the immutable fields of Channel.
pub struct ChannelData {
pub:
elem_type &Type = empty_type
is_mut bool
@@ -114,6 +164,22 @@ pub:
// Map represents map data used by types.
pub struct Map {
MapDataRef = empty_map_data
}
// == compares Map payloads by value.
@[inline]
pub fn (a Map) == (b Map) bool {
return voidptr(a.MapDataRef) == voidptr(b.MapDataRef) || *a.MapDataRef == *b.MapDataRef
}
const empty_map_data = &MapData{}
// MapDataRef references the immutable payload of Map.
pub type MapDataRef = &MapData
// MapData stores the immutable fields of Map.
pub struct MapData {
pub:
key_type &Type = empty_type
value_type &Type = empty_type
@@ -127,6 +193,22 @@ pub:
// FnType represents fn type data used by types.
pub struct FnType {
FnTypeDataRef = empty_fn_type_data
}
// == compares FnType payloads by value.
@[inline]
pub fn (a FnType) == (b FnType) bool {
return voidptr(a.FnTypeDataRef) == voidptr(b.FnTypeDataRef) || *a.FnTypeDataRef == *b.FnTypeDataRef
}
const empty_fn_type_data = &FnTypeData{}
// FnTypeDataRef references the immutable payload of FnType.
pub type FnTypeDataRef = &FnTypeData
// FnTypeData stores the immutable fields of FnType.
pub struct FnTypeData {
pub:
params []FnParam
return_type &Type = empty_type
@@ -178,6 +260,22 @@ pub:
// Alias represents alias data used by types.
pub struct Alias {
AliasDataRef = empty_alias_data
}
// == compares Alias payloads by value.
@[inline]
pub fn (a Alias) == (b Alias) bool {
return voidptr(a.AliasDataRef) == voidptr(b.AliasDataRef) || *a.AliasDataRef == *b.AliasDataRef
}
const empty_alias_data = &AliasData{}
// AliasDataRef references the immutable payload of Alias.
pub type AliasDataRef = &AliasData
// AliasData stores the immutable fields of Alias.
pub struct AliasData {
pub:
name_id u32
base_type &Type = empty_type
@@ -185,8 +283,24 @@ pub:
// MultiReturn represents multi return data used by types.
pub struct MultiReturn {
MultiReturnDataRef = empty_multi_return_data
}
// == compares MultiReturn payloads by value.
@[inline]
pub fn (a MultiReturn) == (b MultiReturn) bool {
return voidptr(a.MultiReturnDataRef) == voidptr(b.MultiReturnDataRef) || *a.MultiReturnDataRef == *b.MultiReturnDataRef
}
const empty_multi_return_data = &MultiReturnData{}
// MultiReturnDataRef references the immutable payload of MultiReturn.
pub type MultiReturnDataRef = &MultiReturnData
// MultiReturnData stores the immutable fields of MultiReturn.
pub struct MultiReturnData {
pub:
types []Type
types []types.Type
}
// new_type_reference allocates a shallow copy of value with stable reference storage.
@@ -371,16 +485,19 @@ pub fn generic_base_name(name string) string {
}
// is_pointer reports whether is pointer applies in types.
@[inline]
pub fn (t &Type) is_pointer() bool {
return t is Pointer
}
// is_string reports whether is string applies in types.
@[inline]
pub fn (t &Type) is_string() bool {
return t is String
}
// is_integer reports whether is integer applies in types.
@[inline]
pub fn (t &Type) is_integer() bool {
if t is Primitive {
return t.props.has(.integer)
@@ -418,6 +535,7 @@ pub fn unsigned_shift_result_type(t Type) Type {
}
// is_float reports whether is float applies in types.
@[inline]
pub fn (t &Type) is_float() bool {
if t is Primitive {
return t.props.has(.float)
@@ -426,19 +544,26 @@ pub fn (t &Type) is_float() bool {
}
// name returns name data for Type.
@[inline]
pub fn (t &Type) name() string {
if t is Void {
return 'void'
}
if t is Struct {
return flat.source_type_name(t.name_id)
}
if t is Alias {
return flat.source_type_name(t.name_id)
}
if t is Primitive {
return prim_name_from(t.props, t.size)
return match t {
Void { 'void' }
Struct, Alias, Interface, Enum, SumType { flat.source_type_name(t.name_id) }
Primitive { prim_name_from(t.props, t.size) }
String { 'string' }
Unknown { 'unknown' }
Nil { 'nil' }
None { 'none' }
Char { 'char' }
Rune { 'rune' }
ISize { 'isize' }
USize { 'usize' }
else { t.composite_name() }
}
}
@[direct_array_access]
fn (t &Type) composite_name() string {
if t is Pointer {
if t.base_type is Void {
return 'voidptr'
@@ -448,18 +573,6 @@ pub fn (t &Type) name() string {
if t is Array {
return '[]${nested_type_name(t.elem_type)}'
}
if t is Interface {
return flat.source_type_name(t.name_id)
}
if t is Enum {
return flat.source_type_name(t.name_id)
}
if t is SumType {
return flat.source_type_name(t.name_id)
}
if t is String {
return 'string'
}
if t is OptionType {
return '?${nested_type_name(t.base_type)}'
}
@@ -467,30 +580,33 @@ pub fn (t &Type) name() string {
return '!${nested_type_name(t.base_type)}'
}
if t is FnType {
mut s := 'fn('
mut s := strings.new_builder(32 + t.params.len * 8)
s.write_string('fn(')
for i in 0 .. t.params.len {
if i > 0 {
s += ', '
s.write_string(', ')
}
param := t.params[i].typ
if t.params[i].is_mut {
s += 'mut '
s.write_string('mut ')
if param is Pointer {
s += nested_type_name(param.base_type)
s.write_string(nested_type_name(param.base_type))
continue
}
}
if t.is_variadic && i == t.params.len - 1 && param is Array {
s += '...${nested_type_name(param.elem_type)}'
s.write_string('...')
s.write_string(nested_type_name(param.elem_type))
} else {
s += nested_type_name(param)
s.write_string(nested_type_name(param))
}
}
s += ')'
s.write_u8(`)`)
if t.return_type !is Void {
s += ' ${nested_type_name(t.return_type)}'
s.write_u8(` `)
s.write_string(nested_type_name(t.return_type))
}
return s
return s.str()
}
if t is Map {
return 'map[${nested_type_name(t.key_type)}]${nested_type_name(t.value_type)}'
@@ -508,27 +624,6 @@ pub fn (t &Type) name() string {
}
return 'chan ${nested_type_name(t.elem_type)}'
}
if t is Unknown {
return 'unknown'
}
if t is Nil {
return 'nil'
}
if t is None {
return 'none'
}
if t is Char {
return 'char'
}
if t is Rune {
return 'rune'
}
if t is ISize {
return 'isize'
}
if t is USize {
return 'usize'
}
if t is MultiReturn {
mut parts := []string{cap: t.types.len}
for i in 0 .. t.types.len {
@@ -682,6 +777,7 @@ pub fn (value SumType) name() string {
}
// source_type_id returns an interned spelling, reusing nominal type identities directly.
@[inline]
pub fn (t &Type) source_type_id() u32 {
return match t {
Struct, Alias, Enum, Interface, SumType { t.name_id }
+10 -8
View File
@@ -935,14 +935,16 @@ fn test_cached_expr_type_references_preserve_storage_and_visibility() {
tc.reset_node_caches(8)
tc.remember_expr_type(flat.NodeId(1), Type(int_))
tc.remember_expr_type(flat.NodeId(5), Type(string_))
dense := tc.cached_expr_type_ref(flat.NodeId(1)) or { panic('missing dense type') }
dense := tc.cached_expr_type_ref(flat.NodeId(1))
assert !isnil(dense)
assert voidptr(dense) == voidptr(tc.expr_type_values[1])
tc.parallel_check_sparse = true
tc.check_range_lo = 0
tc.check_range_hi = 4
assert tc.cached_expr_type_ref(flat.NodeId(5)) == none
assert isnil(tc.cached_expr_type_ref(flat.NodeId(5)))
tc.sparse_expr_type_values[5] = Type(bool_)
sparse := tc.cached_expr_type_ref(flat.NodeId(5)) or { panic('missing sparse type') }
sparse := tc.cached_expr_type_ref(flat.NodeId(5))
assert !isnil(sparse)
assert *sparse == Type(bool_)
for i in 8 .. 4096 {
tc.sparse_expr_type_values[i] = Type(int_)
@@ -953,12 +955,12 @@ fn test_cached_expr_type_references_preserve_storage_and_visibility() {
assert tc.cached_expr_type(flat.NodeId(5))? == Type(string_)
mut probe := tc.fork_storage_observation_view()
assert *probe.cached_expr_type_ref(flat.NodeId(1))? == Type(int_)
assert *probe.cached_expr_type_ref(flat.NodeId(5))? == Type(string_)
assert *probe.cached_expr_type_ref(flat.NodeId(1)) == Type(int_)
assert *probe.cached_expr_type_ref(flat.NodeId(5)) == Type(string_)
probe.sparse_expr_type_values[5] = Type(u64_)
assert *probe.cached_expr_type_ref(flat.NodeId(5))? == Type(u64_)
assert *tc.cached_expr_type_ref(flat.NodeId(5))? == Type(string_)
assert *probe.cached_expr_type_ref(flat.NodeId(5)) == Type(u64_)
assert *tc.cached_expr_type_ref(flat.NodeId(5)) == Type(string_)
tc.invalidate_checked_expr_type(1)
assert tc.cached_expr_type_ref(flat.NodeId(1)) == none
assert isnil(tc.cached_expr_type_ref(flat.NodeId(1)))
assert *dense == Type(int_)
}
+131 -30
View File
@@ -16,12 +16,14 @@ struct CanonicalTypeEntry {
@[heap]
struct TypeInterner {
mut:
lock &sync.RwMutex = unsafe { nil }
types []&Type
names []u32
buckets map[u64]TypeId
entries []&CanonicalTypeEntry
recent [4096]voidptr
lock &sync.RwMutex = unsafe { nil }
types []&Type
names []u32
buckets map[u64]TypeId
entries []&CanonicalTypeEntry
recent [4096]voidptr
identities [4096]voidptr
primitives [8192]voidptr
}
fn new_type_interner() &TypeInterner {
@@ -57,20 +59,41 @@ fn (mut i TypeInterner) intern_locked(t &Type, hash u64) (TypeId, &Type) {
id := TypeId(i.types.len)
canonical := new_type_reference(Type(owned))
i.types << canonical
i.entries << &CanonicalTypeEntry{ id: id, typ: canonical }
entry := &CanonicalTypeEntry{ id: id, typ: canonical }
i.entries << entry
i.remember_canonical_identity(entry)
i.names << u32(0)
i.buckets[key] = id
return id, i.types[int(id)]
}
@[inline]
fn (mut i TypeInterner) own_child_locked(t &Type) &Type {
entry := i.canonical_identity(t)
if !isnil(entry) {
return entry.typ
}
recent := i.recent_canonical_type(t)
if !isnil(recent) {
return recent.typ
}
return i.own_child_locked_miss(t)
}
fn (mut i TypeInterner) own_child_locked_miss(t &Type) &Type {
id, canonical := i.intern_locked(t, semantic_type_hash(t))
i.remember_canonical_type(t, i.entries[int(id)])
return canonical
}
fn (mut i TypeInterner) own_value(t &Type) Type {
return match t {
Array {
_, element := i.intern_locked(t.elem_type, semantic_type_hash(t.elem_type))
element := i.own_child_locked(t.elem_type)
Type(Array{ elem_type: element })
}
ArrayFixed {
_, element := i.intern_locked(t.elem_type, semantic_type_hash(t.elem_type))
element := i.own_child_locked(t.elem_type)
Type(ArrayFixed{
elem_type: element
len: t.len
@@ -78,43 +101,43 @@ fn (mut i TypeInterner) own_value(t &Type) Type {
})
}
Channel {
_, element := i.intern_locked(t.elem_type, semantic_type_hash(t.elem_type))
element := i.own_child_locked(t.elem_type)
Type(Channel{ elem_type: element, is_mut: t.is_mut })
}
Map {
_, key := i.intern_locked(t.key_type, semantic_type_hash(t.key_type))
_, value := i.intern_locked(t.value_type, semantic_type_hash(t.value_type))
key := i.own_child_locked(t.key_type)
value := i.own_child_locked(t.value_type)
Type(Map{ key_type: key, value_type: value })
}
Pointer {
_, base := i.intern_locked(t.base_type, semantic_type_hash(t.base_type))
base := i.own_child_locked(t.base_type)
Type(Pointer{ base_type: base })
}
FnType {
mut params := []FnParam{cap: t.params.len}
for param in t.params {
_, typ := i.intern_locked(param.typ, semantic_type_hash(param.typ))
typ := i.own_child_locked(param.typ)
params << FnParam{ typ: typ, is_mut: param.is_mut }
}
_, result := i.intern_locked(t.return_type, semantic_type_hash(t.return_type))
result := i.own_child_locked(t.return_type)
Type(FnType{ params: params, return_type: result, is_variadic: t.is_variadic })
}
OptionType {
_, base := i.intern_locked(t.base_type, semantic_type_hash(t.base_type))
base := i.own_child_locked(t.base_type)
Type(OptionType{ base_type: base })
}
ResultType {
_, base := i.intern_locked(t.base_type, semantic_type_hash(t.base_type))
base := i.own_child_locked(t.base_type)
Type(ResultType{ base_type: base })
}
Alias {
_, base := i.intern_locked(t.base_type, semantic_type_hash(t.base_type))
base := i.own_child_locked(t.base_type)
Type(Alias{ name_id: t.name_id, base_type: base })
}
MultiReturn {
mut types := []Type{cap: t.types.len}
for typ in t.types {
_, value := i.intern_locked(typ, semantic_type_hash(typ))
value := i.own_child_locked(typ)
types << *value
}
Type(MultiReturn{ types: types })
@@ -127,7 +150,20 @@ fn (mut i TypeInterner) own_value(t &Type) Type {
// probe returns the canonical copy when t is already interned. Readers must
// synchronize with table growth even when they do not insert a missing type.
@[inline]
fn (i &TypeInterner) probe(t &Type) ?&Type {
entry := i.canonical_identity(t)
if !isnil(entry) {
return entry.typ
}
recent := i.recent_canonical_type(t)
if !isnil(recent) {
return recent.typ
}
return i.probe_miss(t)
}
fn (i &TypeInterner) probe_miss(t &Type) ?&Type {
// The semantic lookup is read-only; only its synchronization state is mutable.
// Probes share a read lock, and interned types never change, so the hash
// and the comparisons run outside it: parallel probes do not queue.
@@ -143,9 +179,11 @@ fn (i &TypeInterner) probe(t &Type) ?&Type {
guard.runlock()
return none
}
candidate := i.types[int(id)]
entry := i.entries[int(id)]
candidate := entry.typ
guard.runlock()
if semantic_types_equal(candidate, t) {
i.remember_canonical_type(t, entry)
return candidate
}
key = type_hash_tag(key, 0x5bd1e995)
@@ -155,15 +193,30 @@ fn (i &TypeInterner) probe(t &Type) ?&Type {
// probe_frozen requires an immutable table and immutable semantic payloads
// until every reader joins. Normal callers must use the synchronized probe.
@[inline]
fn (i &TypeInterner) probe_frozen(t &Type) ?&Type {
entry := i.canonical_identity(t)
if !isnil(entry) {
return entry.typ
}
recent := i.recent_canonical_type(t)
if !isnil(recent) {
return recent.typ
}
return i.probe_frozen_miss(t)
}
fn (i &TypeInterner) probe_frozen_miss(t &Type) ?&Type {
mut key := semantic_type_hash(t)
for {
id := i.buckets[key] or { return none }
if int(id) < 0 || int(id) >= i.types.len {
return none
}
candidate := i.types[int(id)]
entry := i.entries[int(id)]
candidate := entry.typ
if semantic_types_equal(candidate, t) {
i.remember_canonical_type(t, entry)
return candidate
}
key = type_hash_tag(key, 0x5bd1e995)
@@ -185,10 +238,20 @@ fn (mut i TypeInterner) source_type_id(id TypeId) u32 {
return i.names[int(id)]
}
@[inline]
fn (mut i TypeInterner) canonicalize(t &Type) (TypeId, &Type) {
if entry := i.recent_canonical_type(t) {
entry := i.canonical_identity(t)
if !isnil(entry) {
return entry.id, entry.typ
}
recent := i.recent_canonical_type(t)
if !isnil(recent) {
return recent.id, recent.typ
}
return i.canonicalize_miss(t)
}
fn (mut i TypeInterner) canonicalize_miss(t &Type) (TypeId, &Type) {
hash := semantic_type_hash(t)
i.lock.lock()
defer {
@@ -240,7 +303,9 @@ fn (mut i TypeInterner) promote_from(start int, scope voidptr) {
owned := i.own_value(typ)
canonical := new_type_reference(Type(owned))
i.types << canonical
i.entries << &CanonicalTypeEntry{ id: TypeId(idx), typ: canonical }
entry := &CanonicalTypeEntry{ id: TypeId(idx), typ: canonical }
i.entries << entry
i.remember_canonical_identity(entry)
}
i.names = i.names.clone()
// A scoped insertion can rehash even after the caller reserves headroom.
@@ -249,6 +314,8 @@ fn (mut i TypeInterner) promote_from(start int, scope voidptr) {
i.buckets = i.buckets.clone()
}
// semantic_type_hash hashes the semantic fields of a type, including borrowed descriptors.
@[direct_array_access]
pub fn semantic_type_hash(t &Type) u64 {
mut hash := u64(14_695_981_039_346_656_037)
match t {
@@ -366,6 +433,7 @@ fn type_hash_tag(initial u64, value int) u64 {
return hash
}
@[direct_array_access]
fn type_hash_string(initial u64, value string) u64 {
mut hash := type_hash_tag(initial, value.len)
for idx in 0 .. value.len {
@@ -382,10 +450,23 @@ fn type_hash_child(initial u64, child &Type) u64 {
return hash
}
// semantic_types_equal compares semantic type values, including borrowed descriptors.
@[inline]
pub fn semantic_types_equal(a &Type, b &Type) bool {
if voidptr(a) == voidptr(b) {
return true
}
if a is Primitive {
return b is Primitive && a.props == b.props && a.size == b.size
}
if a is Struct {
return b is Struct && a.name_id == b.name_id
}
return semantic_types_equal_nested(a, b)
}
@[direct_array_access]
fn semantic_types_equal_nested(a &Type, b &Type) bool {
match a {
Void {
return b is Void
@@ -394,6 +475,9 @@ pub fn semantic_types_equal(a &Type, b &Type) bool {
if b !is Unknown {
return false
}
if voidptr(a.UnknownDataRef) == voidptr(b.UnknownDataRef) {
return true
}
return a.reason == b.reason
}
Primitive {
@@ -433,6 +517,9 @@ pub fn semantic_types_equal(a &Type, b &Type) bool {
if b !is ArrayFixed {
return false
}
if voidptr(a.ArrayFixedDataRef) == voidptr(b.ArrayFixedDataRef) {
return true
}
return a.len == b.len && a.len_expr == b.len_expr
&& semantic_types_equal(a.elem_type, b.elem_type)
}
@@ -440,12 +527,18 @@ pub fn semantic_types_equal(a &Type, b &Type) bool {
if b !is Channel {
return false
}
if voidptr(a.ChannelDataRef) == voidptr(b.ChannelDataRef) {
return true
}
return a.is_mut == b.is_mut && semantic_types_equal(a.elem_type, b.elem_type)
}
Map {
if b !is Map {
return false
}
if voidptr(a.MapDataRef) == voidptr(b.MapDataRef) {
return true
}
return semantic_types_equal(a.key_type, b.key_type)
&& semantic_types_equal(a.value_type, b.value_type)
}
@@ -459,6 +552,9 @@ pub fn semantic_types_equal(a &Type, b &Type) bool {
if b !is FnType {
return false
}
if voidptr(a.FnTypeDataRef) == voidptr(b.FnTypeDataRef) {
return true
}
if a.params.len != b.params.len || a.is_variadic != b.is_variadic || !semantic_types_equal(a.return_type, b.return_type) {
return false
}
@@ -510,12 +606,18 @@ pub fn semantic_types_equal(a &Type, b &Type) bool {
if b !is Alias {
return false
}
if voidptr(a.AliasDataRef) == voidptr(b.AliasDataRef) {
return true
}
return a.name_id == b.name_id && semantic_types_equal(a.base_type, b.base_type)
}
MultiReturn {
if b !is MultiReturn {
return false
}
if voidptr(a.MultiReturnDataRef) == voidptr(b.MultiReturnDataRef) {
return true
}
if a.types.len != b.types.len {
return false
}
@@ -529,7 +631,13 @@ pub fn semantic_types_equal(a &Type, b &Type) bool {
}
}
// fn_type creates a canonical signature with fixed arity.
pub fn (tc &TypeChecker) fn_type(params []Type, result &Type, mutability []bool) FnType {
return tc.variadic_fn_type(params, result, mutability, false)
}
// variadic_fn_type creates a canonical signature while preserving its variadic tail.
pub fn (tc &TypeChecker) variadic_fn_type(params []Type, result &Type, mutability []bool, is_variadic bool) FnType {
mut signature := []FnParam{cap: params.len}
for i in 0 .. params.len {
_, typ := tc.intern_type(params[i])
@@ -542,13 +650,6 @@ pub fn (tc &TypeChecker) fn_type(params []Type, result &Type, mutability []bool)
return FnType{
params: signature
return_type: return_type
}
}
// variadic_fn_type creates a canonical signature while preserving its variadic tail.
pub fn (tc &TypeChecker) variadic_fn_type(params []Type, result &Type, mutability []bool, is_variadic bool) FnType {
return FnType{
...tc.fn_type(params, result, mutability)
is_variadic: is_variadic
}
}
+82 -7
View File
@@ -1,19 +1,37 @@
module types
// Entries are immutable after atomic publication. Their storage follows the
// Cache misses return nil without constructing an Option. Entries are immutable
// after atomic publication. Their storage follows the
// canonical table; promotion clears the cache before releasing a stage arena.
@[direct_array_access; inline]
fn (i &TypeInterner) recent_canonical_type(t &Type) ?&CanonicalTypeEntry {
fn (i &TypeInterner) recent_canonical_type(t &Type) &CanonicalTypeEntry {
if t is Primitive && u32(t.props) < 32 {
// Properties and size are the complete primitive identity. Each slot
// contains an immutable owned entry and needs no structural comparison.
slot := (int(t.props) << 8) | int(t.size)
entry := unsafe { &CanonicalTypeEntry(C.atomic_load_ptr(&i.primitives[slot])) }
if !isnil(entry) {
return entry
}
return unsafe { nil }
}
slot := int(canonical_intern_cache_key(t) & 4095)
entry := unsafe { &CanonicalTypeEntry(C.atomic_load_ptr(&i.recent[slot])) }
if !isnil(entry) && semantic_types_equal(entry.typ, t) {
return entry
}
return none
return unsafe { nil }
}
@[direct_array_access; inline]
fn (i &TypeInterner) remember_canonical_type(t &Type, entry &CanonicalTypeEntry) {
if t is Primitive && u32(t.props) < 32 {
slot := (int(t.props) << 8) | int(t.size)
unsafe {
C.atomic_store_ptr(&i.primitives[slot], entry)
}
return
}
slot := int(canonical_intern_cache_key(t) & 4095)
unsafe {
C.atomic_store_ptr(&i.recent[slot], entry)
@@ -21,18 +39,40 @@ fn (i &TypeInterner) remember_canonical_type(t &Type, entry &CanonicalTypeEntry)
}
fn (i &TypeInterner) clear_recent_canonical_types() {
for slot in 0 .. i.primitives.len {
unsafe {
C.atomic_store_ptr(&i.primitives[slot], nil)
}
}
for slot in 0 .. i.recent.len {
unsafe {
C.atomic_store_ptr(&i.recent[slot], nil)
C.atomic_store_ptr(&i.identities[slot], nil)
}
}
}
// This forms a cache key without recursively hashing children. The owned entry is
// still compared structurally, so borrowed or mutated inputs cannot produce a hit
// merely because their addresses, lengths, or nominal spellings match.
// merely because their addresses, lengths, or nominal spellings match. Signature
// keys sample their contents so separately allocated parameter arrays can hit.
// Keep the common scalar and nominal cases small enough to inline at cache probes.
@[inline]
fn canonical_intern_cache_key(t &Type) u64 {
mut key := u64(0)
if t is Primitive {
key = u64(3) ^ (u64(t.props) << 16) ^ (u64(t.size) << 8)
} else if t is Struct {
key = u64(19) ^ (u64(t.name_id) << 8)
} else {
return canonical_intern_cache_key_composite(t)
}
key = (key ^ (key >> 32)) * u64(0x9e3779b97f4a7c15)
return key ^ (key >> 32)
}
@[direct_array_access]
fn canonical_intern_cache_key_composite(t &Type) u64 {
mut key := match t {
Void { u64(1) }
Unknown { u64(2) ^ (u64(t.reason.len) << 8) }
@@ -52,8 +92,16 @@ fn canonical_intern_cache_key(t &Type) u64 {
}
Pointer { u64(15) ^ canonical_intern_child_key(t.base_type) }
FnType {
u64(16) ^ (u64(t.params.data) >> 3) ^ (u64(t.params.len) << 8) ^
(u64(voidptr(t.return_type)) << 7) ^ u64(t.is_variadic)
mut signature := u64(16) ^ (u64(t.params.len) << 8) ^
(canonical_intern_child_key(t.return_type) << 7) ^ u64(t.is_variadic)
if t.params.len > 0 {
first := t.params[0]
last := t.params[t.params.len - 1]
signature ^= (canonical_intern_child_key(first.typ) << 11) ^
(canonical_intern_child_key(last.typ) << 19) ^
(u64(first.is_mut) << 5) ^ (u64(last.is_mut) << 6)
}
signature
}
OptionType { u64(17) ^ canonical_intern_child_key(t.base_type) }
ResultType { u64(18) ^ canonical_intern_child_key(t.base_type) }
@@ -62,7 +110,14 @@ fn canonical_intern_cache_key(t &Type) u64 {
Enum { u64(21) ^ (u64(t.name_id) << 8) ^ u64(t.is_flag) }
SumType { u64(22) ^ (u64(t.name_id) << 8) }
Alias { u64(23) ^ (u64(t.name_id) << 8) ^ canonical_intern_child_key(t.base_type) }
MultiReturn { u64(24) ^ (u64(t.types.data) >> 3) ^ (u64(t.types.len) << 8) }
MultiReturn {
mut signature := u64(24) ^ (u64(t.types.len) << 8)
if t.types.len > 0 {
signature ^= (canonical_intern_child_key(t.types[0]) << 11) ^
(canonical_intern_child_key(t.types[t.types.len - 1]) << 19)
}
signature
}
}
key = (key ^ (key >> 32)) * u64(0x9e3779b97f4a7c15)
return key ^ (key >> 32)
@@ -89,3 +144,23 @@ fn canonical_intern_child_key(t &Type) u64 {
else { u64(voidptr(t)) >> 3 }
}
}
// Only canonical owned pointers are published here. Borrowed values still need
// structural verification, even when their address has appeared before.
@[direct_array_access; inline]
fn (i &TypeInterner) canonical_identity(t &Type) &CanonicalTypeEntry {
slot := int((usize(t) >> 4) & 4095)
entry := unsafe { &CanonicalTypeEntry(C.atomic_load_ptr(&i.identities[slot])) }
if !isnil(entry) && voidptr(entry.typ) == voidptr(t) {
return entry
}
return unsafe { nil }
}
@[direct_array_access; inline]
fn (i &TypeInterner) remember_canonical_identity(entry &CanonicalTypeEntry) {
slot := int((usize(entry.typ) >> 4) & 4095)
unsafe {
C.atomic_store_ptr(&i.identities[slot], entry)
}
}
@@ -8,6 +8,13 @@ fn test_interner_owns_nested_function_parameters() {
return_type: &Type(string_)
})
id, canonical := interner.canonicalize(original)
assert isnil(interner.canonical_identity(original))
entry := interner.canonical_identity(canonical)
assert !isnil(entry)
assert entry.id == id
same_id, same := interner.canonicalize(canonical)
assert same_id == id
assert voidptr(same) == voidptr(canonical)
params[0] = FnParam{ typ: &Type(bool_), is_mut: true }
assert canonical.name() == 'fn(int) string', canonical.name()
changed_id, changed := interner.canonicalize(original)
@@ -20,8 +27,18 @@ fn test_interner_owns_nested_function_parameters() {
params: [FnParam{ typ: &Type(int_) }]
return_type: &Type(string_)
})
interner.clear_recent_canonical_types()
found := interner.probe(stable) or { panic('canonical payload changed after insertion') }
assert semantic_types_equal(found, stable)
probed := interner.recent_canonical_type(stable)
assert !isnil(probed)
assert probed.id == id
interner.clear_recent_canonical_types()
frozen := interner.probe_frozen(stable) or { panic('frozen probe missed an owned type') }
assert voidptr(frozen) == voidptr(found)
frozen_entry := interner.recent_canonical_type(stable)
assert !isnil(frozen_entry)
assert frozen_entry.id == id
new_id, _ := interner.canonicalize(stable)
assert new_id == id
assert interner.len() == canonical_count
@@ -52,3 +69,64 @@ fn test_interner_readers_do_not_share_caller_mutation() {
second.wait()
assert interner.len() == canonical_count
}
fn test_recent_signature_lookup_accepts_new_arrays_and_verifies_unsampled_parameters() {
mut interner := new_type_interner()
canonical_id, canonical := interner.canonicalize(Type(FnType{
params: [FnParam{ typ: &Type(int_) }, FnParam{ typ: &Type(bool_) },
FnParam{ typ: &Type(string_) }]
return_type: &Type(void_)
}))
mut params := [FnParam{ typ: &Type(int_) }, FnParam{ typ: &Type(bool_) },
FnParam{ typ: &Type(string_) }]
query := Type(FnType{ params: params, return_type: &Type(void_) })
entry := interner.recent_canonical_type(query)
assert !isnil(entry)
assert entry.id == canonical_id
params[1] = FnParam{ typ: &Type(u8_), is_mut: true }
assert isnil(interner.recent_canonical_type(query))
assert canonical.name() == 'fn(int, bool, string)'
multi_id, multi := interner.canonicalize(Type(MultiReturn{
types: [Type(int_), Type(bool_), Type(string_)]
}))
mut values := [Type(int_), Type(bool_), Type(string_)]
multi_query := Type(MultiReturn{ types: values })
multi_entry := interner.recent_canonical_type(multi_query)
assert !isnil(multi_entry)
assert multi_entry.id == multi_id
values[1] = Type(u8_)
assert isnil(interner.recent_canonical_type(multi_query))
assert multi.name() == '(int, bool, string)'
}
fn test_recent_primitive_cache_preserves_all_identity_bits_and_clear() {
mut interner := new_type_interner()
mut ids := []TypeId{}
for bits in 0 .. 32 {
for size in [0, 8, 16, 32, 64, 128, 255] {
typ := Type(Primitive{ props: unsafe { Properties(bits) }, size: u8(size) })
id, _ := interner.canonicalize(typ)
assert id !in ids
ids << id
}
}
mut index := 0
for bits in 0 .. 32 {
for size in [0, 8, 16, 32, 64, 128, 255] {
typ := Type(Primitive{ props: unsafe { Properties(bits) }, size: u8(size) })
entry := interner.recent_canonical_type(typ)
assert !isnil(entry)
assert entry.id == ids[index]
assert semantic_types_equal(entry.typ, typ)
index++
}
}
interner.clear_recent_canonical_types()
input := Type(int_)
assert isnil(interner.recent_canonical_type(input))
found := interner.probe(input) or { panic('cleared primitive was lost') }
assert semantic_types_equal(found, input)
entry := interner.recent_canonical_type(input)
assert !isnil(entry)
assert voidptr(entry.typ) == voidptr(found)
}
@@ -25,6 +25,8 @@ fn interner_parallel_reader(mut interner TypeInterner, ready chan bool, start ch
assert canonical is String
_, repeated := interner.canonicalize(Type(string_))
assert voidptr(repeated) == voidptr(canonical)
_, by_identity := interner.canonicalize(canonical)
assert voidptr(by_identity) == voidptr(canonical)
select {
_ := <-stop {
return
+125
View File
@@ -0,0 +1,125 @@
module types
fn test_type_descriptors_keep_two_word_layout() {
assert sizeof(Type) <= 16
for size in [sizeof(Unknown), sizeof(ArrayFixed), sizeof(Channel), sizeof(Map), sizeof(FnType),
sizeof(Alias), sizeof(MultiReturn)] {
assert size == sizeof(voidptr)
}
}
fn test_indirect_type_defaults_and_promoted_initializers() {
assert Unknown{}.reason == ''
fixed := ArrayFixed{}
assert fixed.elem_type is Void
assert fixed.len == 0
assert fixed.len_expr == ''
channel := Channel{}
assert channel.elem_type is Void
assert !channel.is_mut
mapping := Map{}
assert mapping.key_type is Void
assert mapping.value_type is Void
signature := FnType{}
assert signature.params.len == 0
assert signature.return_type is Void
assert !signature.is_variadic
alias := Alias{}
assert alias.name_id == 0
assert alias.base_type is Void
assert MultiReturn{}.types.len == 0
}
fn test_indirect_type_copies_and_owned_clones_preserve_semantics() {
values := [Type(Unknown{ reason: 'unknown'.clone() }),
Type(ArrayFixed{ elem_type: &builtin_int_type, len: 3, len_expr: 'size'.clone() }),
Type(Channel{ elem_type: &builtin_int_type, is_mut: true }),
Type(Map{ key_type: &builtin_string_type, value_type: &builtin_int_type }), Type(FnType{
params: [FnParam{ typ: &builtin_int_type, is_mut: true }]
return_type: &builtin_string_type
is_variadic: true
}), Type(Alias{ name_id: 123, base_type: &builtin_int_type }),
Type(MultiReturn{ types: [builtin_int_type, builtin_string_type] })]
for value in values {
copy := new_type_reference(value)
owned := clone_owned_type(value)
assert value == *copy
assert value == owned
assert semantic_type_hash(value) == semantic_type_hash(owned)
match value {
Unknown {
assert value == owned as Unknown
}
ArrayFixed {
assert value == owned as ArrayFixed
}
Channel {
assert value == owned as Channel
}
Map {
assert value == owned as Map
}
FnType {
assert value == owned as FnType
}
Alias {
assert value == owned as Alias
}
MultiReturn {
assert value == owned as MultiReturn
}
else {
assert false
}
}
}
}
fn test_promoted_type_payloads_keep_implicit_references() {
fixed := ArrayFixed{ elem_type: Type(string_), len: 2 }
channel := Channel{ elem_type: Type(int_), is_mut: true }
mapping := Map{ key_type: Type(string_), value_type: Type(int_) }
signature := FnType{ return_type: Type(string_) }
alias := Alias{ name_id: 123, base_type: Type(int_) }
assert fixed.elem_type is String
assert fixed.len == 2
assert channel.elem_type is Primitive
assert mapping.key_type is String
assert mapping.value_type is Primitive
assert signature.return_type is String
assert alias.base_type is Primitive
// A promoted initializer must copy the shared default rather than change it.
assert ArrayFixed{}.elem_type is Void
assert Map{}.key_type is Void
assert FnType{}.return_type is Void
}
fn test_indirect_type_payload_equality_rejects_different_fields() {
assert Unknown{ reason: 'a' } != Unknown{ reason: 'b' }
assert ArrayFixed{ len: 2 } != ArrayFixed{ len: 3 }
assert ArrayFixed{ len_expr: 'a' } != ArrayFixed{ len_expr: 'b' }
assert Channel{} != Channel{ is_mut: true }
assert Map{ key_type: &builtin_int_type } != Map{ key_type: &builtin_string_type }
assert FnType{} != FnType{ is_variadic: true }
assert FnType{ params: [FnParam{ typ: &builtin_int_type }] } != FnType{ params: [FnParam{ typ: &builtin_string_type }] }
assert Alias{ name_id: 1 } != Alias{ name_id: 2 }
assert MultiReturn{ types: [builtin_int_type] } != MultiReturn{ types: [builtin_string_type] }
}
fn test_function_type_names_preserve_nested_mutable_and_variadic_parameters() {
assert Type(FnType{}).name() == 'fn()'
mut_int := Type(Pointer{ base_type: &builtin_int_type })
strings := Type(Array{ elem_type: &builtin_string_type })
fn_type := Type(FnType{
params: [FnParam{ typ: &mut_int, is_mut: true }, FnParam{ typ: &strings }]
return_type: &builtin_string_type
is_variadic: true
})
assert fn_type.name() == 'fn(mut int, ...string) string'
outer := Type(FnType{
params: [FnParam{ typ: &fn_type }, FnParam{ typ: &builtin_int_type, is_mut: true }]
})
assert outer.name() == 'fn(fn(mut int, ...string) string, mut int)'
mutable_variadic := Type(FnType{ params: [FnParam{ typ: &mut_int, is_mut: true }], is_variadic: true })
assert mutable_variadic.name() == 'fn(mut int)'
}
+58
View File
@@ -4,6 +4,64 @@ Every compiler fix must preserve or improve self-compilation efficiency. Agents
retired instructions before and after compiler changes and resolve regressions before merging.
Keep correctness tests and generated-output checks alongside performance measurements.
## Compact semantic type descriptors
`types.Type` is again **16 bytes on 64-bit targets**, down from the tagged-union revision's
72 bytes. Its seven large variants hold references to immutable payloads; the remaining
variants stay inline. Promoted fields, constructors, value equality, semantic hashing, and
owned copies retain their semantics. Shared defaults avoid allocations for empty payloads.
The layout and ownership regression tests are in `vlib/v/types/type_layout_test.v` and the
interner tests. See `vlib/v/types/README.md` for the payload ownership rules.
The combined patch also reduces repeated type, smartcast, source-name, and C-name lookups,
retains bounded transform-helper storage through C generation, and shortens builtin map,
string-search, and small-copy paths. FastC, C, and SSA regression tests cover indirect payload
defaults, promoted initializers, and callback aliases needed by the compact representation.
A fresh comparison against master `5f7d4d09805ebbef64650d7bf275a007c7120e12` used the same
**559,990-line** fixture at `2d1367e589ed30c03325e586a8ddfd3bc96c8c3d`. Both roles used the
same seed, two in-place TCC rebuilds, Apple Clang 21 production builds with `-mcpu=apple-m1`,
and fresh matching PGO profiles trained on this fixture with default and single-worker modes.
The compiler build directories differed; measured executable, input, output, and working-directory
paths matched. The measured candidate combines compact types and hot-path changes in the retained
`compact-v76` source snapshot. Subsequent FastC correctness fixes and master integration, including
atomic cache portability changes, are outside this measurement. The final reviewed revision has
not been benchmarked.
One warmup per role preceded ten alternating measured pairs with `VJOBS=1`. All 22 runs
succeeded, each with one positive retired-instruction counter and byte-identical generated C.
The common output passed Apple Clang's Objective-C syntax check with the fixture's include paths.
| Median metric | Master | Compact types and hot-path changes |
| --- | ---: | ---: |
| Retired instructions | 56,454,904,776.5 | 51,480,959,993.5 |
| Instructions/parsed line | 100,814.13 | 91,931.93 |
| CPU cycles, billions | 22.1275 | 21.1105 |
| Peak resident memory, MiB | 1,125.72 | 1,172.06 |
| Elapsed seconds | 7.3736 | 6.9148 |
The candidate used **8.81% fewer instructions**, with **0/10 regressing pairs** and a median
paired difference of `-4,934,100,840.5` instructions. Peak memory increased by about 46 MiB.
Elapsed time and cycles are contextual shared-host measurements, not controlled latency claims.
The **90,000 instructions/line target remains unmet**: it requires `50,399,100,000` instructions
on this unchanged fixture. Keep the fixture and denominator fixed when checking future changes.
The baseline executable SHA-256 is
`56b5b8210947b185aa7e73585ba33f4df072dabd2d0e5403ed8cbcc7db0e82bd`;
the candidate executable SHA-256 is
`15fc29aa638170d24cd98973f62d7bb5d48dcf638063695462b82a264680dcbe`.
Generated C has SHA-256
`ade8fd366a802bee022b06cdb2d3e5167988f2a918f031f10eb6586f6e50660f`.
Exact build commands, source snapshots, fixture inventories, binary hashes, all raw samples,
and generated-output checks are retained in
`/Users/alex/code/v3-self-90k-artifacts/compact-type-20261021/`.
Use `build.py`, `measure.py`, `baseline-master/metadata.json`,
`compact-v76-standard/metadata.json`, `compact-v76-source/sha256.json`, and
`compact-v76-pr-confirmation{,-metadata,-summary}.json` with their per-run logs.
The current fixture manifest and counting procedure are recorded below. Exploratory variants
are excluded from this comparison and from the patch.
## Why the reported scores differed
The earlier **115,328 -> 95,877 instructions/line** comparison used compiler sources and input