whole-function comptime folding for zero-runtime value calls
This commit is contained in:
@@ -62,6 +62,13 @@ append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||
return
|
||||
}
|
||||
|
||||
pop func($T type, list @mut ArrayList(T)) ?T {
|
||||
if (list.items.len == 0) return null
|
||||
value :: list.items[list.items.len - 1]
|
||||
list.items = list.items.ptr[..list.items.len - 1]
|
||||
return value
|
||||
}
|
||||
|
||||
clear func($T type, list @mut ArrayList(T)) void {
|
||||
list.items = list.items.ptr[..0]
|
||||
}
|
||||
@@ -40,8 +40,8 @@ init func(
|
||||
}
|
||||
}
|
||||
|
||||
# free the entries in the hash map.
|
||||
# note: this operation invalidates the map.
|
||||
#! free the entries in the hash map.
|
||||
#! note: this operation invalidates the map.
|
||||
deinit func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
@@ -28,29 +28,20 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
||||
}
|
||||
|
||||
eql func($T type, left, right []T) bool {
|
||||
if left.len != right.len {
|
||||
return false
|
||||
}
|
||||
|
||||
i usize = 0
|
||||
while i < left.len : i += 1 {
|
||||
if left[i] != right[i] {
|
||||
return false
|
||||
}
|
||||
if (left.len != right.len) return false
|
||||
for (0..left.len) |i| if (left[i] != right[i]) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
# allocate memory for a slice of type `T` with `count` elements.
|
||||
#! allocate memory for a slice of type `T` with `count` elements.
|
||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
if count == 0 {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
if (count == 0) return empty_slice(T, 0)
|
||||
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return empty_slice(T, count)
|
||||
}
|
||||
if (element_size == 0) return empty_slice(T, count)
|
||||
|
||||
if count > divtrunc!(maxval!(usize), element_size) {
|
||||
return .out_of_memory
|
||||
}
|
||||
@@ -63,9 +54,9 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
# reallocate memory for a slice of type `T` with `new_count` elements.
|
||||
# reallocating with `new_count == 0` will free the memory and return an empty slice.
|
||||
# note: memory must be reallocated with the same allocator that was used to allocate it.
|
||||
#! reallocate memory for a slice of type `T` with `new_count` elements.
|
||||
#! reallocating with `new_count == 0` will free the memory and return an empty slice.
|
||||
#! note: memory must be reallocated with the same allocator that was used to allocate it.
|
||||
realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
|
||||
if new_count == memory.len {
|
||||
return memory
|
||||
@@ -105,22 +96,25 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
# free memory allocated for a slice of type `T`.
|
||||
# note: memory must be freed with the same allocator that was used to allocate it.
|
||||
#! free memory allocated for a slice of type `T`.
|
||||
#! note: memory must be freed with the same allocator that was used to allocate it.
|
||||
free func($T type, allocator Allocator, memory []T) void {
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
mutable_memory []mut T :: constcast!(memory)
|
||||
raw_free(allocator, ptrcast!(u8, mutable_memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
if (memory.len == 0 or sizeof!(T) == 0) return
|
||||
raw_free(allocator, ptrcast!(
|
||||
u8,
|
||||
constcast!(memory).ptr),
|
||||
memory.len * sizeof!(T),
|
||||
alignof!(T),
|
||||
)
|
||||
}
|
||||
|
||||
# get an empty slice of type `T` with `count` elements.
|
||||
#! get an empty slice of type `T` with `count` elements.
|
||||
empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
# get an empty slice of type `T` with 0 elements.
|
||||
#! get an empty slice of type `T` with 0 elements.
|
||||
empty func($T type) []mut T {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
@@ -130,26 +124,18 @@ hide empty_storage [1]mut u64 = [0]
|
||||
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
|
||||
hide power_of_two func(value usize) bool {
|
||||
if value == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if (value == 0) return false
|
||||
current usize = value
|
||||
while current > 1 {
|
||||
half usize = divtrunc!(current, 2)
|
||||
if half * 2 != current {
|
||||
return false
|
||||
}
|
||||
if (half * 2 != current) return false
|
||||
current = half
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
if power_of_two(alignment) == false {
|
||||
return null
|
||||
}
|
||||
if (power_of_two(alignment) == false) return null
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
@@ -157,17 +143,13 @@ hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [null]
|
||||
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
if status != 0 {
|
||||
return null
|
||||
}
|
||||
if (status != 0) return null
|
||||
|
||||
return ptrcast!(u8, memory[0])
|
||||
}
|
||||
|
||||
hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
if power_of_two(alignment) == false {
|
||||
return null
|
||||
}
|
||||
if (power_of_two(alignment) == false) return null
|
||||
|
||||
if new_size == 0 {
|
||||
c.free(memory)
|
||||
@@ -182,9 +164,7 @@ hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
new_memory ?*mut u8 = c_alloc(null, new_size, alignment)
|
||||
if new_memory |new_bytes| {
|
||||
copy_size usize = old_size
|
||||
if new_size < copy_size {
|
||||
copy_size = new_size
|
||||
}
|
||||
if (new_size < copy_size) copy_size = new_size
|
||||
memcopy!(new_bytes[..copy_size], old_memory[..copy_size])
|
||||
c.free(old_memory)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
Layout :: enum { auto c }
|
||||
Layout :: enum { auto, c }
|
||||
|
||||
ArrayInfo :: struct {
|
||||
child type
|
||||
@@ -46,9 +46,9 @@ TypeInfo :: union(enum) {
|
||||
EnumFieldStruct func($E, $Field type, $default ?Field) type {
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: {
|
||||
names [info.fields.len]mut []u8 = undefined
|
||||
field_types [info.fields.len]mut type = undefined
|
||||
defaults [info.fields.len]mut ?Field = undefined
|
||||
names [info.fields.len]mut []u8 = undefined
|
||||
field_types [info.fields.len]mut type = undefined
|
||||
defaults [info.fields.len]mut ?Field = undefined
|
||||
expand for info.fields |field, index| {
|
||||
names[index] = field.name
|
||||
field_types[index] = Field
|
||||
@@ -1,106 +0,0 @@
|
||||
import "@std/mem"
|
||||
|
||||
StaticStringMap func($V type) type {
|
||||
return struct {
|
||||
keys [][]u8
|
||||
values []V
|
||||
len_indexes []u32
|
||||
min_len u32
|
||||
max_len u32
|
||||
}
|
||||
}
|
||||
|
||||
hide Pair func($V type) type {
|
||||
return struct { []u8, V }
|
||||
}
|
||||
|
||||
init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
||||
return ${
|
||||
if N > usize(maxval!(u32)) {
|
||||
compile_error!("static string map has too many entries")
|
||||
}
|
||||
|
||||
keys [N]mut []u8 = undefined
|
||||
values [N]mut V = undefined
|
||||
for entries |entry, i| {
|
||||
if entry.0.len > usize(maxval!(u32)) {
|
||||
compile_error!("static string map key is too long")
|
||||
}
|
||||
for (usize(0))..i |prior| {
|
||||
if mem.eql(u8, entry.0, entries[prior].0) {
|
||||
compile_error!("duplicate static string map key")
|
||||
}
|
||||
}
|
||||
keys[i] = entry.0
|
||||
values[i] = entry.1
|
||||
}
|
||||
|
||||
result :: done: {
|
||||
if N == 0 {
|
||||
len_indexes [0]mut u32 = undefined
|
||||
yield :done StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = 0,
|
||||
max_len = 0,
|
||||
}
|
||||
}
|
||||
|
||||
# ponytail: insertion sort is compile-time O(N²); replace if large maps affect builds.
|
||||
i usize = 1
|
||||
while i < N : i += 1 {
|
||||
key :: keys[i]
|
||||
value :: values[i]
|
||||
j usize = i
|
||||
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||
keys[j] = keys[j - 1]
|
||||
values[j] = values[j - 1]
|
||||
}
|
||||
keys[j] = key
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
min_len u32 :: u32(keys[0].len)
|
||||
max_len u32 :: u32(keys[N - 1].len)
|
||||
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||
entry_index usize = 0
|
||||
length usize = 0
|
||||
while length <= usize(max_len) : length += 1 {
|
||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||
len_indexes[length] = u32(entry_index)
|
||||
}
|
||||
|
||||
yield :done StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = min_len,
|
||||
max_len = max_len,
|
||||
}
|
||||
}
|
||||
yield result
|
||||
}
|
||||
}
|
||||
|
||||
get func($V type, map @StaticStringMap(V), key []u8) ?V {
|
||||
if map.keys.len == 0 or key.len > usize(maxval!(u32)) {
|
||||
return null
|
||||
}
|
||||
length u32 :: u32(key.len)
|
||||
if length < map.min_len or length > map.max_len {
|
||||
return null
|
||||
}
|
||||
index usize = usize(map.len_indexes[usize(length)])
|
||||
while index < map.keys.len {
|
||||
candidate :: map.keys[index]
|
||||
if candidate.len != key.len {
|
||||
return null
|
||||
}
|
||||
if mem.eql(u8, candidate, key) {
|
||||
return map.values[index]
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import "@std/mem"
|
||||
|
||||
StaticStringMap func($V type) type {
|
||||
return struct {
|
||||
keys [][]u8
|
||||
values []V
|
||||
len_indexes []u32
|
||||
min_len u32
|
||||
max_len u32
|
||||
}
|
||||
}
|
||||
|
||||
hide Pair func($V type) type {
|
||||
return struct { []u8, V }
|
||||
}
|
||||
|
||||
init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
||||
if N > usize(maxval!(u32)) {
|
||||
compile_error!("static string map has too many entries")
|
||||
}
|
||||
|
||||
keys [N]mut []u8 = undefined
|
||||
values [N]mut V = undefined
|
||||
|
||||
# assert no duplicate keys
|
||||
for entries |entry, i| {
|
||||
if entry.0.len > usize(maxval!(u32)) {
|
||||
compile_error!("static string map key is too long")
|
||||
}
|
||||
|
||||
for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) {
|
||||
compile_error!("duplicate static string map key")
|
||||
}
|
||||
|
||||
keys[i] = entry.0
|
||||
values[i] = entry.1
|
||||
}
|
||||
|
||||
if N == 0 {
|
||||
len_indexes [0]u32 = undefined
|
||||
return StaticStringMap(V){
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = 0,
|
||||
max_len = 0,
|
||||
}
|
||||
}
|
||||
|
||||
# fixme: insertion sort is compile-time O(N^2); replace if large maps affect builds
|
||||
for 1..N |i| {
|
||||
key :: keys[i]
|
||||
value :: values[i]
|
||||
j usize = i
|
||||
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||
keys[j] = keys[j - 1]
|
||||
values[j] = values[j - 1]
|
||||
}
|
||||
keys[j] = key
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
min_len u32 :: u32(keys[0].len)
|
||||
max_len u32 :: u32(keys[N - 1].len)
|
||||
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||
entry_index usize = 0
|
||||
for 0..=(usize(max_len)) |length| {
|
||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||
len_indexes[length] = u32(entry_index)
|
||||
}
|
||||
|
||||
return StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = min_len,
|
||||
max_len = max_len,
|
||||
}
|
||||
}
|
||||
|
||||
get func($V type, map @StaticStringMap(V), key []u8) ?V {
|
||||
if (map.keys.len == 0 or key.len > maxval!(u32)) return null
|
||||
|
||||
length u32 = u32(key.len)
|
||||
if (length < map.min_len or length > map.max_len) return null
|
||||
|
||||
idx usize = usize(map.len_indexes[usize(length)])
|
||||
while idx < map.keys.len : idx += 1 {
|
||||
candidate :: map.keys[idx]
|
||||
if (candidate.len != key.len) return null
|
||||
if mem.eql(u8, candidate, key) return map.values[idx]
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import "@std/testing"
|
||||
|
||||
TokenKind :: enum {
|
||||
keyword_if
|
||||
keyword_else
|
||||
keyword_for
|
||||
keyword_return
|
||||
}
|
||||
|
||||
keywords StaticStringMap(TokenKind) = init([
|
||||
{"return", .keyword_return},
|
||||
{"if", .keyword_if},
|
||||
{"for", .keyword_for},
|
||||
{"else", .keyword_else},
|
||||
])
|
||||
|
||||
handles_length_bucket_lookups test {
|
||||
try testing.expect(keywords.keys.len == 4)
|
||||
try testing.expect(keywords.values.len == keywords.keys.len)
|
||||
try testing.expect(keywords.len_indexes.len == 7)
|
||||
try testing.expect_equal(some!(TokenKind.keyword_if), get(&keywords, "if"))
|
||||
try testing.expect_equal(some!(TokenKind.keyword_else), get(&keywords, "else"))
|
||||
try testing.expect_equal(some!(TokenKind.keyword_for), get(&keywords, "for"))
|
||||
try testing.expect_equal(some!(TokenKind.keyword_return), get(&keywords, "return"))
|
||||
try testing.expect_equal(null, get(&keywords, "no"))
|
||||
try testing.expect_equal(null, get(&keywords, "four"))
|
||||
try testing.expect_equal(null, get(&keywords, "longer-than-any-key"))
|
||||
}
|
||||
|
||||
handles_empty_maps test {
|
||||
empty StaticStringMap(TokenKind) = init([])
|
||||
try testing.expect(empty.keys.len == 0)
|
||||
try testing.expect(empty.values.len == 0)
|
||||
try testing.expect(empty.len_indexes.len == 0)
|
||||
try testing.expect_equal(null, get(&empty, "if"))
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import "io"
|
||||
import "enums"
|
||||
import "hashmap"
|
||||
import "arraylist"
|
||||
import "static_string_map"
|
||||
|
||||
Io :: alias io.Io
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
EnumMap :: alias enums.EnumMap
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
StringHashMap :: alias hashmap.StringHashMap
|
||||
StaticStringMap :: alias static_string_map.StaticStringMap
|
||||
@@ -6,14 +6,18 @@ Error :: enum {
|
||||
}
|
||||
|
||||
SourceLocation :: struct {
|
||||
file []u8
|
||||
line usize
|
||||
file []u8
|
||||
line usize
|
||||
column usize
|
||||
}
|
||||
|
||||
expect func(condition bool, location SourceLocation) void ! Error {
|
||||
if !condition {
|
||||
debug.print("{s}:{d}:{d}: expectation failed\n", {location.file, location.line, location.column})
|
||||
debug.print("{s}:{d}:{d}: expectation failed\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
@@ -26,23 +30,39 @@ expect_equal func($T type, expected, actual T, location SourceLocation) void ! E
|
||||
try expect_equal(expected_value, actual_value, location)
|
||||
return
|
||||
}
|
||||
debug.print("{s}:{d}:{d}: expected an optional value, found null\n", {location.file, location.line, location.column})
|
||||
debug.print("{s}:{d}:{d}: expected an optional value, found null\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
if actual |_| {
|
||||
debug.print("{s}:{d}:{d}: expected null, found an optional value\n", {location.file, location.line, location.column})
|
||||
debug.print("{s}:{d}:{d}: expected null, found an optional value\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
.slice: if !mem.eql(expected, actual) {
|
||||
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {location.file, location.line, location.column})
|
||||
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
else: {
|
||||
if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||
return .expectation_failed
|
||||
}
|
||||
else: if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
expected,
|
||||
actual,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,10 +73,10 @@ expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) voi
|
||||
|
||||
run func(name []u8, callback *func() void ! Error) bool {
|
||||
callback() catch |_| {
|
||||
debug.print("{s} [failed]\n", {name,})
|
||||
debug.print("{s}...[failed]\n", {name,})
|
||||
return false
|
||||
}
|
||||
debug.print("{s} [ok]\n", {name,})
|
||||
debug.print("{s}...[ok]\n", {name,})
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user