Merge remote-tracking branch 'offical/master'

This commit is contained in:
ed
2025-01-30 14:36:46 -05:00
126 changed files with 2481 additions and 1004 deletions
+4
View File
@@ -104,6 +104,10 @@ Answers to common questions about Odin.
Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections. Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections.
#### [Examples](https://github.com/odin-lang/examples)
Examples on how to write idiomatic Odin code. Shows how to accomplish specific tasks in Odin, as well as how to use packages from `core` and `vendor`.
#### [Odin Documentation](https://odin-lang.org/docs/) #### [Odin Documentation](https://odin-lang.org/docs/)
Documentation for the Odin language itself. Documentation for the Odin language itself.
+20 -5
View File
@@ -212,10 +212,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
case size == 0: case size == 0:
err = .Mode_Not_Implemented err = .Mode_Not_Implemented
return return
case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: case uintptr(old_data) & uintptr(alignment-1) == 0:
// shrink data in-place if size < old_size {
data = old_data[:size] // shrink data in-place
return data = old_data[:size]
return
}
if block := arena.curr_block; block != nil {
start := uint(uintptr(old_memory)) - uint(uintptr(block.base))
old_end := start + old_size
new_end := start + size
if start < old_end && old_end == block.used && new_end <= block.capacity {
// grow data in-place, adjusting next allocation
block.used = uint(new_end)
data = block.base[start:new_end]
return
}
}
} }
new_memory := arena_alloc(arena, size, alignment, location) or_return new_memory := arena_alloc(arena, size, alignment, location) or_return
@@ -284,9 +298,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
if block := arena.curr_block; block != nil { if block := arena.curr_block; block != nil {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := min(block.used-temp.used, block.capacity-block.used) amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero
} }
} }
+27 -10
View File
@@ -400,7 +400,7 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc
// This procedure returns the address of the just inserted value, and will // This procedure returns the address of the just inserted value, and will
// return 'nil' if there was no room to insert the entry // return 'nil' if there was no room to insert the entry
@(require_results) @(require_results)
map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { map_insert_hash_dynamic_with_key :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (key: uintptr, result: uintptr) {
h := h h := h
pos := map_desired_position(m^, h) pos := map_desired_position(m^, h)
distance := uintptr(0) distance := uintptr(0)
@@ -436,7 +436,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v)
hs[pos] = h hs[pos] = h
return result if result != 0 else v_dst if result == 0 {
key = k_dst
result = v_dst
}
return
} }
if map_hash_is_deleted(element_hash) { if map_hash_is_deleted(element_hash) {
@@ -444,13 +448,14 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
} }
if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance {
if result == 0 {
result = map_cell_index_dynamic(vs, info.vs, pos)
}
kp := map_cell_index_dynamic(ks, info.ks, pos) kp := map_cell_index_dynamic(ks, info.ks, pos)
vp := map_cell_index_dynamic(vs, info.vs, pos) vp := map_cell_index_dynamic(vs, info.vs, pos)
if result == 0 {
key = kp
result = vp
}
intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k)
intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k)
intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k)
@@ -491,7 +496,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v)
hs[pos] = h hs[pos] = h
return result if result != 0 else v_dst if result == 0 {
key = k_dst
result = v_dst
}
return
} }
k_src := map_cell_index_dynamic(ks, info.ks, la_pos) k_src := map_cell_index_dynamic(ks, info.ks, la_pos)
@@ -501,6 +510,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
if probe_distance < look_ahead { if probe_distance < look_ahead {
// probed can be made ideal while placing saved (ending condition) // probed can be made ideal while placing saved (ending condition)
if result == 0 { if result == 0 {
key = k_dst
result = v_dst result = v_dst
} }
intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k)
@@ -550,6 +560,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
} else { } else {
// place saved, save probed // place saved, save probed
if result == 0 { if result == 0 {
key = k_dst
result = v_dst result = v_dst
} }
intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k)
@@ -568,6 +579,12 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
} }
} }
@(require_results)
map_insert_hash_dynamic :: #force_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) {
_, result = map_insert_hash_dynamic_with_key(m, info, h, ik, iv)
return
}
@(require_results) @(require_results)
map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error {
log2_capacity := map_log2_cap(m^) log2_capacity := map_log2_cap(m^)
@@ -959,9 +976,9 @@ __dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_
hash = info.key_hasher(key, map_seed(m^)) hash = info.key_hasher(key, map_seed(m^))
} }
value_ptr = rawptr(map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(zero))) kp, vp := map_insert_hash_dynamic_with_key(m, info, hash, uintptr(key), uintptr(zero))
assert(value_ptr != nil) key_ptr = rawptr(kp)
key_ptr = rawptr(map_cell_index_dynamic(map_data(m^), info.ks, map_desired_position(m^, hash))) value_ptr = rawptr(vp)
m.len += 1 m.len += 1
just_inserted = true just_inserted = true
+1
View File
@@ -1,6 +1,7 @@
#+private #+private
package runtime package runtime
@(priority_index=-1e6)
foreign import "system:Foundation.framework" foreign import "system:Foundation.framework"
import "base:intrinsics" import "base:intrinsics"
+6 -6
View File
@@ -9,7 +9,7 @@ set -eu
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\"" CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14" CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm -lstdc++" LDFLAGS="$LDFLAGS -pthread -lm"
OS_ARCH="$(uname -m)" OS_ARCH="$(uname -m)"
OS_NAME="$(uname -s)" OS_NAME="$(uname -s)"
@@ -95,15 +95,15 @@ Darwin)
;; ;;
FreeBSD) FreeBSD)
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" LDFLAGS="$LDFLAGS -lstdc++ $($LLVM_CONFIG --libs core native --system-libs)"
;; ;;
NetBSD) NetBSD)
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" LDFLAGS="$LDFLAGS -lstdc++ $($LLVM_CONFIG --libs core native --system-libs)"
;; ;;
Linux) Linux)
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
# Copy libLLVM*.so into current directory for linking # Copy libLLVM*.so into current directory for linking
# NOTE: This is needed by the Linux release pipeline! # NOTE: This is needed by the Linux release pipeline!
# cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./
@@ -111,12 +111,12 @@ Linux)
;; ;;
OpenBSD) OpenBSD)
CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)" CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="$LDFLAGS -L/usr/local/lib -liconv" LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
;; ;;
Haiku) Haiku)
CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel" CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
LDFLAGS="$LDFLAGS -liconv" LDFLAGS="$LDFLAGS -lstdc++ -liconv"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
;; ;;
*) *)
+2
View File
@@ -114,3 +114,5 @@ CHAR_BIT :: 8
va_list :: struct #align(16) { va_list :: struct #align(16) {
_: [4096]u8, _: [4096]u8,
} }
FILE :: struct {}
+2 -1
View File
@@ -1,5 +1,6 @@
package libc package libc
import "core:c"
import "core:io" import "core:io"
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
@@ -15,7 +16,7 @@ when ODIN_OS == .Windows {
// 7.21 Input/output // 7.21 Input/output
FILE :: struct {} FILE :: c.FILE
Whence :: enum int { Whence :: enum int {
SET = SEEK_SET, SET = SEEK_SET,
+1 -2
View File
@@ -46,8 +46,7 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
cap = builtin.len(backing), cap = builtin.len(backing),
allocator = {procedure=runtime.nil_allocator_proc, data=nil}, allocator = {procedure=runtime.nil_allocator_proc, data=nil},
} }
q.len = len(backing) q.len = builtin.len(backing)
q.offset = len(backing)
return true return true
} }
+1
View File
@@ -5,6 +5,7 @@
#+build !netbsd #+build !netbsd
#+build !darwin #+build !darwin
#+build !js #+build !js
#+build !wasi
package crypto package crypto
HAS_RAND_BYTES :: false HAS_RAND_BYTES :: false
+13
View File
@@ -0,0 +1,13 @@
package crypto
import "core:fmt"
import "core:sys/wasm/wasi"
HAS_RAND_BYTES :: true
@(private)
_rand_bytes :: proc(dst: []byte) {
if err := wasi.random_get(dst); err != nil {
fmt.panicf("crypto: wasi.random_get failed: %v", err)
}
}
+4 -4
View File
@@ -118,10 +118,10 @@ _encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.al
@(optimization_mode="favor_size") @(optimization_mode="favor_size")
decode :: proc( decode :: proc(
data: string, data: string,
DEC_TBL := DEC_TABLE, DEC_TBL := DEC_TABLE,
validate: Validate_Proc = _validate_default, validate: Validate_Proc = _validate_default,
allocator := context.allocator) -> (out: []byte, err: Error) { allocator := context.allocator) -> (out: []byte, err: Error) {
if len(data) == 0 { if len(data) == 0 {
return nil, .None return nil, .None
} }
+26 -4
View File
@@ -314,7 +314,29 @@ assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_locati
p = runtime.default_assertion_failure_proc p = runtime.default_assertion_failure_proc
} }
message := tprintf(fmt, ..args) message := tprintf(fmt, ..args)
p("Runtime assertion", message, loc) p("runtime assertion", message, loc)
}
internal(loc, fmt, ..args)
}
}
// Runtime ensure with a formatted message
//
// Inputs:
// - condition: The boolean condition to be asserted
// - fmt: A format string with placeholders for the provided arguments
// - args: A variadic list of arguments to be formatted
// - loc: The location of the caller
//
ensuref :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) {
if !condition {
@(cold)
internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) {
p := context.assertion_failure_proc
if p == nil {
p = runtime.default_assertion_failure_proc
}
message := tprintf(fmt, ..args)
p("unsatisfied ensure", message, loc)
} }
internal(loc, fmt, ..args) internal(loc, fmt, ..args)
} }
@@ -332,7 +354,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
p = runtime.default_assertion_failure_proc p = runtime.default_assertion_failure_proc
} }
message := tprintf(fmt, ..args) message := tprintf(fmt, ..args)
p("Panic", message, loc) p("panic", message, loc)
} }
// Creates a formatted C string // Creates a formatted C string
@@ -1357,9 +1379,9 @@ _pad :: proc(fi: ^Info, s: string) {
if fi.minus { // right pad if fi.minus { // right pad
io.write_string(fi.writer, s, &fi.n) io.write_string(fi.writer, s, &fi.n)
fmt_write_padding(fi, width) fmt_write_padding(fi, width)
} else if !fi.space && s != "" && s[0] == '-' { } else if !fi.space && s != "" && (s[0] == '-' || s[0] == '+') {
// left pad accounting for zero pad of negative number // left pad accounting for zero pad of negative number
io.write_byte(fi.writer, '-', &fi.n) io.write_byte(fi.writer, s[0], &fi.n)
fmt_write_padding(fi, width) fmt_write_padding(fi, width)
io.write_string(fi.writer, s[1:], &fi.n) io.write_string(fi.writer, s[1:], &fi.n)
} else { // left pad } else { // left pad
+33 -2
View File
@@ -115,7 +115,7 @@ panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> !
} }
@(disabled=ODIN_DISABLE_ASSERT) @(disabled=ODIN_DISABLE_ASSERT)
assert :: proc(condition: bool, message := "", loc := #caller_location) { assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
if !condition { if !condition {
@(cold) @(cold)
internal :: proc(message: string, loc: runtime.Source_Code_Location) { internal :: proc(message: string, loc: runtime.Source_Code_Location) {
@@ -145,7 +145,38 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
} }
message := fmt.tprintf(fmt_str, ..args) message := fmt.tprintf(fmt_str, ..args)
log(.Fatal, message, location=loc) log(.Fatal, message, location=loc)
p("Runtime assertion", message, loc) p("runtime assertion", message, loc)
}
internal(loc, fmt_str, ..args)
}
}
ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
if !condition {
@(cold)
internal :: proc(message: string, loc: runtime.Source_Code_Location) {
p := context.assertion_failure_proc
if p == nil {
p = runtime.default_assertion_failure_proc
}
log(.Fatal, message, location=loc)
p("unsatisfied ensure", message, loc)
}
internal(message, loc)
}
}
ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) {
if !condition {
@(cold)
internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) {
p := context.assertion_failure_proc
if p == nil {
p = runtime.default_assertion_failure_proc
}
message := fmt.tprintf(fmt_str, ..args)
log(.Fatal, message, location=loc)
p("unsatisfied ensure", message, loc)
} }
internal(loc, fmt_str, ..args) internal(loc, fmt_str, ..args)
} }
+6 -6
View File
@@ -1207,8 +1207,8 @@ matrix2_inverse_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) #no
d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0]
id := 1.0/d id := 1.0/d
c[0, 0] = +m[1, 1] * id c[0, 0] = +m[1, 1] * id
c[0, 1] = -m[1, 0] * id c[0, 1] = -m[0, 1] * id
c[1, 0] = -m[0, 1] * id c[1, 0] = -m[1, 0] * id
c[1, 1] = +m[0, 0] * id c[1, 1] = +m[0, 0] * id
return c return c
} }
@@ -1217,8 +1217,8 @@ matrix2_inverse_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) #no
d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0]
id := 1.0/d id := 1.0/d
c[0, 0] = +m[1, 1] * id c[0, 0] = +m[1, 1] * id
c[0, 1] = -m[1, 0] * id c[0, 1] = -m[0, 1] * id
c[1, 0] = -m[0, 1] * id c[1, 0] = -m[1, 0] * id
c[1, 1] = +m[0, 0] * id c[1, 1] = +m[0, 0] * id
return c return c
} }
@@ -1227,8 +1227,8 @@ matrix2_inverse_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) #no
d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0]
id := 1.0/d id := 1.0/d
c[0, 0] = +m[1, 1] * id c[0, 0] = +m[1, 1] * id
c[0, 1] = -m[1, 0] * id c[0, 1] = -m[0, 1] * id
c[1, 0] = -m[0, 1] * id c[1, 0] = -m[1, 0] * id
c[1, 1] = +m[0, 0] * id c[1, 1] = +m[0, 0] * id
return c return c
} }
+1
View File
@@ -16,6 +16,7 @@ Generator_Query_Info :: runtime.Random_Generator_Query_Info
Default_Random_State :: runtime.Default_Random_State Default_Random_State :: runtime.Default_Random_State
default_random_generator :: runtime.default_random_generator default_random_generator :: runtime.default_random_generator
@(require_results)
create :: proc(seed: u64) -> (state: Default_Random_State) { create :: proc(seed: u64) -> (state: Default_Random_State) {
seed := seed seed := seed
runtime.default_random_generator(&state) runtime.default_random_generator(&state)
+94 -3
View File
@@ -785,6 +785,27 @@ delete_map :: proc(
return runtime.delete_map(m, loc) return runtime.delete_map(m, loc)
} }
/*
Free an SoA slice.
*/
delete_soa_slice :: proc(
array: $T/#soa[]$E,
allocator := context.allocator,
loc := #caller_location,
) -> Allocator_Error {
return runtime.delete_soa_slice(array, allocator, loc)
}
/*
Free an SoA dynamic array.
*/
delete_soa_dynamic_array :: proc(
array: $T/#soa[dynamic]$E,
loc := #caller_location,
) -> Allocator_Error {
return runtime.delete_soa_dynamic_array(array, loc)
}
/* /*
Free. Free.
*/ */
@@ -794,6 +815,8 @@ delete :: proc{
delete_dynamic_array, delete_dynamic_array,
delete_slice, delete_slice,
delete_map, delete_map,
delete_soa_slice,
delete_soa_dynamic_array,
} }
/* /*
@@ -900,8 +923,7 @@ make_dynamic_array :: proc(
Allocate a dynamic array with initial length. Allocate a dynamic array with initial length.
This procedure creates a dynamic array of type `T`, with `allocator` as its This procedure creates a dynamic array of type `T`, with `allocator` as its
backing allocator, and initial capacity of `0`, and initial length specified by backing allocator, and initial capacity and length specified by `len`.
`len`.
*/ */
@(require_results) @(require_results)
make_dynamic_array_len :: proc( make_dynamic_array_len :: proc(
@@ -910,7 +932,7 @@ make_dynamic_array_len :: proc(
allocator := context.allocator, allocator := context.allocator,
loc := #caller_location, loc := #caller_location,
) -> (T, Allocator_Error) { ) -> (T, Allocator_Error) {
return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) return runtime.make_dynamic_array_len(T, len, allocator, loc)
} }
/* /*
@@ -964,6 +986,71 @@ make_multi_pointer :: proc(
return runtime.make_multi_pointer(T, len, allocator, loc) return runtime.make_multi_pointer(T, len, allocator, loc)
} }
/*
Allocate an SoA slice.
This procedure allocates an SoA slice of type `T` with length `len`, from an
allocator specified by `allocator`, and returns the allocated SoA slice.
*/
@(require_results)
make_soa_slice :: proc(
$T: typeid/#soa[]$E,
#any_int len: int,
allocator := context.allocator,
loc := #caller_location
) -> (array: T, err: Allocator_Error) {
return runtime.make_soa_slice(T, len, allocator, loc)
}
/*
Allocate an SoA dynamic array.
This procedure creates an SoA dynamic array of type `T`, with `allocator` as
its backing allocator, and initial length and capacity of `0`.
*/
@(require_results)
make_soa_dynamic_array :: proc(
$T: typeid/#soa[dynamic]$E,
allocator := context.allocator,
loc := #caller_location
) -> (array: T, err: Allocator_Error) {
return runtime.make_soa_dynamic_array(T, allocator, loc)
}
/*
Allocate an SoA dynamic array with initial length.
This procedure creates an SoA dynamic array of type `T`, with `allocator` as its
backing allocator, and initial capacity and length specified by `len`.
*/
@(require_results)
make_soa_dynamic_array_len :: proc(
$T: typeid/#soa[dynamic]$E,
#any_int len: int,
allocator := context.allocator,
loc := #caller_location
) -> (array: T, err: Allocator_Error) {
return runtime.make_soa_dynamic_array_len(T, len, allocator, loc)
}
/*
Allocate an SoA dynamic array with initial length and capacity.
This procedure creates an SoA dynamic array of type `T`, with `allocator` as its
backing allocator, and initial capacity specified by `cap`, and initial length
specified by `len`.
*/
@(require_results)
make_soa_dynamic_array_len_cap :: proc(
$T: typeid/#soa[dynamic]$E,
#any_int len: int,
#any_int cap: int,
allocator := context.allocator,
loc := #caller_location
) -> (array: T, err: Allocator_Error) {
return runtime.make_soa_dynamic_array_len_cap(T, len, cap, allocator, loc)
}
/* /*
Allocate. Allocate.
*/ */
@@ -974,6 +1061,10 @@ make :: proc{
make_dynamic_array_len_cap, make_dynamic_array_len_cap,
make_map, make_map,
make_multi_pointer, make_multi_pointer,
make_soa_slice,
make_soa_dynamic_array,
make_soa_dynamic_array_len,
make_soa_dynamic_array_len_cap,
} }
/* /*
+20 -5
View File
@@ -328,10 +328,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
case size == 0: case size == 0:
err = .Mode_Not_Implemented err = .Mode_Not_Implemented
return return
case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: case uintptr(old_data) & uintptr(alignment-1) == 0:
// shrink data in-place if size < old_size {
data = old_data[:size] // shrink data in-place
return data = old_data[:size]
return
}
if block := arena.curr_block; block != nil {
start := uint(uintptr(old_memory)) - uint(uintptr(block.base))
old_end := start + old_size
new_end := start + size
if start < old_end && old_end == block.used && new_end <= block.reserved {
// grow data in-place, adjusting next allocation
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
data = block.base[start:new_end]
return
}
}
} }
new_memory := arena_alloc(arena, size, alignment, location) or_return new_memory := arena_alloc(arena, size, alignment, location) or_return
@@ -402,9 +416,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
if block := arena.curr_block; block != nil { if block := arena.curr_block; block != nil {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := min(block.used-temp.used, block.reserved-block.used) amount_to_zero := block.used-temp.used
mem.zero_slice(block.base[temp.used:][:amount_to_zero]) mem.zero_slice(block.base[temp.used:][:amount_to_zero])
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero
} }
} }
+4
View File
@@ -95,6 +95,7 @@ Resolve_Error :: enum u32 {
} }
DNS_Error :: enum u32 { DNS_Error :: enum u32 {
None = 0,
Invalid_Hostname_Error = 1, Invalid_Hostname_Error = 1,
Invalid_Hosts_Config_Error, Invalid_Hosts_Config_Error,
Invalid_Resolv_Config_Error, Invalid_Resolv_Config_Error,
@@ -147,6 +148,9 @@ IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}
IP4_Any := IP4_Address{} IP4_Any := IP4_Address{}
IP6_Any := IP6_Address{} IP6_Any := IP6_Address{}
IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353}
IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}
Endpoint :: struct { Endpoint :: struct {
address: Address, address: Address,
port: int, port: int,
+16 -8
View File
@@ -132,7 +132,14 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net
return return
} }
case Host: case Host:
recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) recs: []DNS_Record
if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") {
recs, _ = get_dns_records_from_nameservers(t.hostname, .IP4, {IP4_mDNS_Broadcast}, nil, context.temp_allocator)
} else {
recs, _ = get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator)
}
if len(recs) == 0 { if len(recs) == 0 {
err = .Unable_To_Resolve err = .Unable_To_Resolve
return return
@@ -159,7 +166,14 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
return t, nil return t, nil
} }
case Host: case Host:
recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) recs: []DNS_Record
if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") {
recs, _ = get_dns_records_from_nameservers(t.hostname, .IP6, {IP6_mDNS_Broadcast}, nil, context.temp_allocator)
} else {
recs, _ = get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator)
}
if len(recs) == 0 { if len(recs) == 0 {
err = .Unable_To_Resolve err = .Unable_To_Resolve
return return
@@ -255,12 +269,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type
return nil, .Connection_Error return nil, .Connection_Error
} }
// recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:])
// if recv_err == UDP_Recv_Error.Timeout {
// continue
// } else if recv_err != nil {
// continue
// }
recv_sz, _ := recv_udp(conn, dns_response_buf[:]) or_continue recv_sz, _ := recv_udp(conn, dns_response_buf[:]) or_continue
if recv_sz == 0 { if recv_sz == 0 {
continue continue
+6 -1
View File
@@ -29,9 +29,14 @@ import win "core:sys/windows"
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
context.allocator = allocator context.allocator = allocator
options := win.DNS_QUERY_OPTIONS{}
if strings.has_suffix(hostname, ".local") {
options = {.MULTICAST_ONLY, .MULTICAST_WAIT} // 0x00020500
}
host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator)
rec: ^win.DNS_RECORD rec: ^win.DNS_RECORD
res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil) res := win.DnsQuery_UTF8(host_cstr, u16(type), options, nil, &rec, nil)
switch u32(res) { switch u32(res) {
case 0: case 0:
+3 -1
View File
@@ -35,6 +35,7 @@ Socket_Option :: enum c.int {
Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF),
Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO),
Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO),
Broadcast = c.int(linux.Socket_Option.BROADCAST),
} }
// Wrappers and unwrappers for system-native types // Wrappers and unwrappers for system-native types
@@ -337,7 +338,8 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
.Reuse_Address, .Reuse_Address,
.Keep_Alive, .Keep_Alive,
.Out_Of_Bounds_Data_Inline, .Out_Of_Bounds_Data_Inline,
.TCP_Nodelay: .TCP_Nodelay,
.Broadcast:
// TODO: verify whether these are options or not on Linux // TODO: verify whether these are options or not on Linux
// .Broadcast, <-- yes // .Broadcast, <-- yes
// .Conditional_Accept, // .Conditional_Accept,
+84 -3
View File
@@ -1,20 +1,101 @@
#+private #+private
package os2 package os2
import "core:sys/linux"
Read_Directory_Iterator_Impl :: struct { Read_Directory_Iterator_Impl :: struct {
prev_fi: File_Info,
dirent_backing: []u8,
dirent_buflen: int,
dirent_off: int,
index: int,
} }
@(require_results) @(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
scan_entries :: proc(dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) {
for d in linux.dirent_iterate_buf(entries, offset) {
file_name = linux.dirent_name(d)
if file_name == "." || file_name == ".." {
continue
}
file_name_cstr := cstring(raw_data(file_name))
entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH})
if errno == .NONE {
return entry_fd, file_name
}
}
return -1, ""
}
index = it.impl.index
it.impl.index += 1
dfd := linux.Fd(_fd(it.f))
entries := it.impl.dirent_backing[:it.impl.dirent_buflen]
entry_fd, file_name := scan_entries(dfd, entries, &it.impl.dirent_off)
for entry_fd == -1 {
if len(it.impl.dirent_backing) == 0 {
it.impl.dirent_backing = make([]u8, 512, file_allocator())
}
loop: for {
buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:])
#partial switch errno {
case .EINVAL:
delete(it.impl.dirent_backing, file_allocator())
n := len(it.impl.dirent_backing) * 2
it.impl.dirent_backing = make([]u8, n, file_allocator())
continue
case .NONE:
if buflen == 0 {
return
}
it.impl.dirent_off = 0
it.impl.dirent_buflen = buflen
entries = it.impl.dirent_backing[:buflen]
break loop
case: // error
return
}
}
entry_fd, file_name = scan_entries(dfd, entries, &it.impl.dirent_off)
}
defer linux.close(entry_fd)
file_info_delete(it.impl.prev_fi, file_allocator())
fi, _ = _fstat_internal(entry_fd, file_allocator())
it.impl.prev_fi = fi
ok = true
return return
} }
@(require_results) @(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
return {}, .Unsupported if f == nil || f.impl == nil {
return {}, .Invalid_File
}
stat: linux.Stat
errno := linux.fstat(linux.Fd(fd(f)), &stat)
if errno != .NONE {
return {}, _get_platform_error(errno)
}
if (stat.mode & linux.S_IFMT) != linux.S_IFDIR {
return {}, .Invalid_Dir
}
return {f = f}, nil
} }
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it == nil {
return
}
delete(it.impl.dirent_backing, file_allocator())
file_info_delete(it.impl.prev_fi, file_allocator())
} }
+6 -3
View File
@@ -39,8 +39,11 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
} }
n := len(fimpl.name)+1 n := len(fimpl.name)+1
non_zero_resize(&it.impl.fullpath, n+len(sname)) if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil {
n += copy(it.impl.fullpath[n:], sname) // Can't really tell caller we had an error, sad.
return
}
copy(it.impl.fullpath[n:], sname)
fi = internal_stat(stat, string(it.impl.fullpath[:])) fi = internal_stat(stat, string(it.impl.fullpath[:]))
ok = true ok = true
@@ -60,7 +63,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
iter.f = f iter.f = f
iter.impl.idx = 0 iter.impl.idx = 0
iter.impl.fullpath.allocator = file_allocator() iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return
append(&iter.impl.fullpath, impl.name) append(&iter.impl.fullpath, impl.name)
append(&iter.impl.fullpath, "/") append(&iter.impl.fullpath, "/")
defer if err != nil { delete(iter.impl.fullpath) } defer if err != nil { delete(iter.impl.fullpath) }
+110
View File
@@ -0,0 +1,110 @@
#+private
package os2
import "base:intrinsics"
import "core:sys/wasm/wasi"
Read_Directory_Iterator_Impl :: struct {
fullpath: [dynamic]byte,
buf: []byte,
off: int,
idx: int,
}
@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
fimpl := (^File_Impl)(it.f.impl)
buf := it.impl.buf[it.impl.off:]
index = it.impl.idx
it.impl.idx += 1
for {
if len(buf) < size_of(wasi.dirent_t) {
return
}
entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf)))
buf = buf[size_of(wasi.dirent_t):]
if len(buf) < int(entry.d_namlen) {
// shouldn't be possible.
return
}
name := string(buf[:entry.d_namlen])
buf = buf[entry.d_namlen:]
it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen)
if name == "." || name == ".." {
continue
}
n := len(fimpl.name)+1
if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil {
// Can't really tell caller we had an error, sad.
return
}
copy(it.impl.fullpath[n:], name)
stat, err := wasi.path_filestat_get(__fd(it.f), {}, name)
if err != nil {
// Can't stat, fill what we have from dirent.
stat = {
ino = entry.d_ino,
filetype = entry.d_type,
}
}
fi = internal_stat(stat, string(it.impl.fullpath[:]))
ok = true
return
}
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) {
if f == nil || f.impl == nil {
err = .Invalid_File
return
}
impl := (^File_Impl)(f.impl)
iter.f = f
buf: [dynamic]byte
buf.allocator = file_allocator()
defer if err != nil { delete(buf) }
// NOTE: this is very grug.
for {
non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2) or_return
n, _err := wasi.fd_readdir(__fd(f), buf[:], 0)
if _err != nil {
err = _get_platform_error(_err)
return
}
if n < len(buf) {
non_zero_resize(&buf, n)
break
}
assert(n == len(buf))
}
iter.impl.buf = buf[:]
iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return
append(&iter.impl.fullpath, impl.name)
append(&iter.impl.fullpath, "/")
return
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
delete(it.impl.buf, file_allocator())
delete(it.impl.fullpath)
it^ = {}
}
+5 -5
View File
@@ -76,7 +76,7 @@ _set_env :: proc(key, v_new: string) -> bool {
// wasn't in the environment in the first place. // wasn't in the environment in the first place.
k_addr, v_addr := _kv_addr_from_val(v_curr, key) k_addr, v_addr := _kv_addr_from_val(v_curr, key)
if len(v_new) > len(v_curr) { if len(v_new) > len(v_curr) {
k_addr = ([^]u8)(heap_resize(k_addr, kv_size)) k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size))
if k_addr == nil { if k_addr == nil {
return false return false
} }
@@ -90,7 +90,7 @@ _set_env :: proc(key, v_new: string) -> bool {
} }
} }
k_addr := ([^]u8)(heap_alloc(kv_size)) k_addr := ([^]u8)(runtime.heap_alloc(kv_size))
if k_addr == nil { if k_addr == nil {
return false return false
} }
@@ -129,7 +129,7 @@ _unset_env :: proc(key: string) -> bool {
// if we got this far, the envrionment variable // if we got this far, the envrionment variable
// existed AND was allocated by us. // existed AND was allocated by us.
k_addr, _ := _kv_addr_from_val(v, key) k_addr, _ := _kv_addr_from_val(v, key)
heap_free(k_addr) runtime.heap_free(k_addr)
return true return true
} }
@@ -139,7 +139,7 @@ _clear_env :: proc() {
for kv in _env { for kv in _env {
if !_is_in_org_env(kv) { if !_is_in_org_env(kv) {
heap_free(raw_data(kv)) runtime.heap_free(raw_data(kv))
} }
} }
clear(&_env) clear(&_env)
@@ -193,7 +193,7 @@ _build_env :: proc() {
return return
} }
_env = make(type_of(_env), heap_allocator()) _env = make(type_of(_env), runtime.heap_allocator())
cstring_env := _get_original_env() cstring_env := _get_original_env()
_org_env_begin = uintptr(rawptr(cstring_env[0])) _org_env_begin = uintptr(rawptr(cstring_env[0]))
for i := 0; cstring_env[i] != nil; i += 1 { for i := 0; cstring_env[i] != nil; i += 1 {
+186
View File
@@ -0,0 +1,186 @@
#+private
package os2
import "base:runtime"
import "core:strings"
import "core:sync"
import "core:sys/wasm/wasi"
g_env: map[string]string
g_env_buf: []byte
g_env_mutex: sync.RW_Mutex
g_env_error: Error
g_env_built: bool
build_env :: proc() -> (err: Error) {
if g_env_built || g_env_error != nil {
return g_env_error
}
sync.guard(&g_env_mutex)
if g_env_built || g_env_error != nil {
return g_env_error
}
defer if err != nil {
g_env_error = err
}
num_envs, size_of_envs, _err := wasi.environ_sizes_get()
if _err != nil {
return _get_platform_error(_err)
}
g_env = make(map[string]string, num_envs, file_allocator()) or_return
defer if err != nil { delete(g_env) }
g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
defer if err != nil { delete(g_env_buf, file_allocator()) }
TEMP_ALLOCATOR_GUARD()
envs := make([]cstring, num_envs, temp_allocator()) or_return
_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
if _err != nil {
return _get_platform_error(_err)
}
for env in envs {
key, _, value := strings.partition(string(env), "=")
g_env[key] = value
}
g_env_built = true
return
}
delete_string_if_not_original :: proc(str: string) {
start := uintptr(raw_data(g_env_buf))
end := start + uintptr(len(g_env_buf))
ptr := uintptr(raw_data(str))
if ptr < start || ptr > end {
delete(str, file_allocator())
}
}
@(require_results)
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if err := build_env(); err != nil {
return
}
sync.shared_guard(&g_env_mutex)
value = g_env[key] or_return
value, _ = clone_string(value, allocator)
return
}
@(require_results)
_set_env :: proc(key, value: string) -> bool {
if err := build_env(); err != nil {
return false
}
sync.guard(&g_env_mutex)
key_ptr, value_ptr, just_inserted, err := map_entry(&g_env, key)
if err != nil {
return false
}
alloc_err: runtime.Allocator_Error
if just_inserted {
key_ptr^, alloc_err = clone_string(key, file_allocator())
if alloc_err != nil {
delete_key(&g_env, key)
return false
}
value_ptr^, alloc_err = clone_string(value, file_allocator())
if alloc_err != nil {
delete_key(&g_env, key)
delete(key_ptr^, file_allocator())
return false
}
return true
}
delete_string_if_not_original(value_ptr^)
value_ptr^, alloc_err = clone_string(value, file_allocator())
if alloc_err != nil {
delete_key(&g_env, key)
return false
}
return true
}
@(require_results)
_unset_env :: proc(key: string) -> bool {
if err := build_env(); err != nil {
return false
}
sync.guard(&g_env_mutex)
dkey, dval := delete_key(&g_env, key)
delete_string_if_not_original(dkey)
delete_string_if_not_original(dval)
return true
}
_clear_env :: proc() {
sync.guard(&g_env_mutex)
for k, v in g_env {
delete_string_if_not_original(k)
delete_string_if_not_original(v)
}
delete(g_env_buf, file_allocator())
g_env_buf = {}
clear(&g_env)
g_env_built = true
}
@(require_results)
_environ :: proc(allocator: runtime.Allocator) -> []string {
if err := build_env(); err != nil {
return nil
}
sync.shared_guard(&g_env_mutex)
envs, alloc_err := make([]string, len(g_env), allocator)
if alloc_err != nil {
return nil
}
defer if alloc_err != nil {
for env in envs {
delete(env, allocator)
}
delete(envs, allocator)
}
i: int
for k, v in g_env {
defer i += 1
envs[i], alloc_err = concatenate({k, "=", v}, allocator)
if alloc_err != nil {
return nil
}
}
return envs
}
+11 -2
View File
@@ -10,8 +10,12 @@ _error_string :: proc(errno: i32) -> string {
return string(posix.strerror(posix.Errno(errno))) return string(posix.strerror(posix.Errno(errno)))
} }
_get_platform_error :: proc() -> Error { _get_platform_error_from_errno :: proc() -> Error {
#partial switch errno := posix.errno(); errno { return _get_platform_error_existing(posix.errno())
}
_get_platform_error_existing :: proc(errno: posix.Errno) -> Error {
#partial switch errno {
case .EPERM: case .EPERM:
return .Permission_Denied return .Permission_Denied
case .EEXIST: case .EEXIST:
@@ -32,3 +36,8 @@ _get_platform_error :: proc() -> Error {
return Platform_Error(errno) return Platform_Error(errno)
} }
} }
_get_platform_error :: proc{
_get_platform_error_existing,
_get_platform_error_from_errno,
}
+47
View File
@@ -0,0 +1,47 @@
#+private
package os2
import "base:runtime"
import "core:slice"
import "core:sys/wasm/wasi"
_Platform_Error :: wasi.errno_t
_error_string :: proc(errno: i32) -> string {
e := wasi.errno_t(errno)
if e == .NONE {
return ""
}
err := runtime.Type_Info_Enum_Value(e)
ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum)
if idx, ok := slice.binary_search(ti.values, err); ok {
return ti.names[idx]
}
return "<unknown platform error>"
}
_get_platform_error :: proc(errno: wasi.errno_t) -> Error {
#partial switch errno {
case .PERM:
return .Permission_Denied
case .EXIST:
return .Exist
case .NOENT:
return .Not_Exist
case .TIMEDOUT:
return .Timeout
case .PIPE:
return .Broken_Pipe
case .BADF:
return .Invalid_File
case .NOMEM:
return .Out_Of_Memory
case .NOSYS:
return .Unsupported
case:
return Platform_Error(errno)
}
}
+41 -20
View File
@@ -7,6 +7,13 @@ import "core:time"
import "core:sync" import "core:sync"
import "core:sys/linux" import "core:sys/linux"
// Most implementations will EINVAL at some point when doing big writes.
// In practice a read/write call would probably never read/write these big buffers all at once,
// which is why the number of bytes is returned and why there are procs that will call this in a
// loop for you.
// We set a max of 1GB to keep alignment and to be safe.
MAX_RW :: 1 << 30
File_Impl :: struct { File_Impl :: struct {
file: File, file: File,
name: string, name: string,
@@ -179,10 +186,11 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er
} }
_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
if len(p) == 0 { if len(p) <= 0 {
return 0, nil return 0, nil
} }
n, errno := linux.read(f.fd, p[:])
n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)])
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
@@ -190,13 +198,13 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
} }
_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
if len(p) == 0 { if len(p) <= 0 {
return 0, nil return 0, nil
} }
if offset < 0 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
n, errno := linux.pread(f.fd, p[:], offset) n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset)
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
@@ -206,29 +214,42 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
return i64(n), nil return i64(n), nil
} }
_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { _write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) {
if len(p) == 0 { p := p
return 0, nil for len(p) > 0 {
n, errno := linux.write(f.fd, p[:min(len(p), MAX_RW)])
if errno != .NONE {
err = _get_platform_error(errno)
return
}
p = p[n:]
nt += i64(n)
} }
n, errno := linux.write(f.fd, p[:])
if errno != .NONE { return
return -1, _get_platform_error(errno)
}
return i64(n), nil
} }
_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) {
if len(p) == 0 {
return 0, nil
}
if offset < 0 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
n, errno := linux.pwrite(f.fd, p[:], offset)
if errno != .NONE { p := p
return -1, _get_platform_error(errno) offset := offset
for len(p) > 0 {
n, errno := linux.pwrite(f.fd, p[:min(len(p), MAX_RW)], offset)
if errno != .NONE {
err = _get_platform_error(errno)
return
}
p = p[n:]
nt += i64(n)
offset += i64(n)
} }
return i64(n), nil
return
} }
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
+534
View File
@@ -0,0 +1,534 @@
#+private
package os2
import "base:runtime"
import "core:io"
import "core:sys/wasm/wasi"
import "core:time"
// NOTE: Don't know if there is a max in wasi.
MAX_RW :: 1 << 30
File_Impl :: struct {
file: File,
name: string,
fd: wasi.fd_t,
allocator: runtime.Allocator,
}
// WASI works with "preopened" directories, the environment retrieves directories
// (for example with `wasmtime --dir=. module.wasm`) and those given directories
// are the only ones accessible by the application.
//
// So in order to facilitate the `os` API (absolute paths etc.) we keep a list
// of the given directories and match them when needed (notably `os.open`).
Preopen :: struct {
fd: wasi.fd_t,
prefix: string,
}
preopens: []Preopen
@(init)
init_std_files :: proc() {
new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File {
impl.file.impl = impl
impl.allocator = runtime.nil_allocator()
impl.fd = fd
impl.name = string(name)
impl.file.stream = {
data = impl,
procedure = _file_stream_proc,
}
impl.file.fstat = _fstat
return &impl.file
}
@(static) files: [3]File_Impl
stdin = new_std(&files[0], 0, "/dev/stdin")
stdout = new_std(&files[1], 1, "/dev/stdout")
stderr = new_std(&files[2], 2, "/dev/stderr")
}
@(init)
init_preopens :: proc() {
strip_prefixes :: proc(path: string) -> string {
path := path
loop: for len(path) > 0 {
switch {
case path[0] == '/':
path = path[1:]
case len(path) > 2 && path[0] == '.' && path[1] == '/':
path = path[2:]
case len(path) == 1 && path[0] == '.':
path = path[1:]
case:
break loop
}
}
return path
}
n: int
n_loop: for fd := wasi.fd_t(3); ; fd += 1 {
_, err := wasi.fd_prestat_get(fd)
#partial switch err {
case .BADF: break n_loop
case .SUCCESS: n += 1
case:
print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get")
break n_loop
}
}
alloc_err: runtime.Allocator_Error
preopens, alloc_err = make([]Preopen, n, file_allocator())
if alloc_err != nil {
print_error(stderr, alloc_err, "could not allocate memory for wasi preopens")
return
}
loop: for &preopen, i in preopens {
fd := wasi.fd_t(3 + i)
desc, err := wasi.fd_prestat_get(fd)
assert(err == .SUCCESS)
switch desc.tag {
case .DIR:
buf: []byte
buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator())
if alloc_err != nil {
print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name")
continue loop
}
if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS {
print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name")
continue loop
}
preopen.fd = fd
preopen.prefix = strip_prefixes(string(buf))
}
}
}
@(require_results)
match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
@(require_results)
prefix_matches :: proc(prefix, path: string) -> bool {
// Empty is valid for any relative path.
if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
return true
}
if len(path) < len(prefix) {
return false
}
if path[:len(prefix)] != prefix {
return false
}
// Only match on full components.
i := len(prefix)
for i > 0 && prefix[i-1] == '/' {
i -= 1
}
return path[i] == '/'
}
path := path
if path == "" {
return 0, "", false
}
for len(path) > 0 && path[0] == '/' {
path = path[1:]
}
match: Preopen
#reverse for preopen in preopens {
if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) {
match = preopen
}
}
if match.fd == 0 {
return 0, "", false
}
relative := path[len(match.prefix):]
for len(relative) > 0 && relative[0] == '/' {
relative = relative[1:]
}
if len(relative) == 0 {
relative = "."
}
return match.fd, relative, true
}
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
dir_fd, relative, ok := match_preopen(name)
if !ok {
return nil, .Invalid_Path
}
oflags: wasi.oflags_t
if .Create in flags { oflags += {.CREATE} }
if .Excl in flags { oflags += {.EXCL} }
if .Trunc in flags { oflags += {.TRUNC} }
fdflags: wasi.fdflags_t
if .Append in flags { fdflags += {.APPEND} }
if .Sync in flags { fdflags += {.SYNC} }
// NOTE: rights are adjusted to what this package's functions might want to call.
rights: wasi.rights_t
if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} }
if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} }
fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
if fderr != nil {
err = _get_platform_error(fderr)
return
}
return _new_file(uintptr(fd), name, file_allocator())
}
_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) {
if name == "" {
err = .Invalid_Path
return
}
impl := new(File_Impl, allocator) or_return
defer if err != nil { free(impl, allocator) }
impl.allocator = allocator
// NOTE: wasi doesn't really do full paths afact.
impl.name = clone_string(name, allocator) or_return
impl.fd = wasi.fd_t(handle)
impl.file.impl = impl
impl.file.stream = {
data = impl,
procedure = _file_stream_proc,
}
impl.file.fstat = _fstat
return &impl.file, nil
}
_close :: proc(f: ^File_Impl) -> (err: Error) {
if errno := wasi.fd_close(f.fd); errno != nil {
err = _get_platform_error(errno)
}
delete(f.name, f.allocator)
free(f, f.allocator)
return
}
_fd :: proc(f: ^File) -> uintptr {
return uintptr(__fd(f))
}
__fd :: proc(f: ^File) -> wasi.fd_t {
if f != nil && f.impl != nil {
return (^File_Impl)(f.impl).fd
}
return -1
}
_name :: proc(f: ^File) -> string {
if f != nil && f.impl != nil {
return (^File_Impl)(f.impl).name
}
return ""
}
_sync :: proc(f: ^File) -> Error {
return _get_platform_error(wasi.fd_sync(__fd(f)))
}
_truncate :: proc(f: ^File, size: i64) -> Error {
return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size)))
}
_remove :: proc(name: string) -> Error {
dir_fd, relative, ok := match_preopen(name)
if !ok {
return .Invalid_Path
}
err := wasi.path_remove_directory(dir_fd, relative)
if err == .NOTDIR {
err = wasi.path_unlink_file(dir_fd, relative)
}
return _get_platform_error(err)
}
_rename :: proc(old_path, new_path: string) -> Error {
src_dir_fd, src_relative, src_ok := match_preopen(old_path)
if !src_ok {
return .Invalid_Path
}
new_dir_fd, new_relative, new_ok := match_preopen(new_path)
if !new_ok {
return .Invalid_Path
}
return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative))
}
_link :: proc(old_name, new_name: string) -> Error {
src_dir_fd, src_relative, src_ok := match_preopen(old_name)
if !src_ok {
return .Invalid_Path
}
new_dir_fd, new_relative, new_ok := match_preopen(new_name)
if !new_ok {
return .Invalid_Path
}
return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative))
}
_symlink :: proc(old_name, new_name: string) -> Error {
src_dir_fd, src_relative, src_ok := match_preopen(old_name)
if !src_ok {
return .Invalid_Path
}
new_dir_fd, new_relative, new_ok := match_preopen(new_name)
if !new_ok {
return .Invalid_Path
}
if src_dir_fd != new_dir_fd {
return .Invalid_Path
}
return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative))
}
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
dir_fd, relative, ok := match_preopen(name)
if !ok {
return "", .Invalid_Path
}
n, _err := wasi.path_readlink(dir_fd, relative, nil)
if _err != nil {
err = _get_platform_error(_err)
return
}
buf := make([]byte, n, allocator) or_return
_, _err = wasi.path_readlink(dir_fd, relative, buf)
s = string(buf)
err = _get_platform_error(_err)
return
}
_chdir :: proc(name: string) -> Error {
return .Unsupported
}
_fchdir :: proc(f: ^File) -> Error {
return .Unsupported
}
_fchmod :: proc(f: ^File, mode: int) -> Error {
return .Unsupported
}
_chmod :: proc(name: string, mode: int) -> Error {
return .Unsupported
}
_fchown :: proc(f: ^File, uid, gid: int) -> Error {
return .Unsupported
}
_chown :: proc(name: string, uid, gid: int) -> Error {
return .Unsupported
}
_lchown :: proc(name: string, uid, gid: int) -> Error {
return .Unsupported
}
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
dir_fd, relative, ok := match_preopen(name)
if !ok {
return .Invalid_Path
}
_atime := wasi.timestamp_t(atime._nsec)
_mtime := wasi.timestamp_t(mtime._nsec)
return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM}))
}
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
_atime := wasi.timestamp_t(atime._nsec)
_mtime := wasi.timestamp_t(mtime._nsec)
return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM}))
}
_exists :: proc(path: string) -> bool {
dir_fd, relative, ok := match_preopen(path)
if !ok {
return false
}
_, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative)
if err != nil {
return false
}
return true
}
_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
f := (^File_Impl)(stream_data)
fd := f.fd
switch mode {
case .Read:
if len(p) <= 0 {
return
}
to_read := min(len(p), MAX_RW)
_n, _err := wasi.fd_read(fd, {p[:to_read]})
n = i64(_n)
if _err != nil {
err = .Unknown
} else if n == 0 {
err = .EOF
}
return
case .Read_At:
if len(p) <= 0 {
return
}
if offset < 0 {
err = .Invalid_Offset
return
}
to_read := min(len(p), MAX_RW)
_n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset))
n = i64(_n)
if _err != nil {
err = .Unknown
} else if n == 0 {
err = .EOF
}
return
case .Write:
p := p
for len(p) > 0 {
to_write := min(len(p), MAX_RW)
_n, _err := wasi.fd_write(fd, {p[:to_write]})
if _err != nil {
err = .Unknown
return
}
p = p[_n:]
n += i64(_n)
}
return
case .Write_At:
p := p
offset := offset
if offset < 0 {
err = .Invalid_Offset
return
}
for len(p) > 0 {
to_write := min(len(p), MAX_RW)
_n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset))
if _err != nil {
err = .Unknown
return
}
p = p[_n:]
n += i64(_n)
offset += i64(_n)
}
return
case .Seek:
#assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start))
#assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current))
#assert(int(wasi.whence_t.END) == int(io.Seek_From.End))
switch whence {
case .Start, .Current, .End:
break
case:
err = .Invalid_Whence
return
}
_n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence))
#partial switch _err {
case .INVAL:
err = .Invalid_Offset
case:
err = .Unknown
case .SUCCESS:
n = i64(_n)
}
return
case .Size:
stat, _err := wasi.fd_filestat_get(fd)
if _err != nil {
err = .Unknown
return
}
n = i64(stat.size)
return
case .Flush:
ferr := _sync(&f.file)
err = error_to_io_error(ferr)
return
case .Close, .Destroy:
ferr := _close(f)
err = error_to_io_error(ferr)
return
case .Query:
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
case:
return 0, .Empty
}
}
+2 -722
View File
@@ -1,726 +1,6 @@
#+private #+private
package os2 package os2
import "core:sys/linux" import "base:runtime"
import "core:sync"
import "core:mem"
// NOTEs
//
// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region
// consists of a Region_Header and the memory that will be divided into allocations to
// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes.
// Allocation_Headers are used to navigate the memory in the region. The "next" member of
// the Allocation_Header points to the next header, and the space between the headers
// can be used to send to the user. This space between is referred to as "blocks" in the
// code. The indexes in the header refer to these blocks instead of bytes. This allows us
// to index all the memory in the region with a u16.
//
// When an allocation request is made, it will use the first free block that can contain
// the entire block. If there is an excess number of blocks (as specified by the constant
// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list.
//
// To keep the implementation simple, there can never exist 2 free blocks adjacent to each
// other. Any freeing will result in attempting to merge the blocks before and after the
// newly free'd blocks.
//
// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation
// getting its own individual mmap. Individual mmaps will still get an Allocation_Header
// that contains the size with the last bit set to 1 to indicate it is indeed a direct
// mmap allocation.
// Why not brk?
// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation
// does *not* utilize the brk system call to avoid possible conflicts with foreign C
// code. Just because we aren't directly using libc, there is nothing stopping the user
// from doing it.
// What's with all the #no_bounds_check?
// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere
// until that region is written to by *you*. So, when a new region is created, we call mmap
// to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the
// region itself is never formally initialized by the compiler as this would result in writing
// zeros to memory that we can already assume are 0. This would also have the effect of
// actually commiting this data to memory whether it gets used or not.
//
// Some variables to play with
//
// Minimum blocks used for any one allocation
MINIMUM_BLOCK_COUNT :: 2
// Number of extra blocks beyond the requested amount where we would segment.
// E.g. (blocks) |H0123456| 7 available
// |H01H0123| Ask for 2, now 4 available
BLOCK_SEGMENT_THRESHOLD :: 4
// Anything above this threshold will get its own memory map. Since regions
// are indexed by 16 bit integers, this value should not surpass max(u16) * 6
DIRECT_MMAP_THRESHOLD_USER :: int(max(u16))
// The point at which we convert direct mmap to region. This should be a decent
// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions.
MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4
// free_list is dynamic and is initialized in the begining of the region memory
// when the region is initialized. Once resized, it can be moved anywhere.
FREE_LIST_DEFAULT_CAP :: 32
//
// Other constants that should not be touched
//
// This universally seems to be 4096 outside of uncommon archs.
PAGE_SIZE :: 4096
// just rounding up to nearest PAGE_SIZE
DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE
// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well
// as end right on a page boundary as to not waste space.
SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE)
// size of user memory blocks
BLOCK_SIZE :: size_of(Allocation_Header)
// number of allocation sections (call them blocks) of the region used for allocations
BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE)
// minimum amount of space that can used by any individual allocation (includes header)
MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE
// This is used as a boolean value for Region_Header.local_addr.
CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0))
FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16)
MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE}
MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE}
@thread_local _local_region: ^Region
global_regions: ^Region
// There is no way of correctly setting the last bit of free_idx or
// the last bit of requested, so we can safely use it as a flag to
// determine if we are interacting with a direct mmap.
REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF
IS_DIRECT_MMAP :: 0x8000000000000000
// Special free_idx value that does not index the free_list.
NOT_FREE :: 0x7FFF
Allocation_Header :: struct #raw_union {
using _: struct {
// Block indicies
idx: u16,
prev: u16,
next: u16,
free_idx: u16,
},
requested: u64,
}
Region_Header :: struct #align(16) {
next_region: ^Region, // points to next region in global_heap (linked list)
local_addr: ^^Region, // tracks region ownership via address of _local_region
reset_addr: ^^Region, // tracks old local addr for reset
free_list: []u16,
free_list_len: u16,
free_blocks: u16, // number of free blocks in region (includes headers)
last_used: u16, // farthest back block that has been used (need zeroing?)
_reserved: u16,
}
Region :: struct {
hdr: Region_Header,
memory: [BLOCKS_PER_REGION]Allocation_Header,
}
_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) {
//
// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
// padding. We also store the original pointer returned by heap_alloc right before
// the pointer we return to the user.
//
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) {
a := max(alignment, align_of(rawptr))
space := size + a - 1
allocated_mem: rawptr
if old_ptr != nil {
original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^
allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
} else {
allocated_mem = heap_alloc(space+size_of(rawptr))
}
aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)))
ptr := uintptr(aligned_mem)
aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a)
diff := int(aligned_ptr - ptr)
if (size + diff) > space || allocated_mem == nil {
return nil, .Out_Of_Memory
}
aligned_mem = rawptr(aligned_ptr)
mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem
return mem.byte_slice(aligned_mem, size), nil
}
aligned_free :: proc(p: rawptr) {
if p != nil {
heap_free(mem.ptr_offset((^rawptr)(p), -1)^)
}
}
aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) {
if p == nil {
return nil, nil
}
return aligned_alloc(new_size, new_alignment, p)
}
switch mode {
case .Alloc, .Alloc_Non_Zeroed:
return aligned_alloc(size, alignment)
case .Free:
aligned_free(old_memory)
case .Free_All:
return nil, .Mode_Not_Implemented
case .Resize, .Resize_Non_Zeroed:
if old_memory == nil {
return aligned_alloc(size, alignment)
}
return aligned_resize(old_memory, old_size, size, alignment)
case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory)
if set != nil {
set^ = {.Alloc, .Free, .Resize, .Query_Features}
}
return nil, nil
case .Query_Info:
return nil, .Mode_Not_Implemented
}
return nil, nil
}
heap_alloc :: proc(size: int) -> rawptr {
if size >= DIRECT_MMAP_THRESHOLD {
return _direct_mmap_alloc(size)
}
// atomically check if the local region has been stolen
if _local_region != nil {
res := sync.atomic_compare_exchange_strong_explicit(
&_local_region.hdr.local_addr,
&_local_region,
CURRENTLY_ACTIVE,
.Acquire,
.Relaxed,
)
if res != &_local_region {
// At this point, the region has been stolen and res contains the unexpected value
expected := res
if res != CURRENTLY_ACTIVE {
expected = res
res = sync.atomic_compare_exchange_strong_explicit(
&_local_region.hdr.local_addr,
expected,
CURRENTLY_ACTIVE,
.Acquire,
.Relaxed,
)
}
if res != expected {
_local_region = nil
}
}
}
size := size
size = _round_up_to_nearest(size, BLOCK_SIZE)
blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE))
// retrieve a region if new thread or stolen
if _local_region == nil {
_local_region, _ = _region_retrieve_with_space(blocks_needed)
if _local_region == nil {
return nil
}
}
defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
// At this point we have a usable region. Let's find the user some memory
idx: u16
local_region_idx := _region_get_local_idx()
back_idx := -1
infinite: for {
for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 {
idx = _local_region.hdr.free_list[i]
#no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed {
break infinite
}
}
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
_local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx)
}
user_ptr, used := _region_get_block(_local_region, idx, blocks_needed)
sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release)
// If this memory was ever used before, it now needs to be zero'd.
if idx < _local_region.hdr.last_used {
mem.zero(user_ptr, int(used) * BLOCK_SIZE)
} else {
_local_region.hdr.last_used = idx + used
}
return user_ptr
}
heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check {
alloc := _get_allocation_header(old_memory)
if alloc.requested & IS_DIRECT_MMAP > 0 {
return _direct_mmap_resize(alloc, new_size)
}
if new_size > DIRECT_MMAP_THRESHOLD {
return _direct_mmap_from_region(alloc, new_size)
}
return _region_resize(alloc, new_size)
}
heap_free :: proc(memory: rawptr) {
alloc := _get_allocation_header(memory)
if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP {
_direct_mmap_free(alloc)
return
}
assert(alloc.free_idx == NOT_FREE)
_region_find_and_assign_local(alloc)
_region_local_free(alloc)
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
}
//
// Regions
//
_new_region :: proc() -> ^Region #no_bounds_check {
ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
if errno != .NONE {
return nil
}
new_region := (^Region)(ptr)
new_region.hdr.local_addr = CURRENTLY_ACTIVE
new_region.hdr.reset_addr = &_local_region
free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK)
_region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK)
// + 2 to account for free_list's allocation header
first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2
// first allocation header (this is a free list)
new_region.memory[0].next = u16(first_user_block)
new_region.memory[0].free_idx = NOT_FREE
new_region.memory[first_user_block].idx = u16(first_user_block)
new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1
// add the first user block to the free list
new_region.hdr.free_list[0] = u16(first_user_block)
new_region.hdr.free_list_len = 1
new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1
for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region);
r != nil;
r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {}
return new_region
}
_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check {
assert(alloc.free_idx == NOT_FREE)
old_memory := mem.ptr_offset(alloc, 1)
old_block_count := _get_block_count(alloc^)
new_block_count := u16(
max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE),
)
if new_block_count < old_block_count {
if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT {
_region_find_and_assign_local(alloc)
_region_segment(_local_region, alloc, new_block_count, alloc.free_idx)
new_block_count = _get_block_count(alloc^)
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
}
// need to zero anything within the new block that that lies beyond new_size
extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size
extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE)
mem.zero(extra_bytes_ptr, extra_bytes)
return old_memory
}
if !alloc_is_free_list {
_region_find_and_assign_local(alloc)
}
defer if !alloc_is_free_list {
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
}
// First, let's see if we can grow in place.
if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE {
next_alloc := _local_region.memory[alloc.next]
total_available := old_block_count + _get_block_count(next_alloc) + 1
if total_available >= new_block_count {
alloc.next = next_alloc.next
_local_region.memory[alloc.next].prev = alloc.idx
if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD {
_region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx)
} else {
_region_free_list_remove(_local_region, next_alloc.free_idx)
}
mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE)
_local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used)
_local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count)
if alloc_is_free_list {
_region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^))
}
return old_memory
}
}
// If we made it this far, we need to resize, copy, zero and free.
region_iter := _local_region
local_region_idx := _region_get_local_idx()
back_idx := -1
idx: u16
infinite: for {
for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 {
idx = region_iter.hdr.free_list[i]
if _get_block_count(region_iter.memory[idx]) >= new_block_count {
break infinite
}
}
if region_iter != _local_region {
sync.atomic_store_explicit(
&region_iter.hdr.local_addr,
region_iter.hdr.reset_addr,
.Release,
)
}
region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx)
}
if region_iter != _local_region {
sync.atomic_store_explicit(
&region_iter.hdr.local_addr,
region_iter.hdr.reset_addr,
.Release,
)
}
// copy from old memory
new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count)
mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE))
// zero any new memory
addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count)
new_blocks := used_blocks - old_block_count
mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE)
region_iter.hdr.free_blocks -= (used_blocks + 1)
// Set free_list before freeing.
if alloc_is_free_list {
_region_assign_free_list(_local_region, new_memory, used_blocks)
}
// free old memory
_region_local_free(alloc)
return new_memory
}
_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check {
alloc := alloc
add_to_free_list := true
idx := sync.atomic_load(&alloc.idx)
prev := sync.atomic_load(&alloc.prev)
next := sync.atomic_load(&alloc.next)
block_count := next - idx - 1
free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1
sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release)
// try to merge with prev
if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE {
sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release)
_local_region.memory[next].prev = prev
alloc = &_local_region.memory[prev]
add_to_free_list = false
}
// try to merge with next
if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE {
old_next := next
sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release)
sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release)
if add_to_free_list {
sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release)
sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release)
} else {
// NOTE: We have aleady merged with prev, and now merged with next.
// Now, we are actually going to remove from the free_list.
_region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx)
}
add_to_free_list = false
}
// This is the only place where anything is appended to the free list.
if add_to_free_list {
fl := _local_region.hdr.free_list
fl_len := sync.atomic_load(&_local_region.hdr.free_list_len)
sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release)
fl[alloc.free_idx] = idx
sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release)
if int(fl_len + 1) == len(fl) {
free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list))
_region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true)
}
}
}
_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) {
raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list
raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK
raw_free_list.data = memory
region.hdr.free_list = transmute([]u16)(raw_free_list)
}
_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) {
r: ^Region
idx: int
for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region {
if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks {
idx += 1
continue
}
idx += 1
local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr)
if local_addr != CURRENTLY_ACTIVE {
res := sync.atomic_compare_exchange_strong_explicit(
&r.hdr.local_addr,
local_addr,
CURRENTLY_ACTIVE,
.Acquire,
.Relaxed,
)
if res == local_addr {
r.hdr.reset_addr = local_addr
return r, idx
}
}
}
return _new_region(), idx
}
_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region {
r: ^Region
for r = global_regions; r != nil; r = r.hdr.next_region {
if _region_contains_mem(r, addr) {
return r
}
}
unreachable()
}
_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check {
alloc := &region.memory[idx]
assert(alloc.free_idx != NOT_FREE)
assert(alloc.next > 0)
block_count := _get_block_count(alloc^)
if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD {
_region_segment(region, alloc, blocks_needed, alloc.free_idx)
} else {
_region_free_list_remove(region, alloc.free_idx)
}
alloc.free_idx = NOT_FREE
return mem.ptr_offset(alloc, 1), _get_block_count(alloc^)
}
_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check {
old_next := alloc.next
alloc.next = alloc.idx + blocks + 1
region.memory[old_next].prev = alloc.next
// Initialize alloc.next allocation header here.
region.memory[alloc.next].prev = alloc.idx
region.memory[alloc.next].next = old_next
region.memory[alloc.next].idx = alloc.next
region.memory[alloc.next].free_idx = new_free_idx
// Replace our original spot in the free_list with new segment.
region.hdr.free_list[new_free_idx] = alloc.next
}
_region_get_local_idx :: proc() -> int {
idx: int
for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region {
if r == _local_region {
return idx
}
idx += 1
}
return -1
}
_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) {
// Find the region that contains this memory
if !_region_contains_mem(_local_region, alloc) {
_local_region = _region_retrieve_from_addr(alloc)
}
// At this point, _local_region is set correctly. Spin until acquire
res := CURRENTLY_ACTIVE
for res == CURRENTLY_ACTIVE {
res = sync.atomic_compare_exchange_strong_explicit(
&_local_region.hdr.local_addr,
&_local_region,
CURRENTLY_ACTIVE,
.Acquire,
.Relaxed,
)
}
}
_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check {
if r == nil {
return false
}
mem_int := uintptr(memory)
return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1])
}
_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check {
// pop, swap and update allocation hdr
if n := region.hdr.free_list_len - 1; free_idx != n {
region.hdr.free_list[free_idx] = sync.atomic_load(&region.hdr.free_list[n])
alloc_idx := region.hdr.free_list[free_idx]
sync.atomic_store_explicit(&region.memory[alloc_idx].free_idx, free_idx, .Release)
}
region.hdr.free_list_len -= 1
}
//
// Direct mmap
//
_direct_mmap_alloc :: proc(size: int) -> rawptr {
mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE)
new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
if errno != .NONE {
return nil
}
alloc := (^Allocation_Header)(uintptr(new_allocation))
alloc.requested = u64(size) // NOTE: requested = requested size
alloc.requested += IS_DIRECT_MMAP
return rawptr(mem.ptr_offset(alloc, 1))
}
_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
old_requested := int(alloc.requested & REQUESTED_MASK)
old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE)
new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE)
if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD {
return _direct_mmap_to_region(alloc, new_size)
} else if old_requested == new_size {
return mem.ptr_offset(alloc, 1)
}
new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE})
if errno != .NONE {
return nil
}
new_header := (^Allocation_Header)(uintptr(new_allocation))
new_header.requested = u64(new_size)
new_header.requested += IS_DIRECT_MMAP
if new_mmap_size > old_mmap_size {
// new section may not be pointer aligned, so cast to ^u8
new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE)
mem.zero(new_section, new_mmap_size - old_mmap_size)
}
return mem.ptr_offset(new_header, 1)
}
_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
new_memory := _direct_mmap_alloc(new_size)
if new_memory != nil {
old_memory := mem.ptr_offset(alloc, 1)
mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE)
}
_region_find_and_assign_local(alloc)
_region_local_free(alloc)
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
return new_memory
}
_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
new_memory := heap_alloc(new_size)
if new_memory != nil {
mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size)
_direct_mmap_free(alloc)
}
return new_memory
}
_direct_mmap_free :: proc(alloc: ^Allocation_Header) {
requested := int(alloc.requested & REQUESTED_MASK)
mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE)
linux.munmap(alloc, uint(mmap_size))
}
//
// Util
//
_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 {
return alloc.next - alloc.idx - 1
}
_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header {
return mem.ptr_offset((^Allocation_Header)(raw_mem), -1)
}
_round_up_to_nearest :: #force_inline proc(size, round: int) -> int {
return (size-1) + round - (size-1) % round
}
_heap_allocator_proc :: runtime.heap_allocator_proc
+6
View File
@@ -0,0 +1,6 @@
#+private
package os2
import "base:runtime"
_heap_allocator_proc :: runtime.wasm_allocator_proc
+12
View File
@@ -2,6 +2,8 @@ package os2
import "base:runtime" import "base:runtime"
import "core:path/filepath"
Path_Separator :: _Path_Separator // OS-Specific Path_Separator :: _Path_Separator // OS-Specific
Path_Separator_String :: _Path_Separator_String // OS-Specific Path_Separator_String :: _Path_Separator_String // OS-Specific
Path_List_Separator :: _Path_List_Separator // OS-Specific Path_List_Separator :: _Path_List_Separator // OS-Specific
@@ -39,3 +41,13 @@ setwd :: set_working_directory
set_working_directory :: proc(dir: string) -> (err: Error) { set_working_directory :: proc(dir: string) -> (err: Error) {
return _set_working_directory(dir) return _set_working_directory(dir)
} }
get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
return _get_executable_path(allocator)
}
get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
path = _get_executable_path(allocator) or_return
path, _ = filepath.split(path)
return
}
+17
View File
@@ -0,0 +1,17 @@
package os2
import "base:runtime"
import "core:sys/darwin"
import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = ---
ret := darwin.proc_pidpath(posix.getpid(), raw_data(buffer[:]), len(buffer))
if ret > 0 {
return clone_string(string(buffer[:ret]), allocator)
}
err = _get_platform_error()
return
}
+29
View File
@@ -0,0 +1,29 @@
package os2
import "base:runtime"
import "core:sys/freebsd"
import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
req := []freebsd.MIB_Identifier{.CTL_KERN, .KERN_PROC, .KERN_PROC_PATHNAME, freebsd.MIB_Identifier(-1)}
size: uint
if ret := freebsd.sysctl(req, nil, &size, nil, 0); ret != .NONE {
err = _get_platform_error(posix.Errno(ret))
return
}
assert(size > 0)
buf := make([]byte, size, allocator) or_return
defer if err != nil { delete(buf, allocator) }
assert(uint(len(buf)) == size)
if ret := freebsd.sysctl(req, raw_data(buf), &size, nil, 0); ret != .NONE {
err = _get_platform_error(posix.Errno(ret))
return
}
return string(buf[:size]), nil
}
+21 -1
View File
@@ -1,9 +1,10 @@
#+private #+private
package os2 package os2
import "base:runtime"
import "core:strings" import "core:strings"
import "core:strconv" import "core:strconv"
import "base:runtime"
import "core:sys/linux" import "core:sys/linux"
_Path_Separator :: '/' _Path_Separator :: '/'
@@ -171,6 +172,25 @@ _set_working_directory :: proc(dir: string) -> Error {
return _get_platform_error(linux.chdir(dir_cstr)) return _get_platform_error(linux.chdir(dir_cstr))
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD()
buf := make([dynamic]byte, 1024, temp_allocator()) or_return
for {
n, errno := linux.readlink("/proc/self/exe", buf[:])
if errno != .NONE {
err = _get_platform_error(errno)
return
}
if n < len(buf) {
return clone_string(string(buf[:n]), allocator)
}
resize(&buf, len(buf)*2) or_return
}
}
_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) {
PROC_FD_PATH :: "/proc/self/fd/" PROC_FD_PATH :: "/proc/self/fd/"
+24
View File
@@ -0,0 +1,24 @@
package os2
import "base:runtime"
import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD()
buf := make([dynamic]byte, 1024, temp_allocator()) or_return
for {
n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
if n < 0 {
err = _get_platform_error()
return
}
if n < len(buf) {
return clone_string(string(buf[:n]), allocator)
}
resize(&buf, len(buf)*2) or_return
}
}
+57
View File
@@ -0,0 +1,57 @@
package os2
import "base:runtime"
import "core:strings"
import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
// OpenBSD does not have an API for this, we do our best below.
if len(runtime.args__) <= 0 {
err = .Invalid_Path
return
}
real :: proc(path: cstring, allocator: runtime.Allocator) -> (out: string, err: Error) {
real := posix.realpath(path)
if real == nil {
err = _get_platform_error()
return
}
defer posix.free(real)
return clone_string(string(real), allocator)
}
arg := runtime.args__[0]
sarg := string(arg)
if len(sarg) == 0 {
err = .Invalid_Path
return
}
if sarg[0] == '.' || sarg[0] == '/' {
return real(arg, allocator)
}
TEMP_ALLOCATOR_GUARD()
buf := strings.builder_make(temp_allocator())
paths := get_env("PATH", temp_allocator())
for dir in strings.split_iterator(&paths, ":") {
strings.builder_reset(&buf)
strings.write_string(&buf, dir)
strings.write_string(&buf, "/")
strings.write_string(&buf, sarg)
cpath := strings.to_cstring(&buf)
if posix.access(cpath, {.X_OK}) == .OK {
return real(cpath, allocator)
}
}
err = .Invalid_Path
return
}
+1 -1
View File
@@ -81,7 +81,7 @@ _remove_all :: proc(path: string) -> Error {
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
if entry.d_type == .DIR { if entry.d_type == .DIR {
_remove_all(fullpath[:len(fullpath)-1]) _remove_all(fullpath[:len(fullpath)-1]) or_return
} else { } else {
if posix.unlink(cstring(raw_data(fullpath))) != .OK { if posix.unlink(cstring(raw_data(fullpath))) != .OK {
return _get_platform_error() return _get_platform_error()
+113
View File
@@ -0,0 +1,113 @@
#+private
package os2
import "base:runtime"
import "core:path/filepath"
import "core:sync"
import "core:sys/wasm/wasi"
_Path_Separator :: '/'
_Path_Separator_String :: "/"
_Path_List_Separator :: ':'
_is_path_separator :: proc(c: byte) -> bool {
return c == _Path_Separator
}
_mkdir :: proc(name: string, perm: int) -> Error {
dir_fd, relative, ok := match_preopen(name)
if !ok {
return .Invalid_Path
}
return _get_platform_error(wasi.path_create_directory(dir_fd, relative))
}
_mkdir_all :: proc(path: string, perm: int) -> Error {
if path == "" {
return .Invalid_Path
}
TEMP_ALLOCATOR_GUARD()
if exists(path) {
return .Exist
}
clean_path := filepath.clean(path, temp_allocator())
return internal_mkdir_all(clean_path)
internal_mkdir_all :: proc(path: string) -> Error {
dir, file := filepath.split(path)
if file != path && dir != "/" {
if len(dir) > 1 && dir[len(dir) - 1] == '/' {
dir = dir[:len(dir) - 1]
}
internal_mkdir_all(dir) or_return
}
err := _mkdir(path, 0)
if err == .Exist { err = nil }
return err
}
}
_remove_all :: proc(path: string) -> (err: Error) {
// PERF: this works, but wastes a bunch of memory using the read_directory_iterator API
// and using open instead of wasi fds directly.
{
dir := open(path) or_return
defer close(dir)
iter := read_directory_iterator_create(dir) or_return
defer read_directory_iterator_destroy(&iter)
for fi in read_directory_iterator(&iter) {
if fi.type == .Directory {
_remove_all(fi.fullpath) or_return
} else {
remove(fi.fullpath) or_return
}
}
}
return remove(path)
}
g_wd: string
g_wd_mutex: sync.Mutex
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
sync.guard(&g_wd_mutex)
return clone_string(g_wd if g_wd != "" else "/", allocator)
}
_set_working_directory :: proc(dir: string) -> (err: Error) {
sync.guard(&g_wd_mutex)
if dir == g_wd {
return
}
if g_wd != "" {
delete(g_wd, file_allocator())
}
g_wd = clone_string(dir, file_allocator()) or_return
return
}
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
if len(args) <= 0 {
return clone_string("/", allocator)
}
arg := args[0]
if len(arg) > 0 && (arg[0] == '.' || arg[0] == '/') {
return clone_string(arg, allocator)
}
return concatenate({"/", arg}, allocator)
}
+20
View File
@@ -136,6 +136,26 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
return return
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD()
buf := make([dynamic]u16, 512, temp_allocator()) or_return
for {
ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
if ret == 0 {
err = _get_platform_error()
return
}
if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER {
resize(&buf, len(buf)*2) or_return
continue
}
return win32_utf16_to_utf8(buf[:ret], allocator)
}
}
can_use_long_paths: bool can_use_long_paths: bool
@(init) @(init)
+13
View File
@@ -0,0 +1,13 @@
#+private
package os2
_pipe :: proc() -> (r, w: ^File, err: Error) {
err = .Unsupported
return
}
@(require_results)
_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
err = .Unsupported
return
}
+5
View File
@@ -547,6 +547,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE {
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
} }
if dir_fd != linux.AT_FDCWD {
if errno = linux.fchdir(dir_fd); errno != .NONE {
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
}
}
errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) errno = linux.execveat(dir_fd, exe_path, &cargs[0], env)
assert(errno != nil) assert(errno != nil)
+89
View File
@@ -0,0 +1,89 @@
#+private
package os2
import "base:runtime"
import "core:time"
import "core:sys/wasm/wasi"
_exit :: proc "contextless" (code: int) -> ! {
wasi.proc_exit(wasi.exitcode_t(code))
}
_get_uid :: proc() -> int {
return 0
}
_get_euid :: proc() -> int {
return 0
}
_get_gid :: proc() -> int {
return 0
}
_get_egid :: proc() -> int {
return 0
}
_get_pid :: proc() -> int {
return 0
}
_get_ppid :: proc() -> int {
return 0
}
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
err = .Unsupported
return
}
_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
err = .Unsupported
return
}
_Sys_Process_Attributes :: struct {}
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
err = .Unsupported
return
}
_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
err = .Unsupported
return
}
_process_close :: proc(process: Process) -> Error {
return .Unsupported
}
_process_kill :: proc(process: Process) -> (err: Error) {
return .Unsupported
}
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
err = .Unsupported
return
}
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
err = .Unsupported
return
}
_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
process.pid = pid
err = .Unsupported
return
}
_process_handle_still_valid :: proc(p: Process) -> Error {
return nil
}
_process_state_update_times :: proc(p: Process, state: ^Process_State) {
return
}
+101
View File
@@ -0,0 +1,101 @@
#+private
package os2
import "base:runtime"
import "core:path/filepath"
import "core:sys/wasm/wasi"
import "core:time"
internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) {
fi.fullpath = fullpath
fi.name = filepath.base(fi.fullpath)
fi.inode = u128(stat.ino)
fi.size = i64(stat.size)
switch stat.filetype {
case .BLOCK_DEVICE: fi.type = .Block_Device
case .CHARACTER_DEVICE: fi.type = .Character_Device
case .DIRECTORY: fi.type = .Directory
case .REGULAR_FILE: fi.type = .Regular
case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket
case .SYMBOLIC_LINK: fi.type = .Symlink
case .UNKNOWN: fi.type = .Undetermined
case: fi.type = .Undetermined
}
fi.creation_time = time.Time{_nsec=i64(stat.ctim)}
fi.modification_time = time.Time{_nsec=i64(stat.mtim)}
fi.access_time = time.Time{_nsec=i64(stat.atim)}
return
}
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
if f == nil || f.impl == nil {
err = .Invalid_File
return
}
impl := (^File_Impl)(f.impl)
stat, _err := wasi.fd_filestat_get(__fd(f))
if _err != nil {
err = _get_platform_error(_err)
return
}
fullpath := clone_string(impl.name, allocator) or_return
return internal_stat(stat, fullpath), nil
}
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
if name == "" {
err = .Invalid_Path
return
}
dir_fd, relative, ok := match_preopen(name)
if !ok {
err = .Invalid_Path
return
}
stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative)
if _err != nil {
err = _get_platform_error(_err)
return
}
// NOTE: wasi doesn't really do full paths afact.
fullpath := clone_string(name, allocator) or_return
return internal_stat(stat, fullpath), nil
}
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
if name == "" {
err = .Invalid_Path
return
}
dir_fd, relative, ok := match_preopen(name)
if !ok {
err = .Invalid_Path
return
}
stat, _err := wasi.path_filestat_get(dir_fd, {}, relative)
if _err != nil {
err = _get_platform_error(_err)
return
}
// NOTE: wasi doesn't really do full paths afact.
fullpath := clone_string(name, allocator) or_return
return internal_stat(stat, fullpath), nil
}
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
+9
View File
@@ -0,0 +1,9 @@
#+private
package os2
import "base:runtime"
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
// NOTE: requires user to add /tmp to their preopen dirs, no standard way exists.
return clone_string("/tmp", allocator)
}
+1 -1
View File
@@ -1287,7 +1287,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc
} }
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
result := _unix_send(c.int(sd), raw_data(data), len(data), 0) result := _unix_send(c.int(sd), raw_data(data), len(data), i32(flags))
if result < 0 { if result < 0 {
return 0, get_last_error() return 0, get_last_error()
} }
+1 -1
View File
@@ -1155,7 +1155,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc
} }
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0) result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, nil, 0)
if result < 0 { if result < 0 {
return 0, _get_errno(int(result)) return 0, _get_errno(int(result))
} }
+1
View File
@@ -1,3 +1,4 @@
#+build !wasi
package filepath package filepath
import "core:os" import "core:os"
+36
View File
@@ -0,0 +1,36 @@
package filepath
import "base:runtime"
import "core:strings"
SEPARATOR :: '/'
SEPARATOR_STRING :: `/`
LIST_SEPARATOR :: ':'
is_reserved_name :: proc(path: string) -> bool {
return false
}
is_abs :: proc(path: string) -> bool {
return strings.has_prefix(path, "/")
}
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
if is_abs(path) {
return strings.clone(string(path), allocator), true
}
return path, false
}
join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
for e, i in elems {
if e != "" {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
return clean(p, allocator)
}
}
return "", nil
}
+1
View File
@@ -1,3 +1,4 @@
#+build !wasi
package filepath package filepath
import "core:os" import "core:os"
-3
View File
@@ -49,9 +49,6 @@ _futex_signal :: proc "contextless" (futex: ^Futex) {
} }
_futex_broadcast :: proc "contextless" (futex: ^Futex) { _futex_broadcast :: proc "contextless" (futex: ^Futex) {
// NOTE(flysand): This code was kinda funny and I don't want to remove it, but here I will
// record history of what has been in here before
// FUTEX_WAKE_PRIVATE | FUTEX_WAKE
_, errno := linux.futex(cast(^linux.Futex) futex, linux.FUTEX_WAKE, {.PRIVATE}, max(i32)) _, errno := linux.futex(cast(^linux.Futex) futex, linux.FUTEX_WAKE, {.PRIVATE}, max(i32))
#partial switch errno { #partial switch errno {
case .NONE: case .NONE:
+2 -2
View File
@@ -26,7 +26,7 @@ foreign Ntdll {
BUT requires taking the return value of it and if it is non-zero BUT requires taking the return value of it and if it is non-zero
converting that status to a DOS error and then SetLastError converting that status to a DOS error and then SetLastError
If this is not done, then things don't work as expected when If this is not done, then things don't work as expected when
and error occurs an error occurs
GODDAMN MICROSOFT! GODDAMN MICROSOFT!
*/ */
@@ -46,7 +46,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> bool {
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> bool { _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> bool {
expect := expect expect := expect
// NOTE(bill): for some bizarre reason, this has be a negative number // NOTE(bill): for some bizarre reason, this has to be a negative number
timeout := -i64(duration / 100) timeout := -i64(duration / 100)
return CustomWaitOnAddress(f, &expect, size_of(expect), &timeout) return CustomWaitOnAddress(f, &expect, size_of(expect), &timeout)
} }
+4
View File
@@ -25,6 +25,10 @@ Block_createLocalWithParam :: proc (user_data: rawptr, user_proc: proc "c" (user
b, _ := Block_createInternalWithParam(false, user_data, user_proc, {}) b, _ := Block_createInternalWithParam(false, user_data, user_proc, {})
return b return b
} }
@(objc_type=Block, objc_name="invoke")
Block_invoke :: proc "c" (self: ^Block, args: ..any) -> ^Object {
return msgSend(^Object, self, "invoke:", ..args)
}
@(private) @(private)
Internal_Block_Literal_Base :: struct { Internal_Block_Literal_Base :: struct {
+17
View File
@@ -13,6 +13,23 @@ Data_init :: proc "c" (self: ^Data) -> ^Data {
return msgSend(^Data, self, "init") return msgSend(^Data, self, "init")
} }
@(objc_type=Data, objc_name="initWithBytes")
Data_initWithBytes :: proc "c" (self: ^Data, bytes: []byte) -> ^Data {
return msgSend(^Data, self, "initWithBytes:length:", raw_data(bytes), len(bytes))
}
@(objc_type=Data, objc_name="initWithBytesNoCopy")
Data_initWithBytesNoCopy :: proc "c" (self: ^Data, bytes: []byte, freeWhenDone: BOOL) -> ^Data {
return msgSend(
^Data,
self,
"initWithBytesNoCopy:length:freeWhenDone:",
raw_data(bytes),
len(bytes),
freeWhenDone,
)
}
@(objc_type=Data, objc_name="mutableBytes") @(objc_type=Data, objc_name="mutableBytes")
Data_mutableBytes :: proc "c" (self: ^Data) -> rawptr { Data_mutableBytes :: proc "c" (self: ^Data) -> rawptr {
return msgSend(rawptr, self, "mutableBytes") return msgSend(rawptr, self, "mutableBytes")
+5
View File
@@ -18,6 +18,11 @@ Date_dateWithTimeIntervalSinceNow :: proc "c" (secs: TimeInterval) -> ^Date {
return msgSend(^Date, Date, "dateWithTimeIntervalSinceNow:", secs) return msgSend(^Date, Date, "dateWithTimeIntervalSinceNow:", secs)
} }
@(objc_type=Date, objc_name="timeIntervalSince1970")
Date_timeIntervalSince1970 :: proc "c" (self: ^Date) -> f64 {
return msgSend(f64, self, "timeIntervalSince1970")
}
@(objc_type=Date, objc_name="distantFuture", objc_is_class_method=true) @(objc_type=Date, objc_name="distantFuture", objc_is_class_method=true)
Date_distantFuture :: proc "c" () -> ^Date { Date_distantFuture :: proc "c" () -> ^Date {
return msgSend(^Date, Date, "distantFuture") return msgSend(^Date, Date, "distantFuture")
+16
View File
@@ -30,6 +30,7 @@ MenuItem :: struct {using _: Object}
MenuItem_alloc :: proc "c" () -> ^MenuItem { MenuItem_alloc :: proc "c" () -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "alloc") return msgSend(^MenuItem, MenuItem, "alloc")
} }
@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) @(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
s := string(name) s := string(name)
@@ -50,11 +51,21 @@ MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCa
return sel return sel
} }
@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true)
MenuItem_separatorItem :: proc "c" () -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "separatorItem")
}
@(objc_type=MenuItem, objc_name="init") @(objc_type=MenuItem, objc_name="init")
MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
return msgSend(^MenuItem, self, "init") return msgSend(^MenuItem, self, "init")
} }
@(objc_type=MenuItem, objc_name="initWithTitle")
MenuItem_initWithTitle :: proc "c" (self: ^MenuItem, title: ^String, action: SEL, keyEquivalent: ^String) -> ^MenuItem {
return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", title, action, keyEquivalent)
}
@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask") @(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) { MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) {
msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask) msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask)
@@ -75,6 +86,11 @@ MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "title") return msgSend(^String, self, "title")
} }
@(objc_type=MenuItem, objc_name="setTitle")
MenuItem_setTitle :: proc "c" (self: ^MenuItem, title: ^String) -> ^String {
return msgSend(^String, self, "title:", title)
}
@(objc_class="NSMenu") @(objc_class="NSMenu")
@@ -7,3 +7,13 @@ SavePanel :: struct{ using _: Panel }
SavePanel_runModal :: proc "c" (self: ^SavePanel) -> ModalResponse { SavePanel_runModal :: proc "c" (self: ^SavePanel) -> ModalResponse {
return msgSend(ModalResponse, self, "runModal") return msgSend(ModalResponse, self, "runModal")
} }
@(objc_type=SavePanel, objc_name="savePanel", objc_is_class_method=true)
SavePanel_savePanel :: proc "c" () -> ^SavePanel {
return msgSend(^SavePanel, SavePanel, "savePanel")
}
@(objc_type=SavePanel, objc_name="URL")
SavePanel_URL :: proc "c" (self: ^SavePanel) -> ^Array {
return msgSend(^Array, self, "URL")
}
+14
View File
@@ -0,0 +1,14 @@
package objc_Foundation
@(objc_class = "NSToolbar")
Toolbar :: struct { using _: Object }
@(objc_type = Toolbar, objc_name = "alloc", objc_is_class_method = true)
Toolbar_alloc :: proc "c" () -> ^Toolbar {
return msgSend(^Toolbar, Toolbar, "alloc")
}
@(objc_type = Toolbar, objc_name = "init")
Toolbar_init :: proc "c" (self: ^Toolbar) -> ^Toolbar {
return msgSend(^Toolbar, self, "init")
}
+5
View File
@@ -28,3 +28,8 @@ URL_initFileURLWithPath :: proc "c" (self: ^URL, path: ^String) -> ^URL {
URL_fileSystemRepresentation :: proc "c" (self: ^URL) -> cstring { URL_fileSystemRepresentation :: proc "c" (self: ^URL) -> cstring {
return msgSend(cstring, self, "fileSystemRepresentation") return msgSend(cstring, self, "fileSystemRepresentation")
} }
@(objc_type=URL, objc_name="relativePath")
URL_relativePath :: proc "c" (self: ^URL) -> ^String {
return msgSend(^String, self, "relativePath")
}
@@ -0,0 +1,24 @@
package objc_Foundation
@(objc_class = "URLRequest")
URLRequest :: struct { using _: Object }
@(objc_type = URLRequest, objc_name = "alloc", objc_is_class_method = true)
URLRequest_alloc :: proc "c" () -> ^URLRequest {
return msgSend(^URLRequest, URLRequest, "alloc")
}
@(objc_type = URLRequest, objc_name = "requestWithURL", objc_is_class_method = true)
URLRequest_requestWithURL :: proc "c" (url: ^URL) -> ^URLRequest {
return msgSend(^URLRequest, URLRequest, "requestWithURL:", url)
}
@(objc_type = URLRequest, objc_name = "init")
URLRequest_init :: proc "c" (self: ^URLRequest) -> ^URLRequest {
return msgSend(^URLRequest, URLRequest, "init")
}
@(objc_type = URLRequest, objc_name = "url")
URLRequest_url :: proc "c" (self: ^URLRequest) -> ^URL {
return msgSend(^URL, self, "URL")
}
@@ -0,0 +1,19 @@
package objc_Foundation
@(objc_class = "NSURLResponse")
URLResponse :: struct { using _: Object }
@(objc_type = URLResponse, objc_name = "alloc", objc_is_class_method = true)
URLResponse_alloc :: proc "c" () -> ^URLResponse {
return msgSend(^URLResponse, URLResponse, "alloc")
}
@(objc_type = URLResponse, objc_name = "init")
URLResponse_init :: proc "c" (self: ^URLResponse) -> ^URLResponse {
return msgSend(^URLResponse, URLResponse, "init")
}
@(objc_type = URLResponse, objc_name = "initWithURL")
URLResponse_initWithURL :: proc "c" (self: ^URLResponse, url: ^URL, mime_type: ^String, length: int, encoding: ^String ) -> ^URLResponse {
return msgSend(^URLResponse, self, "initWithURL:MIMEType:expectedContentLength:textEncodingName:", url, mime_type, Integer(length), encoding)
}
+32
View File
@@ -129,6 +129,10 @@ WindowDelegateTemplate :: struct {
windowDidExitVersionBrowser: proc(notification: ^Notification), windowDidExitVersionBrowser: proc(notification: ^Notification),
} }
Window_Title_Visibility :: enum UInteger {
Visible,
Hidden,
}
WindowDelegate :: struct { using _: Object } // This is not the same as NSWindowDelegate WindowDelegate :: struct { using _: Object } // This is not the same as NSWindowDelegate
_WindowDelegateInternal :: struct { _WindowDelegateInternal :: struct {
@@ -616,6 +620,10 @@ View_setWantsLayer :: proc "c" (self: ^View, wantsLayer: BOOL) {
View_convertPointFromView :: proc "c" (self: ^View, point: Point, view: ^View) -> Point { View_convertPointFromView :: proc "c" (self: ^View, point: Point, view: ^View) -> Point {
return msgSend(Point, self, "convertPoint:fromView:", point, view) return msgSend(Point, self, "convertPoint:fromView:", point, view)
} }
@(objc_type=View, objc_name="addSubview")
View_addSubview :: proc "c" (self: ^View, view: ^View) {
msgSend(nil, self, "addSubview:", view)
}
@(objc_class="NSWindow") @(objc_class="NSWindow")
Window :: struct {using _: Responder} Window :: struct {using _: Responder}
@@ -748,4 +756,28 @@ Window_hasTitleBar :: proc "c" (self: ^Window) -> BOOL {
@(objc_type=Window, objc_name="orderedIndex") @(objc_type=Window, objc_name="orderedIndex")
Window_orderedIndex :: proc "c" (self: ^Window) -> Integer { Window_orderedIndex :: proc "c" (self: ^Window) -> Integer {
return msgSend(Integer, self, "orderedIndex") return msgSend(Integer, self, "orderedIndex")
}
@(objc_type=Window, objc_name="setMinSize")
Window_setMinSize :: proc "c" (self: ^Window, size: Size) {
msgSend(nil, self, "setMinSize:", size)
}
@(objc_type=Window, objc_name="setTitleVisibility")
Window_setTitleVisibility :: proc "c" (self: ^Window, visibility: Window_Title_Visibility) {
msgSend(nil, self, "setTitleVisibility:", visibility)
}
@(objc_type=Window, objc_name="performZoom")
Window_performZoom :: proc "c" (self: ^Window) {
msgSend(nil, self, "performZoom:", self)
}
@(objc_type=Window, objc_name="setFrameAutosaveName")
NSWindow_setFrameAutosaveName :: proc "c" (self: ^Window, name: ^String) {
msgSend(nil, self, "setFrameAutosaveName:", name)
}
@(objc_type=Window, objc_name="performWindowDragWithEvent")
Window_performWindowDragWithEvent :: proc "c" (self: ^Window, event: ^Event) {
msgSend(nil, self, "performWindowDragWithEvent:", event)
}
@(objc_type=Window, objc_name="setToolbar")
Window_setToolbar :: proc "c" (self: ^Window, toolbar: ^Toolbar) {
msgSend(nil, self, "setToolbar:", toolbar)
} }
+12 -16
View File
@@ -86,22 +86,18 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent,
/// The lifetime of the string is bound to the lifetime of the provided dirent structure /// The lifetime of the string is bound to the lifetime of the provided dirent structure
dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
str := ([^]u8)(&dirent.name) str := ([^]u8)(&dirent.name)
// Note(flysand): The string size calculated above applies only to the ideal case // Dirents are aligned to 8 bytes, so there is guaranteed to be a null
// we subtract 1 byte from the string size, because a null terminator is guaranteed // terminator in the last 8 bytes.
// to be present. But! That said, the dirents are aligned to 8 bytes and the padding str_size := int(dirent.reclen) - cast(int)offset_of(Dirent, name)
// between the null terminator and the start of the next struct may be not initialized
// which means we also have to scan these garbage bytes. trunc := min(str_size, 8)
str_size := int(dirent.reclen) - 1 - cast(int)offset_of(Dirent, name) str_size -= trunc
// This skips *only* over the garbage, since if we're not garbage we're at nul terminator, for _ in 0..<trunc {
// which skips this loop str_size += 1
for str[str_size] != 0 { if str[str_size] == 0 {
str_size -= 1 break
}
} }
for str[str_size-1] == 0 {
str_size -= 1
}
// Oh yeah btw i could also just `repne scasb` this thing, but honestly I started doing
// it the painful way, might as well finish doing it that way
return string(str[:str_size]) return string(str[:str_size])
} }
@@ -117,4 +113,4 @@ perf_cache_config :: #force_inline proc "contextless" (id: Perf_Hardware_Cache_I
op: Perf_Hardware_Cache_Op_Id, op: Perf_Hardware_Cache_Op_Id,
res: Perf_Hardware_Cache_Result_Id) -> u64 { res: Perf_Hardware_Cache_Result_Id) -> u64 {
return u64(id) | (u64(op) << 8) | (u64(res) << 16) return u64(id) | (u64(op) << 8) | (u64(res) << 16)
} }
+1 -1
View File
@@ -5,6 +5,6 @@ foreign import "system:Dnsapi.lib"
@(default_calling_convention="system") @(default_calling_convention="system")
foreign Dnsapi { foreign Dnsapi {
DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DNS_QUERY_OPTIONS, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS ---
DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) --- DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) ---
} }
+29
View File
@@ -123,6 +123,7 @@ foreign kernel32 {
WaitCommEvent :: proc(handle: HANDLE, lpEvtMask: LPDWORD, lpOverlapped: LPOVERLAPPED) -> BOOL --- WaitCommEvent :: proc(handle: HANDLE, lpEvtMask: LPDWORD, lpOverlapped: LPOVERLAPPED) -> BOOL ---
GetCommandLineW :: proc() -> LPCWSTR --- GetCommandLineW :: proc() -> LPCWSTR ---
GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD --- GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD ---
GetTempFileNameW :: proc(lpPathName: LPCWSTR, lpPrefixString: LPCWSTR, uUnique: c_int, lpTempFileName: LPWSTR) -> c_uint ---
GetCurrentProcess :: proc() -> HANDLE --- GetCurrentProcess :: proc() -> HANDLE ---
GetCurrentProcessId :: proc() -> DWORD --- GetCurrentProcessId :: proc() -> DWORD ---
GetCurrentThread :: proc() -> HANDLE --- GetCurrentThread :: proc() -> HANDLE ---
@@ -1240,3 +1241,31 @@ GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT)
GPTR :: (GMEM_FIXED | GMEM_ZEROINIT) GPTR :: (GMEM_FIXED | GMEM_ZEROINIT)
LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER
ACTCTXW :: struct {
Size: ULONG,
Flags: DWORD,
Source: LPCWSTR,
ProcessorArchitecture: USHORT,
LangId: LANGID,
AssemblyDirectory: LPCWSTR,
ResourceName: LPCWSTR,
ApplicationName: LPCWSTR,
Module: HMODULE,
}
PACTCTXW :: ^ACTCTXW
PCACTCTXW :: ^ACTCTXW
ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID :: 0x001
ACTCTX_FLAG_LANGID_VALID :: 0x002
ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID :: 0x004
ACTCTX_FLAG_RESOURCE_NAME_VALID :: 0x008
ACTCTX_FLAG_SET_PROCESS_DEFAULT :: 0x010
ACTCTX_FLAG_APPLICATION_NAME_VALID :: 0x020
ACTCTX_FLAG_HMODULE_VALID :: 0x080
@(default_calling_convention="system")
foreign kernel32 {
CreateActCtxW :: proc(pActCtx: ^ACTCTXW) -> HANDLE ---
ActivateActCtx :: proc(hActCtx: HANDLE, lpCookie: ^ULONG_PTR) -> BOOL ---
}
+25
View File
@@ -4576,6 +4576,31 @@ DNS_SRV_DATAA :: struct {
_: WORD, // padding _: WORD, // padding
} }
// See https://learn.microsoft.com/en-us/windows/win32/dns/dns-constants
DNS_QUERY_OPTION :: enum DWORD {
ACCEPT_TRUNCATED_RESPONSE = 0,
DNS_QUERY_USE_TCP_ONLY = 1,
NO_RECURSION = 2,
BYPASS_CACHE = 3,
NO_WIRE_QUERY = 4,
NO_LOCAL_NAME = 5,
NO_HOSTS_FILE = 6,
NO_NETBT = 7,
WIRE_ONLY = 8,
RETURN_MESSAGE = 9,
MULTICAST_ONLY = 10,
NO_MULTICAST = 11,
TREAT_AS_FQDN = 12,
ADDRCONFIG = 13,
DUAL_ADDR = 14,
MULTICAST_WAIT = 17,
MULTICAST_VERIFY = 18,
DONT_RESET_TTL_VALUES = 20,
DISABLE_IDN_ENCODING = 21,
APPEND_MULTILABEL = 23,
}
DNS_QUERY_OPTIONS :: bit_set[DNS_QUERY_OPTION; DWORD]
SOCKADDR :: struct { SOCKADDR :: struct {
sa_family: ADDRESS_FAMILY, sa_family: ADDRESS_FAMILY,
sa_data: [14]CHAR, sa_data: [14]CHAR,
+12 -4
View File
@@ -682,11 +682,14 @@ find_aux :: proc(
// iterative matching which returns the 0th/1st match // iterative matching which returns the 0th/1st match
// rest has to be used from captures // rest has to be used from captures
// assumes captures is zeroed on first iteration
// resets captures to zero on last iteration
gmatch :: proc( gmatch :: proc(
haystack: ^string, haystack: ^string,
pattern: string, pattern: string,
captures: ^[MAX_CAPTURES]Match, captures: ^[MAX_CAPTURES]Match,
) -> (res: string, ok: bool) { ) -> (res: string, ok: bool) {
haystack^ = haystack[captures[0].byte_end:]
if len(haystack) > 0 { if len(haystack) > 0 {
length, err := find_aux(haystack^, pattern, 0, false, captures) length, err := find_aux(haystack^, pattern, 0, false, captures)
@@ -695,10 +698,11 @@ gmatch :: proc(
first := length > 1 ? 1 : 0 first := length > 1 ? 1 : 0
cap := captures[first] cap := captures[first]
res = haystack[cap.byte_start:cap.byte_end] res = haystack[cap.byte_start:cap.byte_end]
haystack^ = haystack[cap.byte_end:]
} }
} }
if !ok {
captures^ = {}
}
return return
} }
@@ -794,11 +798,14 @@ gsub_with :: proc(
gsub :: proc { gsub_builder, gsub_allocator } gsub :: proc { gsub_builder, gsub_allocator }
// iterative find with zeroth capture only // iterative find with zeroth capture only
// assumes captures is zeroed on first iteration
// resets captures to zero on last iteration
gfind :: proc( gfind :: proc(
haystack: ^string, haystack: ^string,
pattern: string, pattern: string,
captures: ^[MAX_CAPTURES]Match, captures: ^[MAX_CAPTURES]Match,
) -> (res: string, ok: bool) { ) -> (res: string, ok: bool) {
haystack^ = haystack[captures[0].byte_end:]
if len(haystack) > 0 { if len(haystack) > 0 {
length, err := find_aux(haystack^, pattern, 0, true, captures) length, err := find_aux(haystack^, pattern, 0, true, captures)
@@ -806,10 +813,11 @@ gfind :: proc(
ok = true ok = true
cap := captures[0] cap := captures[0]
res = haystack[cap.byte_start:cap.byte_end] res = haystack[cap.byte_start:cap.byte_end]
haystack^ = haystack[cap.byte_end:]
} }
} }
if !ok {
captures^ = {}
}
return return
} }
+2 -2
View File
@@ -168,7 +168,7 @@ process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime
}, },
} }
record_sort_proc :: proc(i, j: datetime.TZ_Record) -> bool { record_sort_proc :: proc(i, j: datetime.TZ_Record) -> bool {
return i.time > j.time return i.time < j.time
} }
slice.sort_by(records, record_sort_proc) slice.sort_by(records, record_sort_proc)
@@ -179,7 +179,7 @@ process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime
} }
} }
return records[len(records)-1], true return records[0], true
} }
datetime_to_utc :: proc(dt: datetime.DateTime) -> (out: datetime.DateTime, success: bool) #optional_ok { datetime_to_utc :: proc(dt: datetime.DateTime) -> (out: datetime.DateTime, success: bool) #optional_ok {
+4 -1
View File
@@ -251,7 +251,10 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success
exp *= 10; exp *= 10;
exp += v; exp += v;
} }
big_int_exp_u64(dst, &b, exp, success); BigInt tmp = {};
mp_init(&tmp);
big_int_exp_u64(&tmp, &b, exp, success);
big_int_mul_eq(dst, &tmp);
} }
if (is_negative) { if (is_negative) {
+26 -4
View File
@@ -472,6 +472,7 @@ struct BuildContext {
bool ignore_microsoft_magic; bool ignore_microsoft_magic;
bool linker_map_file; bool linker_map_file;
bool use_single_module;
bool use_separate_modules; bool use_separate_modules;
bool module_per_file; bool module_per_file;
bool cached; bool cached;
@@ -1719,13 +1720,15 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3);
#if defined(GB_SYSTEM_WINDOWS)
if (bc->optimization_level <= 0) { if (bc->optimization_level <= 0) {
if (!is_arch_wasm()) { if (!is_arch_wasm()) {
bc->use_separate_modules = true; bc->use_separate_modules = true;
} }
} }
#endif
if (build_context.use_single_module) {
bc->use_separate_modules = false;
}
// TODO: Static map calls are bugged on `amd64sysv` abi. // TODO: Static map calls are bugged on `amd64sysv` abi.
@@ -2124,6 +2127,7 @@ gb_internal bool init_build_paths(String init_filename) {
} }
} }
bool no_crt_checks_failed = false;
if (build_context.no_crt && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR && !build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { if (build_context.no_crt && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR && !build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) {
switch (build_context.metrics.os) { switch (build_context.metrics.os) {
case TargetOs_linux: case TargetOs_linux:
@@ -2133,11 +2137,29 @@ gb_internal bool init_build_paths(String init_filename) {
case TargetOs_openbsd: case TargetOs_openbsd:
case TargetOs_netbsd: case TargetOs_netbsd:
case TargetOs_haiku: case TargetOs_haiku:
gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); gb_printf_err("-no-crt on Unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present, because the default allocator requires CRT\n");
return false; no_crt_checks_failed = true;
} }
} }
if (build_context.no_crt && !build_context.no_thread_local) {
switch (build_context.metrics.os) {
case TargetOs_linux:
case TargetOs_darwin:
case TargetOs_essence:
case TargetOs_freebsd:
case TargetOs_openbsd:
case TargetOs_netbsd:
case TargetOs_haiku:
gb_printf_err("-no-crt on Unix systems requires the -no-thread-local flag to also be present, because the TLS is inaccessible without CRT\n");
no_crt_checks_failed = true;
}
}
if (no_crt_checks_failed) {
return false;
}
return true; return true;
} }
+1 -1
View File
@@ -60,7 +60,7 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o
error(operand->expr, "Cannot assign a type '%s' to variable '%.*s'", t, LIT(e->token.string)); error(operand->expr, "Cannot assign a type '%s' to variable '%.*s'", t, LIT(e->token.string));
} }
if (e->type == nullptr) { if (e->type == nullptr) {
error_line("\tThe type of the variable '%.*s' cannot be inferred as a type does not have a default type\n", LIT(e->token.string)); error_line("\tThe type of the variable '%.*s' cannot be inferred as a type and does not have a default type\n", LIT(e->token.string));
} }
e->type = operand->type; e->type = operand->type;
return nullptr; return nullptr;
+5 -5
View File
@@ -1044,7 +1044,7 @@ gb_internal AstPackage *get_package_of_type(Type *type) {
} }
// NOTE(bill): 'content_name' is for debugging and error messages // NOTE(bill): 'context_name' is for debugging and error messages
gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *type, String context_name) { gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *type, String context_name) {
check_not_tuple(c, operand); check_not_tuple(c, operand);
if (operand->mode == Addressing_Invalid) { if (operand->mode == Addressing_Invalid) {
@@ -1973,10 +1973,10 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) {
case Token_Quo: case Token_Quo:
case Token_QuoEq: case Token_QuoEq:
if (is_type_matrix(main_type)) { if (is_type_matrix(main_type)) {
error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); error(op, "Operator '%.*s' is not allowed with matrix types", LIT(op.string));
return false; return false;
} else if (is_type_simd_vector(main_type) && is_type_integer(type)) { } else if (is_type_simd_vector(main_type) && is_type_integer(type)) {
error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); error(op, "Operator '%.*s' is not allowed with #simd types with integer elements", LIT(op.string));
return false; return false;
} }
/*fallthrough*/ /*fallthrough*/
@@ -2023,14 +2023,14 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) {
case Token_ModEq: case Token_ModEq:
case Token_ModModEq: case Token_ModModEq:
if (is_type_matrix(main_type)) { if (is_type_matrix(main_type)) {
error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); error(op, "Operator '%.*s' is not allowed with matrix types", LIT(op.string));
return false; return false;
} }
if (!is_type_integer(type)) { if (!is_type_integer(type)) {
error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string));
return false; return false;
} else if (is_type_simd_vector(main_type)) { } else if (is_type_simd_vector(main_type)) {
error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); error(op, "Operator '%.*s' is not allowed with #simd types with integer elements", LIT(op.string));
return false; return false;
} }
break; break;
+2 -1
View File
@@ -685,7 +685,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
ST_ALIGN(min_field_align); ST_ALIGN(min_field_align);
ST_ALIGN(max_field_align); ST_ALIGN(max_field_align);
ST_ALIGN(align); ST_ALIGN(align);
if (struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { if (struct_type->Struct.custom_align != 0 &&
struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) {
error(st->align, "#align(%lld) is defined to be less than #min_field_align(%lld)", error(st->align, "#align(%lld) is defined to be less than #min_field_align(%lld)",
cast(long long)struct_type->Struct.custom_align, cast(long long)struct_type->Struct.custom_align,
cast(long long)struct_type->Struct.custom_min_field_align); cast(long long)struct_type->Struct.custom_min_field_align);
+18 -3
View File
@@ -749,9 +749,15 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl
// TODO(bill): When is a good size warn? // TODO(bill): When is a good size warn?
// Is >256 KiB good enough? // Is >256 KiB good enough?
if (sz > 1ll<<18) { if (sz > 1ll<<18) {
gbString type_str = type_to_string(e->type); bool is_ref = false;
warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); if((e->flags & EntityFlag_ForValue) != 0) {
gb_string_free(type_str); is_ref = type_deref(e->Variable.for_loop_parent_type) != NULL;
}
if(!is_ref) {
gbString type_str = type_to_string(e->type);
warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz);
gb_string_free(type_str);
}
} }
} }
} }
@@ -5034,6 +5040,12 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) {
ac->extra_linker_flags = ev.value_string; ac->extra_linker_flags = ev.value_string;
} }
return true; return true;
} else if (name == "ignore_duplicates") {
if (value != nullptr) {
error(elem, "Expected no parameter for '%.*s'", LIT(name));
}
ac->ignore_duplicates = true;
return true;
} }
return false; return false;
} }
@@ -5184,6 +5196,9 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) {
if (ac.foreign_import_priority_index != 0) { if (ac.foreign_import_priority_index != 0) {
e->LibraryName.priority_index = ac.foreign_import_priority_index; e->LibraryName.priority_index = ac.foreign_import_priority_index;
} }
if (ac.ignore_duplicates) {
e->LibraryName.ignore_duplicates = true;
}
String extra_linker_flags = string_trim_whitespace(ac.extra_linker_flags); String extra_linker_flags = string_trim_whitespace(ac.extra_linker_flags);
if (extra_linker_flags.len != 0) { if (extra_linker_flags.len != 0) {
e->LibraryName.extra_linker_flags = extra_linker_flags; e->LibraryName.extra_linker_flags = extra_linker_flags;
+1
View File
@@ -140,6 +140,7 @@ struct AttributeContext {
bool instrumentation_enter : 1; bool instrumentation_enter : 1;
bool instrumentation_exit : 1; bool instrumentation_exit : 1;
bool rodata : 1; bool rodata : 1;
bool ignore_duplicates : 1;
u32 optimization_mode; // ProcedureOptimizationMode u32 optimization_mode; // ProcedureOptimizationMode
i64 foreign_import_priority_index; i64 foreign_import_priority_index;
String extra_linker_flags; String extra_linker_flags;
+1
View File
@@ -274,6 +274,7 @@ struct Entity {
Slice<String> paths; Slice<String> paths;
String name; String name;
i64 priority_index; i64 priority_index;
bool ignore_duplicates;
String extra_linker_flags; String extra_linker_flags;
} LibraryName; } LibraryName;
i32 Nil; i32 Nil;
+32 -1
View File
@@ -449,6 +449,26 @@ gb_internal i32 linker_stage(LinkerData *gen) {
if (extra_linker_flags.len != 0) { if (extra_linker_flags.len != 0) {
lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags));
} }
if (build_context.metrics.os == TargetOs_darwin) {
// Print frameworks first
for (String lib : e->LibraryName.paths) {
lib = string_trim_whitespace(lib);
if (lib.len == 0) {
continue;
}
if (string_ends_with(lib, str_lit(".framework"))) {
if (string_set_update(&min_libs_set, lib)) {
continue;
}
String lib_name = lib;
lib_name = remove_extension_from_path(lib_name);
lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name));
}
}
}
for (String lib : e->LibraryName.paths) { for (String lib : e->LibraryName.paths) {
lib = string_trim_whitespace(lib); lib = string_trim_whitespace(lib);
if (lib.len == 0) { if (lib.len == 0) {
@@ -536,7 +556,18 @@ gb_internal i32 linker_stage(LinkerData *gen) {
} }
array_add(&gen->output_object_paths, obj_file); array_add(&gen->output_object_paths, obj_file);
} else { } else {
if (string_set_update(&min_libs_set, lib) && build_context.min_link_libs) { bool short_circuit = false;
if (string_ends_with(lib, str_lit(".framework"))) {
short_circuit = true;
} else if (string_ends_with(lib, str_lit(".dylib"))) {
short_circuit = true;
} else if (string_ends_with(lib, str_lit(".so"))) {
short_circuit = true;
} else if (e->LibraryName.ignore_duplicates) {
short_circuit = true;
}
if (string_set_update(&min_libs_set, lib) && (build_context.min_link_libs || short_circuit)) {
continue; continue;
} }
+2 -2
View File
@@ -1147,14 +1147,14 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) {
String name = entry.key; String name = entry.key;
args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args); lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args);
lb_addr_store(p, entry.value, ptr); lb_addr_store(p, entry.value.local_module_addr, ptr);
} }
for (auto const &entry : m->objc_selectors) { for (auto const &entry : m->objc_selectors) {
String name = entry.key; String name = entry.key;
args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args);
lb_addr_store(p, entry.value, ptr); lb_addr_store(p, entry.value.local_module_addr, ptr);
} }
lb_end_procedure_body(p); lb_end_procedure_body(p);
+7 -2
View File
@@ -143,6 +143,11 @@ struct lbPadType {
LLVMTypeRef type; LLVMTypeRef type;
}; };
struct lbObjcRef {
Entity * entity;
lbAddr local_module_addr;
};
struct lbModule { struct lbModule {
LLVMModuleRef mod; LLVMModuleRef mod;
LLVMContextRef ctx; LLVMContextRef ctx;
@@ -196,8 +201,8 @@ struct lbModule {
RecursiveMutex debug_values_mutex; RecursiveMutex debug_values_mutex;
PtrMap<void *, LLVMMetadataRef> debug_values; PtrMap<void *, LLVMMetadataRef> debug_values;
StringMap<lbAddr> objc_classes; StringMap<lbObjcRef> objc_classes;
StringMap<lbAddr> objc_selectors; StringMap<lbObjcRef> objc_selectors;
PtrMap<Type *, lbAddr> map_cell_info_map; // address of runtime.Map_Info PtrMap<Type *, lbAddr> map_cell_info_map; // address of runtime.Map_Info
PtrMap<Type *, lbAddr> map_info_map; // address of runtime.Map_Cell_Info PtrMap<Type *, lbAddr> map_info_map; // address of runtime.Map_Cell_Info
+11 -10
View File
@@ -4294,6 +4294,17 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) {
gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) {
ast_node(se, SliceExpr, expr); ast_node(se, SliceExpr, expr);
lbAddr addr = lb_build_addr(p, se->expr);
lbValue base = lb_addr_load(p, addr);
Type *type = base_type(base.type);
if (is_type_pointer(type)) {
type = base_type(type_deref(type));
addr = lb_addr(base);
base = lb_addr_load(p, addr);
}
lbValue low = lb_const_int(p->module, t_int, 0); lbValue low = lb_const_int(p->module, t_int, 0);
lbValue high = {}; lbValue high = {};
@@ -4306,16 +4317,6 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) {
bool no_indices = se->low == nullptr && se->high == nullptr; bool no_indices = se->low == nullptr && se->high == nullptr;
lbAddr addr = lb_build_addr(p, se->expr);
lbValue base = lb_addr_load(p, addr);
Type *type = base_type(base.type);
if (is_type_pointer(type)) {
type = base_type(type_deref(type));
addr = lb_addr(base);
base = lb_addr_load(p, addr);
}
switch (type->kind) { switch (type->kind) {
case Type_Slice: { case Type_Slice: {
Type *slice_type = type; Type *slice_type = type;
+53 -47
View File
@@ -2093,23 +2093,34 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export
gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) {
lbAddr *found = string_map_get(&p->module->objc_selectors, name); lbObjcRef *found = string_map_get(&p->module->objc_selectors, name);
if (found) { if (found) {
return *found; return found->local_module_addr;
} else {
lbModule *default_module = &p->module->gen->default_module;
Entity *e = nullptr;
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e);
lbValue ptr = lb_find_value_from_entity(p->module, e);
lbAddr local_addr = lb_addr(ptr);
string_map_set(&default_module->objc_selectors, name, default_addr);
if (default_module != p->module) {
string_map_set(&p->module->objc_selectors, name, local_addr);
}
return local_addr;
} }
lbModule *default_module = &p->module->gen->default_module;
Entity *entity = {};
if (default_module != p->module) {
found = string_map_get(&default_module->objc_selectors, name);
if (found) {
entity = found->entity;
}
}
if (!entity) {
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &entity);
string_map_set(&default_module->objc_selectors, name, lbObjcRef{entity, default_addr});
}
lbValue ptr = lb_find_value_from_entity(p->module, entity);
lbAddr local_addr = lb_addr(ptr);
if (default_module != p->module) {
string_map_set(&p->module->objc_selectors, name, lbObjcRef{entity, local_addr});
}
return local_addr;
} }
gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) {
@@ -2139,23 +2150,34 @@ gb_internal lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr)
} }
gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) {
lbAddr *found = string_map_get(&p->module->objc_classes, name); lbObjcRef *found = string_map_get(&p->module->objc_classes, name);
if (found) { if (found) {
return *found; return found->local_module_addr;
} else {
lbModule *default_module = &p->module->gen->default_module;
Entity *e = nullptr;
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e);
lbValue ptr = lb_find_value_from_entity(p->module, e);
lbAddr local_addr = lb_addr(ptr);
string_map_set(&default_module->objc_classes, name, default_addr);
if (default_module != p->module) {
string_map_set(&p->module->objc_classes, name, local_addr);
}
return local_addr;
} }
lbModule *default_module = &p->module->gen->default_module;
Entity *entity = {};
if (default_module != p->module) {
found = string_map_get(&default_module->objc_classes, name);
if (found) {
entity = found->entity;
}
}
if (!entity) {
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &entity);
string_map_set(&default_module->objc_classes, name, lbObjcRef{entity, default_addr});
}
lbValue ptr = lb_find_value_from_entity(p->module, entity);
lbAddr local_addr = lb_addr(ptr);
if (default_module != p->module) {
string_map_set(&p->module->objc_classes, name, lbObjcRef{entity, local_addr});
}
return local_addr;
} }
gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) {
@@ -2196,23 +2218,7 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) {
GB_ASSERT(e->kind == Entity_TypeName); GB_ASSERT(e->kind == Entity_TypeName);
String name = e->TypeName.objc_class_name; String name = e->TypeName.objc_class_name;
lbAddr *found = string_map_get(&p->module->objc_classes, name); return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name));
if (found) {
return lb_addr_load(p, *found);
} else {
lbModule *default_module = &p->module->gen->default_module;
Entity *e = nullptr;
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &e);
lbValue ptr = lb_find_value_from_entity(p->module, e);
lbAddr local_addr = lb_addr(ptr);
string_map_set(&default_module->objc_classes, name, default_addr);
if (default_module != p->module) {
string_map_set(&p->module->objc_classes, name, local_addr);
}
return lb_addr_load(p, local_addr);
}
} }
return lb_build_expr(p, expr); return lb_build_expr(p, expr);
+27 -3
View File
@@ -331,6 +331,7 @@ enum BuildFlagKind {
BuildFlag_UseRADLink, BuildFlag_UseRADLink,
BuildFlag_Linker, BuildFlag_Linker,
BuildFlag_UseSeparateModules, BuildFlag_UseSeparateModules,
BuildFlag_UseSingleModule,
BuildFlag_NoThreadedChecker, BuildFlag_NoThreadedChecker,
BuildFlag_ShowDebugMessages, BuildFlag_ShowDebugMessages,
@@ -545,6 +546,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
add_flag(&build_flags, BuildFlag_UseRADLink, str_lit("radlink"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_UseRADLink, str_lit("radlink"), BuildFlagParam_None, Command__does_build);
add_flag(&build_flags, BuildFlag_Linker, str_lit("linker"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_Linker, str_lit("linker"), BuildFlagParam_String, Command__does_build);
add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build);
add_flag(&build_flags, BuildFlag_UseSingleModule, str_lit("use-single-module"), BuildFlagParam_None, Command__does_build);
add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all);
@@ -1240,8 +1242,19 @@ gb_internal bool parse_build_flags(Array<String> args) {
case BuildFlag_UseSeparateModules: case BuildFlag_UseSeparateModules:
if (build_context.use_single_module) {
gb_printf_err("-use-separate-modules cannot be used with -use-single-module\n");
bad_flags = true;
}
build_context.use_separate_modules = true; build_context.use_separate_modules = true;
break; break;
case BuildFlag_UseSingleModule:
if (build_context.use_separate_modules) {
gb_printf_err("-use-single-module cannot be used with -use-separate-modules\n");
bad_flags = true;
}
build_context.use_single_module = true;
break;
case BuildFlag_NoThreadedChecker: case BuildFlag_NoThreadedChecker:
build_context.no_threaded_checker = true; build_context.no_threaded_checker = true;
break; break;
@@ -1801,7 +1814,10 @@ gb_internal void check_defines(BuildContext *bc, Checker *c) {
if (!found) { if (!found) {
ERROR_BLOCK(); ERROR_BLOCK();
warning(nullptr, "given -define:%.*s is unused in the project", LIT(name)); warning(nullptr, "given -define:%.*s is unused in the project", LIT(name));
error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n");
if (!global_ignore_warnings()) {
error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n");
}
} }
} }
} }
@@ -2331,6 +2347,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing.");
} }
if (print_flag("-default-to-panic-allocator")) {
print_usage_line(2, "Sets the default allocator to be the panic_allocator, an allocator which calls panic() on any allocation attempt.");
}
if (print_flag("-define:<name>=<value>")) { if (print_flag("-define:<name>=<value>")) {
print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); print_usage_line(2, "Defines a scalar boolean, integer or string as global constant.");
print_usage_line(2, "Example: -define:SPAM=123"); print_usage_line(2, "Example: -define:SPAM=123");
@@ -2710,8 +2730,12 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (run_or_build) { if (run_or_build) {
if (print_flag("-use-separate-modules")) { if (print_flag("-use-separate-modules")) {
print_usage_line(2, "The backend generates multiple build units which are then linked together."); print_usage_line(2, "The backend generates multiple build units which are then linked together.");
print_usage_line(2, "Normally, a single build unit is generated for a standard project."); print_usage_line(2, "This is the default behaviour for '-o:none' and '-o:minimal' builds.");
print_usage_line(2, "This is the default behaviour on Windows for '-o:none' and '-o:minimal' builds."); print_usage_line(2, "Normally, a single build unit is generated for a standard project for '-o:speed' or '-o:size'.");
}
if (print_flag("-use-single-module")) {
print_usage_line(2, "The backend generates only a single build unit.");
print_usage_line(2, "This is the default behaviour for '-o:speed' or '-o:size'.");
} }
} }
+3 -1
View File
@@ -4773,7 +4773,9 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
case Type_BitSet: case Type_BitSet:
str = gb_string_appendc(str, "bit_set["); str = gb_string_appendc(str, "bit_set[");
if (is_type_enum(type->BitSet.elem)) { if (type->BitSet.elem == nullptr) {
str = gb_string_appendc(str, "<unresolved>");
} else if (is_type_enum(type->BitSet.elem)) {
str = write_type_to_string(str, type->BitSet.elem); str = write_type_to_string(str, type->BitSet.elem);
} else { } else {
str = gb_string_append_fmt(str, "%lld", type->BitSet.lower); str = gb_string_append_fmt(str, "%lld", type->BitSet.lower);
+22
View File
@@ -0,0 +1,22 @@
package tests_core_os_os2
import os "core:os/os2"
import "core:log"
import "core:path/filepath"
import "core:testing"
import "core:strings"
@(test)
test_executable :: proc(t: ^testing.T) {
path, err := os.get_executable_path(context.allocator)
defer delete(path)
log.infof("executable path: %q", path)
// NOTE: some sanity checks that should always be the case, at least in the CI.
testing.expect_value(t, err, nil)
testing.expect(t, len(path) > 0)
testing.expect(t, filepath.is_abs(path))
testing.expectf(t, strings.contains(path, filepath.base(os.args[0])), "expected the executable path to contain the base of os.args[0] which is %q", filepath.base(os.args[0]))
}
+1 -2
View File
@@ -7,7 +7,6 @@
package vendor_commonmark package vendor_commonmark
import "core:c" import "core:c"
import "core:c/libc"
import "base:runtime" import "base:runtime"
COMMONMARK_SHARED :: #config(COMMONMARK_SHARED, false) COMMONMARK_SHARED :: #config(COMMONMARK_SHARED, false)
@@ -450,7 +449,7 @@ foreign lib {
// Called `parse_from_libc_file` so as not to confuse with Odin's file handling. // Called `parse_from_libc_file` so as not to confuse with Odin's file handling.
@(link_name = "parse_from_file") @(link_name = "parse_from_file")
parse_from_libc_file :: proc(file: ^libc.FILE, options: Options) -> (root: ^Node) --- parse_from_libc_file :: proc(file: ^c.FILE, options: Options) -> (root: ^Node) ---
} }
parser_feed_from_string :: proc "c" (parser: ^Parser, s: string) { parser_feed_from_string :: proc "c" (parser: ^Parser, s: string) {
+6 -6
View File
@@ -1580,14 +1580,14 @@ SHADER_COMPONENT_MAPPING :: enum u32 {
FORCE_VALUE_1 = 5, FORCE_VALUE_1 = 5,
} }
ENCODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (Src0, Src1, Src2, Src3: u32) -> u32 { ENCODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (Src0, Src1, Src2, Src3: u32) -> u32 {
return (Src0 & SHADER_COMPONENT_MAPPING_MASK) | return (Src0 & SHADER_COMPONENT_MAPPING_MASK) |
((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) | ((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) |
((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) | ((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) |
((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) | ((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) |
SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES
} }
DECODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (ComponentToExtract, Mapping: u32) -> u32 { DECODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (ComponentToExtract, Mapping: u32) -> u32 {
return Mapping >> (SHADER_COMPONENT_MAPPING_SHIFT * ComponentToExtract) & SHADER_COMPONENT_MAPPING_MASK return Mapping >> (SHADER_COMPONENT_MAPPING_SHIFT * ComponentToExtract) & SHADER_COMPONENT_MAPPING_MASK
} }
BUFFER_SRV_FLAGS :: distinct bit_set[BUFFER_SRV_FLAG; u32] BUFFER_SRV_FLAGS :: distinct bit_set[BUFFER_SRV_FLAG; u32]
+1
View File
@@ -71,6 +71,7 @@ foreign glfw {
GetPrimaryMonitor :: proc() -> MonitorHandle --- GetPrimaryMonitor :: proc() -> MonitorHandle ---
GetMonitors :: proc(count: ^c.int) -> [^]MonitorHandle --- GetMonitors :: proc(count: ^c.int) -> [^]MonitorHandle ---
GetMonitorPos :: proc(monitor: MonitorHandle, xpos, ypos: ^c.int) --- GetMonitorPos :: proc(monitor: MonitorHandle, xpos, ypos: ^c.int) ---
GetMonitorWorkarea :: proc(monitor: MonitorHandle, xpos, ypos, width, height: ^c.int) ---
GetMonitorPhysicalSize :: proc(monitor: MonitorHandle, widthMM, heightMM: ^c.int) --- GetMonitorPhysicalSize :: proc(monitor: MonitorHandle, widthMM, heightMM: ^c.int) ---
GetMonitorContentScale :: proc(monitor: MonitorHandle, xscale, yscale: ^f32) --- GetMonitorContentScale :: proc(monitor: MonitorHandle, xscale, yscale: ^f32) ---
+4
View File
@@ -33,6 +33,10 @@ GetMonitorPos :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos: c.int) {
glfw.GetMonitorPos(monitor, &xpos, &ypos) glfw.GetMonitorPos(monitor, &xpos, &ypos)
return return
} }
GetMonitorWorkarea :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos, width, height: c.int) {
glfw.GetMonitorWorkarea(monitor, &xpos, &ypos, &width, &height)
return
}
GetMonitorPhysicalSize :: proc "c" (monitor: MonitorHandle) -> (widthMM, heightMM: c.int) { GetMonitorPhysicalSize :: proc "c" (monitor: MonitorHandle) -> (widthMM, heightMM: c.int) {
glfw.GetMonitorPhysicalSize(monitor, &widthMM, &heightMM) glfw.GetMonitorPhysicalSize(monitor, &widthMM, &heightMM)
return return
+8 -5
View File
@@ -351,8 +351,11 @@ device_id :: struct #raw_union {
nullbackend: c.int, /* The null backend uses an integer for device IDs. */ nullbackend: c.int, /* The null backend uses an integer for device IDs. */
} }
data_format_flag :: enum c.int {
EXCLUSIVE_MODE = 1, /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */
}
DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: 1 << 1 /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ data_format_flags :: bit_set[data_format_flag; u32]
MAX_DEVICE_NAME_LENGTH :: 255 MAX_DEVICE_NAME_LENGTH :: 255
@@ -364,10 +367,10 @@ device_info :: struct {
nativeDataFormatCount: u32, nativeDataFormatCount: u32,
nativeDataFormats: [/*len(format_count) * standard_sample_rate.rate_count * MAX_CHANNELS*/ 64]struct { /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ nativeDataFormats: [/*len(format_count) * standard_sample_rate.rate_count * MAX_CHANNELS*/ 64]struct { /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */
format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */
channels: u32, /* If set to 0, all channels are supported. */ channels: u32, /* If set to 0, all channels are supported. */
sampleRate: u32, /* If set to 0, all sample rates are supported. */ sampleRate: u32, /* If set to 0, all sample rates are supported. */
flags: u32, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ flags: data_format_flags, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */
}, },
} }
+31 -29
View File
@@ -11,20 +11,22 @@ Engine
************************************************************************************************************************************************************/ ************************************************************************************************************************************************************/
/* Sound flags. */ /* Sound flags. */
sound_flags :: enum c.int { sound_flag :: enum c.int {
/* Resource manager flags. */ /* Resource manager flags. */
STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ STREAM = 0, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */
DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ DECODE = 1, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */
ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ ASYNC = 2, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ WAIT_INIT = 3, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ UNKNOWN_LENGTH = 4, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
/* ma_sound specific flags. */ /* ma_sound specific flags. */
NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ NO_DEFAULT_ATTACHMENT = 12, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
NO_PITCH = 0x00002000, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ NO_PITCH = 13, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */
NO_SPATIALIZATION = 0x00004000, /* Disable spatialization. */ NO_SPATIALIZATION = 14, /* Disable spatialization. */
} }
sound_flags :: bit_set[sound_flag; u32]
ENGINE_MAX_LISTENERS :: 4 ENGINE_MAX_LISTENERS :: 4
LISTENER_INDEX_CLOSEST :: 255 LISTENER_INDEX_CLOSEST :: 255
@@ -81,7 +83,7 @@ engine_node :: struct {
@(default_calling_convention="c", link_prefix="ma_") @(default_calling_convention="c", link_prefix="ma_")
foreign lib { foreign lib {
engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: u32) -> engine_node_config --- engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: sound_flags) -> engine_node_config ---
engine_node_get_heap_size :: proc(pConfig: ^engine_node_config, pHeapSizeInBytes: ^c.size_t) -> result --- engine_node_get_heap_size :: proc(pConfig: ^engine_node_config, pHeapSizeInBytes: ^c.size_t) -> result ---
engine_node_init_preallocated :: proc(pConfig: ^engine_node_config, pHeap: rawptr, pEngineNode: ^engine_node) -> result --- engine_node_init_preallocated :: proc(pConfig: ^engine_node_config, pHeap: rawptr, pEngineNode: ^engine_node) -> result ---
@@ -96,17 +98,17 @@ SOUND_SOURCE_CHANNEL_COUNT :: 0xFFFFFFFF
sound_end_proc :: #type proc "c" (pUserData: rawptr, pSound: ^sound) sound_end_proc :: #type proc "c" (pUserData: rawptr, pSound: ^sound)
sound_config :: struct { sound_config :: struct {
pFilePath: cstring, /* Set this to load from the resource manager. */ pFilePath: cstring, /* Set this to load from the resource manager. */
pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */
pDataSource: ^data_source, /* Set this to load from an existing data source. */ pDataSource: ^data_source, /* Set this to load from an existing data source. */
pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */
initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */
channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */
channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */
monoExpansionMode: mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ monoExpansionMode: mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */
flags: u32, /* A combination of MA_SOUND_FLAG_* flags. */ flags: sound_flags, /* A combination of MA_SOUND_FLAG_* flags. */
volumeSmoothTimeInPCMFrames: u32, /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ volumeSmoothTimeInPCMFrames: u32, /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */
initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */
rangeBegInPCMFrames: u64, rangeBegInPCMFrames: u64,
rangeEndInPCMFrames: u64, rangeEndInPCMFrames: u64,
loopPointBegInPCMFrames: u64, loopPointBegInPCMFrames: u64,
@@ -148,14 +150,14 @@ sound_inlined :: struct {
@(default_calling_convention="c", link_prefix="ma_") @(default_calling_convention="c", link_prefix="ma_")
foreign lib { foreign lib {
@(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.") @(deprecated="Will be removed in 0.12. Use sound_config_init_2() instead.")
sound_config_init :: proc() -> sound_config --- sound_config_init :: proc() -> sound_config ---
sound_config_init2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */ sound_config_init_2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */
sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result ---
sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result ---
sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result ---
sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result ---
sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result --- sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result ---
sound_uninit :: proc(pSound: ^sound) --- sound_uninit :: proc(pSound: ^sound) ---
sound_get_engine :: proc(pSound: ^sound) -> ^engine --- sound_get_engine :: proc(pSound: ^sound) -> ^engine ---
@@ -239,11 +241,11 @@ sound_group :: distinct sound
@(default_calling_convention="c", link_prefix="ma_") @(default_calling_convention="c", link_prefix="ma_")
foreign lib { foreign lib {
@(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.") @(deprecated="Will be removed in 0.12. Use sound_config_init_2() instead.")
sound_group_config_init :: proc() -> sound_group_config --- sound_group_config_init :: proc() -> sound_group_config ---
sound_group_config_init2 :: proc(pEngine: ^engine) -> sound_group_config --- sound_group_config_init_2 :: proc(pEngine: ^engine) -> sound_group_config ---
sound_group_init :: proc(pEngine: ^engine, flags: u32, pParentGroup, pGroup: ^sound_group) -> result --- sound_group_init :: proc(pEngine: ^engine, flags: sound_flags, pParentGroup, pGroup: ^sound_group) -> result ---
sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result --- sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result ---
sound_group_uninit :: proc(pGroup: ^sound_group) --- sound_group_uninit :: proc(pGroup: ^sound_group) ---
sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine --- sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine ---
+8 -6
View File
@@ -108,7 +108,7 @@ job :: struct {
pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/,
pFilePath: cstring, pFilePath: cstring,
pFilePathW: [^]c.wchar_t, pFilePathW: [^]c.wchar_t,
flags: u32, /* Resource manager data source flags that were used when initializing the data buffer. */ flags: resource_manager_data_source_flags, /* Resource manager data source flags that were used when initializing the data buffer. */
pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */
pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */
pInitFence: ^fence, /* Released when initialization of the decoder is complete. */ pInitFence: ^fence, /* Released when initialization of the decoder is complete. */
@@ -194,19 +194,21 @@ ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if not
This flag should always be used for platforms that do not support multithreading. This flag should always be used for platforms that do not support multithreading.
*/ */
job_queue_flags :: enum c.int { job_queue_flag :: enum c.int {
NON_BLOCKING = 0x00000001, NON_BLOCKING = 0,
} }
job_queue_flags :: bit_set[job_queue_flag; u32]
job_queue_config :: struct { job_queue_config :: struct {
flags: u32, flags: job_queue_flags,
capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. */ capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. */
} }
USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false
job_queue :: struct { job_queue :: struct {
flags: u32, /* Flags passed in at initialization time. */ flags: job_queue_flags, /* Flags passed in at initialization time. */
capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */
head: u64, /*atomic*/ /* The first item in the list. Required for removing from the top of the list. */ head: u64, /*atomic*/ /* The first item in the list. Required for removing from the top of the list. */
tail: u64, /*atomic*/ /* The last item in the list. Required for appending to the end of the list. */ tail: u64, /*atomic*/ /* The last item in the list. Required for appending to the end of the list. */
@@ -222,7 +224,7 @@ job_queue :: struct {
@(default_calling_convention="c", link_prefix="ma_") @(default_calling_convention="c", link_prefix="ma_")
foreign lib { foreign lib {
job_queue_config_init :: proc(flags, capacity: u32) -> job_queue_config --- job_queue_config_init :: proc(flags: job_queue_flags, capacity: u32) -> job_queue_config ---
job_queue_get_heap_size :: proc(pConfig: ^job_queue_config, pHeapSizeInBytes: ^c.size_t) -> result --- job_queue_get_heap_size :: proc(pConfig: ^job_queue_config, pHeapSizeInBytes: ^c.size_t) -> result ---
job_queue_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result --- job_queue_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result ---
+16 -8
View File
@@ -22,14 +22,16 @@ NODE_BUS_COUNT_UNKNOWN :: 255
node :: struct {} node :: struct {}
/* Node flags. */ /* Node flags. */
node_flags :: enum c.int { node_flag :: enum c.int {
PASSTHROUGH = 0x00000001, PASSTHROUGH = 0,
CONTINUOUS_PROCESSING = 0x00000002, CONTINUOUS_PROCESSING = 1,
ALLOW_NULL_INPUT = 0x00000004, ALLOW_NULL_INPUT = 2,
DIFFERENT_PROCESSING_RATES = 0x00000008, DIFFERENT_PROCESSING_RATES = 3,
SILENT_OUTPUT = 0x00000010, SILENT_OUTPUT = 4,
} }
node_flags :: bit_set[node_flag; u32]
/* The playback state of a node. Either started or stopped. */ /* The playback state of a node. Either started or stopped. */
node_state :: enum c.int { node_state :: enum c.int {
started = 0, started = 0,
@@ -75,7 +77,7 @@ node_vtable :: struct {
Flags describing characteristics of the node. This is currently just a placeholder for some Flags describing characteristics of the node. This is currently just a placeholder for some
ideas for later on. ideas for later on.
*/ */
flags: u32, flags: node_flags,
} }
node_config :: struct { node_config :: struct {
@@ -87,6 +89,12 @@ node_config :: struct {
pOutputChannels: ^u32, /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ pOutputChannels: ^u32, /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */
} }
node_output_bus_flag :: enum c.int {
HAS_READ = 0, /* 0x01 */
}
node_output_bus_flags :: bit_set[node_output_bus_flag; u32]
/* /*
A node has multiple output buses. An output bus is attached to an input bus as an item in a linked A node has multiple output buses. An output bus is attached to an input bus as an item in a linked
list. Think of the input bus as a linked list, with the output bus being an item in that list. list. Think of the input bus as a linked list, with the output bus being an item in that list.
@@ -99,7 +107,7 @@ node_output_bus :: struct {
/* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */
inputNodeInputBusIndex: u8, /* The index of the input bus on the input. Required for detaching. Will only be used in the spinlock so does not need to be atomic. */ inputNodeInputBusIndex: u8, /* The index of the input bus on the input. Required for detaching. Will only be used in the spinlock so does not need to be atomic. */
flags: u32, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ flags: node_output_bus_flags, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */
refCount: u32, /*atomic*/ /* Reference count for some thread-safety when detaching. */ refCount: u32, /*atomic*/ /* Reference count for some thread-safety when detaching. */
isAttached: b32, /*atomic*/ /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ isAttached: b32, /*atomic*/ /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */
lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */
+14 -10
View File
@@ -10,14 +10,16 @@ Resource Manager
************************************************************************************************************************************************************/ ************************************************************************************************************************************************************/
resource_manager_data_source_flags :: enum c.int { resource_manager_data_source_flag :: enum c.int {
STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ STREAM = 0, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */
DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ DECODE = 1, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */
ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ ASYNC = 2, /* When set, the resource manager will load the data source asynchronously. */
WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ WAIT_INIT = 3, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */
UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ UNKNOWN_LENGTH = 4, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */
} }
resource_manager_data_source_flags :: bit_set[resource_manager_data_source_flag; u32]
/* /*
Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional.
*/ */
@@ -58,14 +60,16 @@ resource_manager_job_queue_next :: job_queue_next
/* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ /* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */
RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64 RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64
resource_manager_flags :: enum c.int { resource_manager_flag :: enum c.int {
/* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */
NON_BLOCKING = 0x00000001, NON_BLOCKING = 0,
/* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */
NO_THREADING = 0x00000002, NO_THREADING = 1,
} }
resource_manager_flags :: bit_set[resource_manager_flag; u32]
resource_manager_data_source_config :: struct { resource_manager_data_source_config :: struct {
pFilePath: cstring, pFilePath: cstring,
pFilePathW: [^]c.wchar_t, pFilePathW: [^]c.wchar_t,
@@ -126,7 +130,7 @@ resource_manager_data_buffer :: struct {
ds: data_source_base, /* Base data source. A data buffer is a data source. */ ds: data_source_base, /* Base data source. A data buffer is a data source. */
pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this buffer. */ pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this buffer. */
pNode: ^resource_manager_data_buffer_node, /* The data node. This is reference counted and is what supplies the data. */ pNode: ^resource_manager_data_buffer_node, /* The data node. This is reference counted and is what supplies the data. */
flags: u32, /* The flags that were passed used to initialize the buffer. */ flags: resource_manager_flags, /* The flags that were passed used to initialize the buffer. */
executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */
executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */
seekTargetInPCMFrames: u64, /* Only updated by the public API. Never written nor read from the job thread. */ seekTargetInPCMFrames: u64, /* Only updated by the public API. Never written nor read from the job thread. */
+7 -2
View File
@@ -119,7 +119,12 @@ offset_pcm_frames_const_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFr
data_source :: struct {} data_source :: struct {}
DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: 0x00000001 data_source_flag :: enum c.int {
SELF_MANAGED_RANGE_AND_LOOP_POINT = 0,
}
data_source_flags :: bit_set[data_source_flag; u32]
data_source_vtable :: struct { data_source_vtable :: struct {
onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result, onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result,
@@ -128,7 +133,7 @@ data_source_vtable :: struct {
onGetCursor: proc "c" (pDataSource: ^data_source, pCursor: ^u64) -> result, onGetCursor: proc "c" (pDataSource: ^data_source, pCursor: ^u64) -> result,
onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> result, onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> result,
onSetLooping: proc "c" (pDataSource: ^data_source, isLooping: b32) -> result, onSetLooping: proc "c" (pDataSource: ^data_source, isLooping: b32) -> result,
flags: u32, flags: data_source_flags,
} }
data_source_get_next_proc :: proc "c" (pDataSource: ^data_source) -> ^data_source data_source_get_next_proc :: proc "c" (pDataSource: ^data_source) -> ^data_source
+9 -7
View File
@@ -16,11 +16,13 @@ appropriate for a given situation.
vfs :: struct {} vfs :: struct {}
vfs_file :: distinct handle vfs_file :: distinct handle
open_mode_flags :: enum c.int { open_mode_flag :: enum c.int {
READ = 0x00000001, READ = 0,
WRITE = 0x00000002, WRITE = 1,
} }
open_mode_flags :: bit_set[open_mode_flag; u32]
seek_origin :: enum c.int { seek_origin :: enum c.int {
start, start,
current, current,
@@ -32,8 +34,8 @@ file_info :: struct {
} }
vfs_callbacks :: struct { vfs_callbacks :: struct {
onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result, onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: open_mode_flags, pFile: ^vfs_file) -> result,
onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: u32, pFile: ^vfs_file) -> result, onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: open_mode_flags, pFile: ^vfs_file) -> result,
onClose: proc "c" (pVFS: ^vfs, file: vfs_file) -> result, onClose: proc "c" (pVFS: ^vfs, file: vfs_file) -> result,
onRead: proc "c" (pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result, onRead: proc "c" (pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result,
onWrite: proc "c" (pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result, onWrite: proc "c" (pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result,
@@ -54,8 +56,8 @@ ma_tell_proc :: proc "c" (pUserData: rawptr, pCursor: ^i64) -> result
@(default_calling_convention="c", link_prefix="ma_") @(default_calling_convention="c", link_prefix="ma_")
foreign lib { foreign lib {
vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result --- vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: open_mode_flags, pFile: ^vfs_file) -> result ---
vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: u32, pFile: ^vfs_file) -> result --- vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: open_mode_flags, pFile: ^vfs_file) -> result ---
vfs_close :: proc(pVFS: ^vfs, file: vfs_file) -> result --- vfs_close :: proc(pVFS: ^vfs, file: vfs_file) -> result ---
vfs_read :: proc(pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result --- vfs_read :: proc(pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result ---
vfs_write :: proc(pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result --- vfs_write :: proc(pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result ---
+2 -2
View File
@@ -1205,7 +1205,7 @@ foreign lib {
CameraMoveForward :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move the camera in its forward direction CameraMoveForward :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move the camera in its forward direction
CameraMoveUp :: proc(camera: ^Camera, distance: f32) --- // move camera in its up direction CameraMoveUp :: proc(camera: ^Camera, distance: f32) --- // move camera in its up direction
CameraMoveRight :: proc(camera: ^Camera, distance: f32, delta: f32) --- // move camera in it's current right direction CameraMoveRight :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move camera in it's current right direction
CameraMoveToTarget :: proc(camera: ^Camera, delta: f32) --- // moves the camera position closer/farther to/from the camera target CameraMoveToTarget :: proc(camera: ^Camera, delta: f32) --- // moves the camera position closer/farther to/from the camera target
CameraYaw :: proc(camera: ^Camera, angle: f32, rotateAroundTarget: bool) --- // rotates the camera around its up vector (left and right) CameraYaw :: proc(camera: ^Camera, angle: f32, rotateAroundTarget: bool) --- // rotates the camera around its up vector (left and right)
CameraPitch :: proc(camera: ^Camera, angle: f32, lockView: bool, rotateAroundTarget: bool, rotateUp: bool) --- // rotates the camera around its right vector (up and down) CameraPitch :: proc(camera: ^Camera, angle: f32, lockView: bool, rotateAroundTarget: bool, rotateUp: bool) --- // rotates the camera around its right vector (up and down)
@@ -1256,7 +1256,7 @@ foreign lib {
DrawRectangleLines :: proc(posX, posY: c.int, width, height: c.int, color: Color) --- // Draw rectangle outline DrawRectangleLines :: proc(posX, posY: c.int, width, height: c.int, color: Color) --- // Draw rectangle outline
DrawRectangleLinesEx :: proc(rec: Rectangle, lineThick: f32, color: Color) --- // Draw rectangle outline with extended parameters DrawRectangleLinesEx :: proc(rec: Rectangle, lineThick: f32, color: Color) --- // Draw rectangle outline with extended parameters
DrawRectangleRounded :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle with rounded edges DrawRectangleRounded :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle with rounded edges
DrawRectangleRoundedLines :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle lines with rounded edges DrawRectangleRoundedLines :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle lines with rounded edges
DrawRectangleRoundedLinesEx :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle with rounded edges outline DrawRectangleRoundedLinesEx :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle with rounded edges outline
DrawTriangle :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw a color-filled triangle (vertex in counter-clockwise order!) DrawTriangle :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw a color-filled triangle (vertex in counter-clockwise order!)
DrawTriangleLines :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw triangle outline (vertex in counter-clockwise order!) DrawTriangleLines :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw triangle outline (vertex in counter-clockwise order!)
+2
View File
@@ -26,8 +26,10 @@ import "core:c"
import "base:intrinsics" import "base:intrinsics"
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
@(ignore_duplicates)
foreign import lib "SDL2.lib" foreign import lib "SDL2.lib"
} else { } else {
@(ignore_duplicates)
foreign import lib "system:SDL2" foreign import lib "system:SDL2"
} }
+2
View File
@@ -3,8 +3,10 @@ package sdl2
import "core:c" import "core:c"
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
@(ignore_duplicates)
foreign import lib "SDL2.lib" foreign import lib "SDL2.lib"
} else { } else {
@(ignore_duplicates)
foreign import lib "system:SDL2" foreign import lib "system:SDL2"
} }
+2
View File
@@ -3,8 +3,10 @@ package sdl2
import "core:c" import "core:c"
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
@(ignore_duplicates)
foreign import lib "SDL2.lib" foreign import lib "SDL2.lib"
} else { } else {
@(ignore_duplicates)
foreign import lib "system:SDL2" foreign import lib "system:SDL2"
} }

Some files were not shown because too many files have changed in this diff Show More